{"action":"created","issue":{"url":"https://api.github.com/repos/apache/spark/issues/15651","repository_url":"https://api.github.com/repos/apache/spark","labels_url":"https://api.github.com/repos/apache/spark/issues/15651/labels{/name}","comments_url":"https://api.github.com/repos/apache/spark/issues/15651/comments","events_url":"https://api.github.com/repos/apache/spark/issues/15651/events","html_url":"https://github.com/apache/spark/pull/15651","id":185541716,"number":15651,"title":"[SPARK-17972][SQL] Add Dataset.checkpoint() to truncate large query plans","user":{"login":"liancheng","id":230655,"avatar_url":"https://avatars.githubusercontent.com/u/230655?v=3","gravatar_id":"","url":"https://api.github.com/users/liancheng","html_url":"https://github.com/liancheng","followers_url":"https://api.github.com/users/liancheng/followers","following_url":"https://api.github.com/users/liancheng/following{/other_user}","gists_url":"https://api.github.com/users/liancheng/gists{/gist_id}","starred_url":"https://api.github.com/users/liancheng/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/liancheng/subscriptions","organizations_url":"https://api.github.com/users/liancheng/orgs","repos_url":"https://api.github.com/users/liancheng/repos","events_url":"https://api.github.com/users/liancheng/events{/privacy}","received_events_url":"https://api.github.com/users/liancheng/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":19,"created_at":"2016-10-27T00:04:18Z","updated_at":"2016-10-31T19:15:49Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/apache/spark/pulls/15651","html_url":"https://github.com/apache/spark/pull/15651","diff_url":"https://github.com/apache/spark/pull/15651.diff","patch_url":"https://github.com/apache/spark/pull/15651.patch"},"body":"## What changes were proposed in this pull request?\n### Problem\n\nIterative ML code may easily create query plans that grow exponentially. We found that query planning time also increases exponentially even when all the sub-plan trees are cached.\n\nThe following snippet illustrates the problem:\n\n``` scala\n(0 until 6).foldLeft(Seq(1, 2, 3).toDS) { (plan, iteration) =>\n  println(s\"== Iteration $iteration ==\")\n  val time0 = System.currentTimeMillis()\n  val joined = plan.join(plan, \"value\").join(plan, \"value\").join(plan, \"value\").join(plan, \"value\")\n  joined.cache()\n  println(s\"Query planning takes ${System.currentTimeMillis() - time0} ms\")\n  joined.as[Int]\n}\n\n// == Iteration 0 ==\n// Query planning takes 9 ms\n// == Iteration 1 ==\n// Query planning takes 26 ms\n// == Iteration 2 ==\n// Query planning takes 53 ms\n// == Iteration 3 ==\n// Query planning takes 163 ms\n// == Iteration 4 ==\n// Query planning takes 700 ms\n// == Iteration 5 ==\n// Query planning takes 3418 ms\n```\n\nThis is because when building a new Dataset, the new plan is always built upon `QueryExecution.analyzed`, which doesn't leverage existing cached plans.\n\nOn the other hand, usually, doing caching every a few iterations may not be the right direction for this problem since caching is too memory consuming (imaging computing connected components over a graph with 50 billion nodes). What we really need here is to truncate both the query plan (to minimize query planning time) and the lineage of the underlying RDD (to avoid stack overflow).\n### Changes introduced in this PR\n\nThis PR tries to fix this issue by introducing a `checkpoint()` method into `Dataset[T]`, which does exactly the things described above. The following snippet, which is essentially the same as the one above but invokes `checkpoint()` instead of `cache()`, shows the micro benchmark result of this PR:\n\nOne key point is that the checkpointed Dataset should preserve the origianl partitioning and ordering information of the original Dataset, so that we can avoid unnecessary shuffling (similar to reading from a pre-bucketed table). This is done by adding `outputPartitioning` and `outputOrdering` to `LogicalRDD` and `RDDScanExec`.\n### Micro benchmark\n\n``` scala\nspark.sparkContext.setCheckpointDir(\"/tmp/cp\")\n\n(0 until 100).foldLeft(Seq(1, 2, 3).toDS) { (plan, iteration) =>\n  println(s\"== Iteration $iteration ==\")\n  val time0 = System.currentTimeMillis()\n  val cp = plan.checkpoint()\n  cp.count()\n  System.out.println(s\"Checkpointing takes ${System.currentTimeMillis() - time0} ms\")\n\n  val time1 = System.currentTimeMillis()\n  val joined = cp.join(cp, \"value\").join(cp, \"value\").join(cp, \"value\").join(cp, \"value\")\n  val result = joined.as[Int]\n\n  println(s\"Query planning takes ${System.currentTimeMillis() - time1} ms\")\n  result\n}\n\n// == Iteration 0 ==\n// Checkpointing takes 591 ms\n// Query planning takes 13 ms\n// == Iteration 1 ==\n// Checkpointing takes 1605 ms\n// Query planning takes 16 ms\n// == Iteration 2 ==\n// Checkpointing takes 782 ms\n// Query planning takes 8 ms\n// == Iteration 3 ==\n// Checkpointing takes 729 ms\n// Query planning takes 10 ms\n// == Iteration 4 ==\n// Checkpointing takes 734 ms\n// Query planning takes 9 ms\n// == Iteration 5 ==\n// ...\n// == Iteration 50 ==\n// Checkpointing takes 571 ms\n// Query planning takes 7 ms\n// == Iteration 51 ==\n// Checkpointing takes 548 ms\n// Query planning takes 7 ms\n// == Iteration 52 ==\n// Checkpointing takes 596 ms\n// Query planning takes 8 ms\n// == Iteration 53 ==\n// Checkpointing takes 568 ms\n// Query planning takes 7 ms\n// ...\n```\n\nYou may see that although checkpointing is more heavy weight an operation, it always takes roughly the same amount of time to perform both checkpointing and query planning.\n### Open question\n\n@mengxr mentioned that it would be more convenient if we can make `Dataset.checkpoint()` eager, i.e., always performs a `RDD.count()` after calling `RDD.checkpoint()`. Not quite sure whether this is a universal requirement. Maybe we can add a `eager: Boolean` argument for `Dataset.checkpoint()` to support that.\n## How was this patch tested?\n\nUnit test added in `DatasetSuite`.\n"},"comment":{"url":"https://api.github.com/repos/apache/spark/issues/comments/257391698","html_url":"https://github.com/apache/spark/pull/15651#issuecomment-257391698","issue_url":"https://api.github.com/repos/apache/spark/issues/15651","id":257391698,"user":{"login":"AmplabJenkins","id":4031291,"avatar_url":"https://avatars.githubusercontent.com/u/4031291?v=3","gravatar_id":"","url":"https://api.github.com/users/AmplabJenkins","html_url":"https://github.com/AmplabJenkins","followers_url":"https://api.github.com/users/AmplabJenkins/followers","following_url":"https://api.github.com/users/AmplabJenkins/following{/other_user}","gists_url":"https://api.github.com/users/AmplabJenkins/gists{/gist_id}","starred_url":"https://api.github.com/users/AmplabJenkins/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AmplabJenkins/subscriptions","organizations_url":"https://api.github.com/users/AmplabJenkins/orgs","repos_url":"https://api.github.com/users/AmplabJenkins/repos","events_url":"https://api.github.com/users/AmplabJenkins/events{/privacy}","received_events_url":"https://api.github.com/users/AmplabJenkins/received_events","type":"User","site_admin":false},"created_at":"2016-10-31T19:15:49Z","updated_at":"2016-10-31T19:15:49Z","body":"Test PASSed.\nRefer to this link for build results (access rights to CI server needed): \nhttps://amplab.cs.berkeley.edu/jenkins//job/SparkPullRequestBuilder/67813/\nTest PASSed."}}
{"actor":{"display_login":"AmplabJenkins"}}