{"action":"created","comment":{"url":"https://api.github.com/repos/apache/spark/pulls/comments/30176759","id":30176759,"diff_hunk":"@@ -0,0 +1,418 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements.  See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License.  You may obtain a copy of the License at\n+ *\n+ *    http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+package org.apache.spark.shuffle.unsafe;\n+\n+import java.io.File;\n+import java.io.IOException;\n+import java.util.LinkedList;\n+\n+import scala.Tuple2;\n+\n+import com.google.common.annotations.VisibleForTesting;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import org.apache.spark.SparkConf;\n+import org.apache.spark.TaskContext;\n+import org.apache.spark.executor.ShuffleWriteMetrics;\n+import org.apache.spark.serializer.SerializerInstance;\n+import org.apache.spark.shuffle.ShuffleMemoryManager;\n+import org.apache.spark.storage.*;\n+import org.apache.spark.unsafe.PlatformDependent;\n+import org.apache.spark.unsafe.memory.MemoryBlock;\n+import org.apache.spark.unsafe.memory.TaskMemoryManager;\n+\n+/**\n+ * An external sorter that is specialized for sort-based shuffle.\n+ * <p>\n+ * Incoming records are appended to data pages. When all records have been inserted (or when the\n+ * current thread's shuffle memory limit is reached), the in-memory records are sorted according to\n+ * their partition ids (using a {@link UnsafeShuffleSorter}). The sorted records are then written\n+ * to a single output file (or multiple files, if we've spilled). The format of the output files is\n+ * the same as the format of the final output file written by\n+ * {@link org.apache.spark.shuffle.sort.SortShuffleWriter}: each output partition's records are\n+ * written as a single serialized, compressed stream that can be read with a new decompression and\n+ * deserialization stream.\n+ * <p>\n+ * Unlike {@link org.apache.spark.util.collection.ExternalSorter}, this sorter does not merge its\n+ * spill files. Instead, this merging is performed in {@link UnsafeShuffleWriter}, which uses a\n+ * specialized merge procedure that avoids extra serialization/deserialization.\n+ */\n+final class UnsafeShuffleExternalSorter {\n+\n+  private final Logger logger = LoggerFactory.getLogger(UnsafeShuffleExternalSorter.class);\n+\n+  private static final int PAGE_SIZE = PackedRecordPointer.MAXIMUM_PAGE_SIZE_BYTES;\n+  @VisibleForTesting\n+  static final int DISK_WRITE_BUFFER_SIZE = 1024 * 1024;\n+  @VisibleForTesting\n+  static final int MAX_RECORD_SIZE = PAGE_SIZE - 4;\n+\n+  private final int initialSize;\n+  private final int numPartitions;\n+  private final TaskMemoryManager memoryManager;\n+  private final ShuffleMemoryManager shuffleMemoryManager;\n+  private final BlockManager blockManager;\n+  private final TaskContext taskContext;\n+  private final boolean spillingEnabled;\n+  private final ShuffleWriteMetrics writeMetrics;\n+\n+  /** The buffer size to use when writing spills using DiskBlockObjectWriter */\n+  private final int fileBufferSize;\n+\n+  /**\n+   * Memory pages that hold the records being sorted. The pages in this list are freed when\n+   * spilling, although in principle we could recycle these pages across spills (on the other hand,\n+   * this might not be necessary if we maintained a pool of re-usable pages in the TaskMemoryManager\n+   * itself).\n+   */\n+  private final LinkedList<MemoryBlock> allocatedPages = new LinkedList<MemoryBlock>();\n+\n+  private final LinkedList<SpillInfo> spills = new LinkedList<SpillInfo>();\n+\n+  // All three of these variables are reset after spilling:\n+  private UnsafeShuffleSorter sorter;\n+  private MemoryBlock currentPage = null;\n+  private long currentPagePosition = -1;\n+  private long freeSpaceInCurrentPage = 0;\n+\n+  public UnsafeShuffleExternalSorter(\n+      TaskMemoryManager memoryManager,\n+      ShuffleMemoryManager shuffleMemoryManager,\n+      BlockManager blockManager,\n+      TaskContext taskContext,\n+      int initialSize,\n+      int numPartitions,\n+      SparkConf conf,\n+      ShuffleWriteMetrics writeMetrics) throws IOException {\n+    this.memoryManager = memoryManager;\n+    this.shuffleMemoryManager = shuffleMemoryManager;\n+    this.blockManager = blockManager;\n+    this.taskContext = taskContext;\n+    this.initialSize = initialSize;\n+    this.numPartitions = numPartitions;\n+    this.spillingEnabled = conf.getBoolean(\"spark.shuffle.spill\", true);\n+    // Use getSizeAsKb (not bytes) to maintain backwards compatibility for units\n+    this.fileBufferSize = (int) conf.getSizeAsKb(\"spark.shuffle.file.buffer\", \"32k\") * 1024;\n+    this.writeMetrics = writeMetrics;\n+    openSorter();\n+  }\n+\n+  /**\n+   * Allocates a new sorter. Called when opening the spill writer for the first time and after\n+   * each spill.\n+   */\n+  private void openSorter() throws IOException {\n+    // TODO: move this sizing calculation logic into a static method of sorter:\n+    final long memoryRequested = initialSize * 8L;\n+    if (spillingEnabled) {\n+      final long memoryAcquired = shuffleMemoryManager.tryToAcquire(memoryRequested);\n+      if (memoryAcquired != memoryRequested) {\n+        shuffleMemoryManager.release(memoryAcquired);\n+        throw new IOException(\"Could not acquire memory!\");\n+      }\n+    }\n+\n+    this.sorter = new UnsafeShuffleSorter(initialSize);\n+  }\n+\n+  /**\n+   * Sorts the in-memory records and writes the sorted records to a spill file.\n+   * This method does not free the sort data structures.\n+   *\n+   * @param isSpill if true, this indicates that we're writing a spill and that bytes written should\n+   *                be counted towards shuffle spill metrics rather than shuffle write metrics.\n+   */\n+  private void writeSpillFile(boolean isSpill) throws IOException {","path":"core/src/main/java/org/apache/spark/shuffle/unsafe/UnsafeShuffleExternalSorter.java","position":141,"original_position":141,"commit_id":"4f0b770829698c319eee24ecdc959ab3fd1cdb29","original_commit_id":"4f0b770829698c319eee24ecdc959ab3fd1cdb29","user":{"login":"JoshRosen","id":50748,"avatar_url":"https://avatars.githubusercontent.com/u/50748?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshRosen","html_url":"https://github.com/JoshRosen","followers_url":"https://api.github.com/users/JoshRosen/followers","following_url":"https://api.github.com/users/JoshRosen/following{/other_user}","gists_url":"https://api.github.com/users/JoshRosen/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshRosen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshRosen/subscriptions","organizations_url":"https://api.github.com/users/JoshRosen/orgs","repos_url":"https://api.github.com/users/JoshRosen/repos","events_url":"https://api.github.com/users/JoshRosen/events{/privacy}","received_events_url":"https://api.github.com/users/JoshRosen/received_events","type":"User","site_admin":false},"body":"I went with `writeSortedFile` to emphasize that this writes a new file.","created_at":"2015-05-12T20:31:22Z","updated_at":"2015-05-12T20:31:22Z","html_url":"https://github.com/apache/spark/pull/5868#discussion_r30176759","pull_request_url":"https://api.github.com/repos/apache/spark/pulls/5868","_links":{"self":{"href":"https://api.github.com/repos/apache/spark/pulls/comments/30176759"},"html":{"href":"https://github.com/apache/spark/pull/5868#discussion_r30176759"},"pull_request":{"href":"https://api.github.com/repos/apache/spark/pulls/5868"}}},"pull_request":{"url":"https://api.github.com/repos/apache/spark/pulls/5868","id":34592881,"html_url":"https://github.com/apache/spark/pull/5868","diff_url":"https://github.com/apache/spark/pull/5868.diff","patch_url":"https://github.com/apache/spark/pull/5868.patch","issue_url":"https://api.github.com/repos/apache/spark/issues/5868","number":5868,"state":"open","locked":false,"title":"[WIP][SPARK-7081]Faster sort-based shuffle path using binary processing cache-aware sort","user":{"login":"JoshRosen","id":50748,"avatar_url":"https://avatars.githubusercontent.com/u/50748?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshRosen","html_url":"https://github.com/JoshRosen","followers_url":"https://api.github.com/users/JoshRosen/followers","following_url":"https://api.github.com/users/JoshRosen/following{/other_user}","gists_url":"https://api.github.com/users/JoshRosen/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshRosen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshRosen/subscriptions","organizations_url":"https://api.github.com/users/JoshRosen/orgs","repos_url":"https://api.github.com/users/JoshRosen/repos","events_url":"https://api.github.com/users/JoshRosen/events{/privacy}","received_events_url":"https://api.github.com/users/JoshRosen/received_events","type":"User","site_admin":false},"body":"This patch introduces a new shuffle manager that enhances the existing sort-based shuffle with a new cache-friendly sort algorithm that operates directly on binary data. The goals of this patch are to lower memory usage and Java object overheads during shuffle and to speed up sorting. It also lays groundwork for follow-up patches that will enable end-to-end processing of serialized records.\r\n\r\nThe new shuffle manager, `UnsafeShuffleManager`, can be enabled by setting `spark.shuffle.manager=unsafe` in SparkConf. The new shuffle write path in this manager will only be used for shuffles in which there is no specified key ordering, no aggregation, and for which a supported serializer is used. In cases where these conditions are not met, `UnsafeShuffleManager` will automatically delegate to the old `SortShuffleManager`.\r\n\r\nThis patch uses a specialized external sorting implementation that operates on serialized records. By using a specialized sorter, we can benefit from several performance optimizations that would be difficult to implement in a more general-purpose sorter:\r\n\r\n- We may not need a full 32-bits to record the partition id; since shuffles are unlikely to have more than, say, 10 million partitions, we can use fewer bits to encode the partition id, allowing us to pack it into a single word that uses the remaining bits to store the record pointer (exploiting the fact that we can choose a reasonable upper bound on our addressable memory (say 1 terabyte) and can save address bits due to word-alignment). This should give us excellent cache-locality benefits, since the sort array will only require 8 bytes of space per record.\r\n- Because sort-shuffle's sort keys are partition ids, we can expect to encounter long runs of records with the same sort key in the sorted file; this may not be the case in a more general-purpose sort, where there might be cases in which all of the sort keys are distinct. As a result, this allows external sort to use a specialized merge procedure that operates on runs of records with the same key rather than individual records.\r\n- Ignoring the spilling/merging case for a moment, the sort shuffle writer only needs to know individual records' lengths during the in-memory sort: we need to know a record's length in order to copy it to the sorted output file, but once a partition's records are adjacent in the output file we no longer need to know their individual lengths. This is a consequence of the fact that the sorted data will be consumed by a serializer that knows how to identify record boundaries, building on our assumption that the serializer supports reordering of serialized records in its serialization stream.\r\n- If we go further and assume that our IO compression codec supports concatenation of compressed data (which Snappy seems to support), then we can implement an IO-efficient merging procedure for external sort. If we store an index file for each spill file, identifying the offsets of each partition within the sorted file, then the merge sort procedure can simply traverse these indices and concatenate the partitions' serialized data without interpreting it. This would let us use methods like transferTo to implement copying without requiring data to be buffered in the JVM, sidestepping the complexity of managing IO buffers during the merge.\r\n\r\nThe shuffle read path is unchanged.\r\n\r\nThis patch is similar to [SPARK-4550](http://issues.apache.org/jira/browse/SPARK-4550) / #4450 but uses a slightly different implementation. The `unsafe`-based implementation featured in this patch lays the groundwork for followup patches that will enable sorting to operate on serialized data pages that will be prepared by Spark SQL's new `unsafe` operators (such as the new aggregation operator introduced in #5725).\r\n\r\n### Supported serializers\r\n\r\nBecause this sort operates on serialized records, it places certain requirements on the record serialization format. Specifically, it requires a _relocatability_ property in which records in a serialized stream of records can be re-ordered by reordering their serialized representations.  See #5924 for a patch that extends the Serializer API to let us determine whether this property holds.\r\n\r\n### Future work\r\n\r\nThere are several tasks that build upon this patch, which will be left to future work:\r\n\r\n- [SPARK-7271](https://issues.apache.org/jira/browse/SPARK-7271) Redesign / extend the shuffle interfaces to accept binary data as input. The goal here is to let us bypass serialization steps in cases where the sort input is produced by an operator that operates directly on binary data.\r\n- Extension / redesign of the `Serializer` API. We can add new methods which allow serializers to determine the size requirements for serializing objects and for serializing objects directly to a specified memory address (similar to how `UnsafeRowConverter` works in Spark SQL).\r\n  \r\n**TODOs for this patch**:\r\n\r\n- More test cases:\r\n  - [ ] Property-based / fuzz tests for sorter.\r\n  - [ ] Test that checks that counters like `numRecordsWritten`, etc., are properly updated.\r\n  - [x] After #5948 is merged, update SQL's Exchange operator to also avoid copying when this new shuffle manager is used.\r\n - [x] Audit the potential exception sources in the shuffle writer and ensure that temporary spill files are eventually cleaned up.\r\n\r\n<!-- Reviewable:start -->\r\n[<img src=\"https://reviewable.io/review_button.png\" height=40 alt=\"Review on Reviewable\"/>](https://reviewable.io/reviews/apache/spark/5868)\r\n<!-- Reviewable:end -->\r\n","created_at":"2015-05-02T22:26:37Z","updated_at":"2015-05-12T20:31:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1cb2818b81eada24987507c220f073e4e336c460","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/apache/spark/pulls/5868/commits","review_comments_url":"https://api.github.com/repos/apache/spark/pulls/5868/comments","review_comment_url":"https://api.github.com/repos/apache/spark/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/spark/issues/5868/comments","statuses_url":"https://api.github.com/repos/apache/spark/statuses/4f0b770829698c319eee24ecdc959ab3fd1cdb29","head":{"label":"JoshRosen:unsafe-sort","ref":"unsafe-sort","sha":"4f0b770829698c319eee24ecdc959ab3fd1cdb29","user":{"login":"JoshRosen","id":50748,"avatar_url":"https://avatars.githubusercontent.com/u/50748?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshRosen","html_url":"https://github.com/JoshRosen","followers_url":"https://api.github.com/users/JoshRosen/followers","following_url":"https://api.github.com/users/JoshRosen/following{/other_user}","gists_url":"https://api.github.com/users/JoshRosen/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshRosen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshRosen/subscriptions","organizations_url":"https://api.github.com/users/JoshRosen/orgs","repos_url":"https://api.github.com/users/JoshRosen/repos","events_url":"https://api.github.com/users/JoshRosen/events{/privacy}","received_events_url":"https://api.github.com/users/JoshRosen/received_events","type":"User","site_admin":false},"repo":{"id":22238710,"name":"spark","full_name":"JoshRosen/spark","owner":{"login":"JoshRosen","id":50748,"avatar_url":"https://avatars.githubusercontent.com/u/50748?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshRosen","html_url":"https://github.com/JoshRosen","followers_url":"https://api.github.com/users/JoshRosen/followers","following_url":"https://api.github.com/users/JoshRosen/following{/other_user}","gists_url":"https://api.github.com/users/JoshRosen/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshRosen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshRosen/subscriptions","organizations_url":"https://api.github.com/users/JoshRosen/orgs","repos_url":"https://api.github.com/users/JoshRosen/repos","events_url":"https://api.github.com/users/JoshRosen/events{/privacy}","received_events_url":"https://api.github.com/users/JoshRosen/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/JoshRosen/spark","description":"Mirror of Apache Spark","fork":true,"url":"https://api.github.com/repos/JoshRosen/spark","forks_url":"https://api.github.com/repos/JoshRosen/spark/forks","keys_url":"https://api.github.com/repos/JoshRosen/spark/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JoshRosen/spark/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JoshRosen/spark/teams","hooks_url":"https://api.github.com/repos/JoshRosen/spark/hooks","issue_events_url":"https://api.github.com/repos/JoshRosen/spark/issues/events{/number}","events_url":"https://api.github.com/repos/JoshRosen/spark/events","assignees_url":"https://api.github.com/repos/JoshRosen/spark/assignees{/user}","branches_url":"https://api.github.com/repos/JoshRosen/spark/branches{/branch}","tags_url":"https://api.github.com/repos/JoshRosen/spark/tags","blobs_url":"https://api.github.com/repos/JoshRosen/spark/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JoshRosen/spark/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JoshRosen/spark/git/refs{/sha}","trees_url":"https://api.github.com/repos/JoshRosen/spark/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JoshRosen/spark/statuses/{sha}","languages_url":"https://api.github.com/repos/JoshRosen/spark/languages","stargazers_url":"https://api.github.com/repos/JoshRosen/spark/stargazers","contributors_url":"https://api.github.com/repos/JoshRosen/spark/contributors","subscribers_url":"https://api.github.com/repos/JoshRosen/spark/subscribers","subscription_url":"https://api.github.com/repos/JoshRosen/spark/subscription","commits_url":"https://api.github.com/repos/JoshRosen/spark/commits{/sha}","git_commits_url":"https://api.github.com/repos/JoshRosen/spark/git/commits{/sha}","comments_url":"https://api.github.com/repos/JoshRosen/spark/comments{/number}","issue_comment_url":"https://api.github.com/repos/JoshRosen/spark/issues/comments{/number}","contents_url":"https://api.github.com/repos/JoshRosen/spark/contents/{+path}","compare_url":"https://api.github.com/repos/JoshRosen/spark/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JoshRosen/spark/merges","archive_url":"https://api.github.com/repos/JoshRosen/spark/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JoshRosen/spark/downloads","issues_url":"https://api.github.com/repos/JoshRosen/spark/issues{/number}","pulls_url":"https://api.github.com/repos/JoshRosen/spark/pulls{/number}","milestones_url":"https://api.github.com/repos/JoshRosen/spark/milestones{/number}","notifications_url":"https://api.github.com/repos/JoshRosen/spark/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JoshRosen/spark/labels{/name}","releases_url":"https://api.github.com/repos/JoshRosen/spark/releases{/id}","created_at":"2014-07-25T00:47:52Z","updated_at":"2015-05-01T22:45:32Z","pushed_at":"2015-05-12T03:07:35Z","git_url":"git://github.com/JoshRosen/spark.git","ssh_url":"git@github.com:JoshRosen/spark.git","clone_url":"https://github.com/JoshRosen/spark.git","svn_url":"https://github.com/JoshRosen/spark","homepage":null,"size":104778,"stargazers_count":1,"watchers_count":1,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":0,"forks":2,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"apache:master","ref":"master","sha":"7d0f17208cda641651dcbd1bc0da639cd74307e7","user":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":17165658,"name":"spark","full_name":"apache/spark","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/spark","description":"Mirror of Apache Spark","fork":false,"url":"https://api.github.com/repos/apache/spark","forks_url":"https://api.github.com/repos/apache/spark/forks","keys_url":"https://api.github.com/repos/apache/spark/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/spark/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/spark/teams","hooks_url":"https://api.github.com/repos/apache/spark/hooks","issue_events_url":"https://api.github.com/repos/apache/spark/issues/events{/number}","events_url":"https://api.github.com/repos/apache/spark/events","assignees_url":"https://api.github.com/repos/apache/spark/assignees{/user}","branches_url":"https://api.github.com/repos/apache/spark/branches{/branch}","tags_url":"https://api.github.com/repos/apache/spark/tags","blobs_url":"https://api.github.com/repos/apache/spark/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/spark/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/spark/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/spark/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/spark/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/spark/languages","stargazers_url":"https://api.github.com/repos/apache/spark/stargazers","contributors_url":"https://api.github.com/repos/apache/spark/contributors","subscribers_url":"https://api.github.com/repos/apache/spark/subscribers","subscription_url":"https://api.github.com/repos/apache/spark/subscription","commits_url":"https://api.github.com/repos/apache/spark/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/spark/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/spark/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/spark/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/spark/contents/{+path}","compare_url":"https://api.github.com/repos/apache/spark/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/spark/merges","archive_url":"https://api.github.com/repos/apache/spark/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/spark/downloads","issues_url":"https://api.github.com/repos/apache/spark/issues{/number}","pulls_url":"https://api.github.com/repos/apache/spark/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/spark/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/spark/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/spark/labels{/name}","releases_url":"https://api.github.com/repos/apache/spark/releases{/id}","created_at":"2014-02-25T08:00:08Z","updated_at":"2015-05-12T20:20:19Z","pushed_at":"2015-05-12T20:26:22Z","git_url":"git://github.com/apache/spark.git","ssh_url":"git@github.com:apache/spark.git","clone_url":"https://github.com/apache/spark.git","svn_url":"https://github.com/apache/spark","homepage":null,"size":1655193,"stargazers_count":3940,"watchers_count":3940,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3460,"mirror_url":"git://git.apache.org/spark.git","open_issues_count":308,"forks":3460,"open_issues":308,"watchers":3940,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/spark/pulls/5868"},"html":{"href":"https://github.com/apache/spark/pull/5868"},"issue":{"href":"https://api.github.com/repos/apache/spark/issues/5868"},"comments":{"href":"https://api.github.com/repos/apache/spark/issues/5868/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/spark/pulls/5868/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/spark/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/spark/pulls/5868/commits"},"statuses":{"href":"https://api.github.com/repos/apache/spark/statuses/4f0b770829698c319eee24ecdc959ab3fd1cdb29"}}}}