{"action":"created","comment":{"url":"https://api.github.com/repos/druid-io/druid/pulls/comments/86062202","pull_request_review_id":6739396,"id":86062202,"diff_hunk":"@@ -0,0 +1,313 @@\n+/*\n+ * Licensed to Metamarkets Group Inc. (Metamarkets) under one\n+ * or more contributor license agreements. See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership. Metamarkets licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * \"License\"); you may not use this file except in compliance\n+ * with 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,\n+ * software distributed under the License is distributed on an\n+ * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied. See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ */\n+\n+package io.druid.segment.realtime.firehose;\n+\n+import com.fasterxml.jackson.annotation.JsonCreator;\n+import com.fasterxml.jackson.annotation.JsonProperty;\n+import com.fasterxml.jackson.core.JsonFactory;\n+import com.fasterxml.jackson.core.JsonGenerator;\n+import com.fasterxml.jackson.core.JsonParser;\n+import com.fasterxml.jackson.dataformat.smile.SmileFactory;\n+import com.fasterxml.jackson.dataformat.smile.SmileGenerator;\n+import com.google.common.base.Preconditions;\n+import com.google.common.base.Throwables;\n+import com.google.common.collect.Iterators;\n+import com.google.common.collect.Lists;\n+import com.google.common.collect.Ordering;\n+import com.google.common.collect.Sets;\n+import com.google.common.io.CountingOutputStream;\n+import com.google.common.io.Files;\n+import com.metamx.emitter.EmittingLogger;\n+import io.druid.data.input.Firehose;\n+import io.druid.data.input.FirehoseFactory;\n+import io.druid.data.input.InputRow;\n+import io.druid.data.input.Row;\n+import io.druid.data.input.Rows;\n+import io.druid.data.input.impl.InputRowParser;\n+import io.druid.jackson.DefaultObjectMapper;\n+import io.druid.java.util.common.guava.CloseQuietly;\n+import io.druid.java.util.common.parsers.ParseException;\n+import io.druid.utils.Runnables;\n+\n+import java.io.Closeable;\n+import java.io.File;\n+import java.io.FileOutputStream;\n+import java.io.IOException;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Set;\n+\n+/**\n+ * Creates a wrapper firehose that writes from another firehose to disk and then serves nextRow() from disk. Useful for\n+ * tasks that require multiple passes through the data to prevent multiple remote fetches. Also has support for\n+ * retrying fetches if the underlying firehose throws an exception while the local cache is being generated.\n+ */\n+public class ReplayableFirehoseFactory implements FirehoseFactory<InputRowParser>, Closeable\n+{\n+  private static final EmittingLogger log = new EmittingLogger(ReplayableFirehoseFactory.class);\n+  private static final boolean DELETE_TEMP_FILES = true;\n+  private static final boolean DEFAULT_REPORT_PARSE_EXCEPTIONS = false;\n+  private static final int DEFAULT_MAX_TEMP_FILE_SIZE = 250000000;\n+  private static final int DEFAULT_READ_FIREHOSE_RETRIES = 3;\n+\n+  private final FirehoseFactory delegateFactory;\n+  private final boolean reportParseExceptions;\n+  private final int maxTempFileSize;\n+  private final int readFirehoseRetries;\n+\n+  private ReplayableFirehose firehose;\n+\n+  @JsonCreator\n+  public ReplayableFirehoseFactory(\n+      @JsonProperty(\"delegate\") FirehoseFactory delegateFactory,\n+      @JsonProperty(\"reportParseExceptions\") Boolean reportParseExceptions,\n+      @JsonProperty(\"maxTempFileSize\") Integer maxTempFileSize,\n+      @JsonProperty(\"readFirehoseRetries\") Integer readFirehoseRetries\n+  )\n+  {\n+    Preconditions.checkNotNull(delegateFactory, \"delegate cannot be null\");\n+    Preconditions.checkArgument(\n+        !(delegateFactory instanceof ReplayableFirehoseFactory),\n+        \"Refusing to wrap another ReplayableFirehoseFactory\"\n+    );\n+\n+    this.delegateFactory = delegateFactory;\n+    this.reportParseExceptions = reportParseExceptions == null\n+                                 ? DEFAULT_REPORT_PARSE_EXCEPTIONS\n+                                 : reportParseExceptions;\n+    this.maxTempFileSize = maxTempFileSize == null ? DEFAULT_MAX_TEMP_FILE_SIZE : maxTempFileSize;\n+    this.readFirehoseRetries = readFirehoseRetries == null ? DEFAULT_READ_FIREHOSE_RETRIES : readFirehoseRetries;\n+\n+    log.info(this.toString());\n+  }\n+\n+  @Override\n+  public Firehose connect(InputRowParser parser) throws IOException\n+  {\n+    if (firehose == null) {\n+      firehose = new ReplayableFirehose(parser);\n+    } else {\n+      log.info(\"Rewinding and returning existing firehose\");\n+      firehose.rewind();\n+    }\n+\n+    return firehose;\n+  }\n+\n+  @Override\n+  public void close() throws IOException\n+  {\n+    if (firehose != null) {\n+      firehose.possiblyDeleteTempFiles();\n+      firehose.close();\n+    }\n+\n+    firehose = null;\n+  }\n+\n+  public class ReplayableFirehose implements Firehose\n+  {\n+    private final JsonFactory factory = new DefaultObjectMapper(\n+        new SmileFactory().enable(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES)\n+    ).getFactory();\n+\n+    private final List<File> files = Lists.newArrayList();\n+    private final List<String> dimensions;\n+\n+    private int fileIndex = 0;\n+    private JsonParser jsonParser;\n+    private Iterator<Row> it;\n+\n+    public ReplayableFirehose(InputRowParser parser) throws IOException\n+    {\n+      long counter = 0, totalBytes = 0, unparseable = 0, retryCount = 0;\n+      Set<String> dimensionScratch = Sets.newHashSet();\n+\n+      File tmpDir = Files.createTempDir();\n+      if (DELETE_TEMP_FILES) {\n+        tmpDir.deleteOnExit();\n+      }\n+\n+      long ingestStartMillis = System.currentTimeMillis();\n+      do {\n+        possiblyDeleteTempFiles();\n+        try (Firehose delegateFirehose = delegateFactory.connect(parser)) {\n+          while (delegateFirehose.hasMore()) {\n+            File tmpFile = File.createTempFile(\"replayable-\", null, tmpDir);\n+            if (DELETE_TEMP_FILES) {\n+              tmpFile.deleteOnExit();\n+            }\n+\n+            files.add(tmpFile);\n+            log.debug(\"Created file [%s]\", tmpFile.getAbsolutePath());\n+\n+            try (CountingOutputStream cos = new CountingOutputStream(new FileOutputStream(tmpFile));\n+                 JsonGenerator generator = factory.createGenerator(cos)) {\n+\n+              while (delegateFirehose.hasMore() && cos.getCount() < getMaxTempFileSize()) {\n+                try {\n+                  InputRow row = delegateFirehose.nextRow();\n+                  generator.writeObject(row);\n+                  dimensionScratch.addAll(row.getDimensions());\n+                  counter++;\n+                }\n+                catch (ParseException e) {\n+                  if (reportParseExceptions) {\n+                    throw e;\n+                  }\n+                  unparseable++;\n+                }\n+              }\n+\n+              totalBytes += cos.getCount();\n+            }\n+          }\n+          break; // we succeeded so break out of retry loop\n+        }\n+        catch (Exception e) {\n+          if (++retryCount <= readFirehoseRetries && !(e instanceof ParseException)) {\n+            log.error(e, \"Delegate firehose threw an exception, retrying (%d of %d)\", retryCount, readFirehoseRetries);\n+          } else {\n+            log.error(e, \"Delegate firehose threw an exception, retries exhausted, aborting\");\n+            Throwables.propagate(e);\n+          }\n+        }\n+      } while (true);\n+\n+      log.info(\n+          \"Finished reading from firehose in [%,dms], [%,d] events parsed, [%,d] bytes written, [%,d] events unparseable\",\n+          System.currentTimeMillis() - ingestStartMillis,","path":"server/src/main/java/io/druid/segment/realtime/firehose/ReplayableFirehoseFactory.java","position":196,"original_position":196,"commit_id":"516b929c63261fb392ee31eed6421c1c6213b580","original_commit_id":"8cbdfac240f2dd27e3b480951ddd8fe8984198c6","user":{"login":"dclim","id":11050882,"avatar_url":"https://avatars.githubusercontent.com/u/11050882?v=3","gravatar_id":"","url":"https://api.github.com/users/dclim","html_url":"https://github.com/dclim","followers_url":"https://api.github.com/users/dclim/followers","following_url":"https://api.github.com/users/dclim/following{/other_user}","gists_url":"https://api.github.com/users/dclim/gists{/gist_id}","starred_url":"https://api.github.com/users/dclim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dclim/subscriptions","organizations_url":"https://api.github.com/users/dclim/orgs","repos_url":"https://api.github.com/users/dclim/repos","events_url":"https://api.github.com/users/dclim/events{/privacy}","received_events_url":"https://api.github.com/users/dclim/received_events","type":"User","site_admin":false},"body":"Might be interesting, but probably not necessary in this PR","created_at":"2016-11-02T02:05:21Z","updated_at":"2016-11-02T02:05:21Z","html_url":"https://github.com/druid-io/druid/pull/3611#discussion_r86062202","pull_request_url":"https://api.github.com/repos/druid-io/druid/pulls/3611","_links":{"self":{"href":"https://api.github.com/repos/druid-io/druid/pulls/comments/86062202"},"html":{"href":"https://github.com/druid-io/druid/pull/3611#discussion_r86062202"},"pull_request":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3611"}}},"pull_request":{"url":"https://api.github.com/repos/druid-io/druid/pulls/3611","id":90916036,"html_url":"https://github.com/druid-io/druid/pull/3611","diff_url":"https://github.com/druid-io/druid/pull/3611.diff","patch_url":"https://github.com/druid-io/druid/pull/3611.patch","issue_url":"https://api.github.com/repos/druid-io/druid/issues/3611","number":3611,"state":"open","locked":false,"title":"IndexTask improvements","user":{"login":"dclim","id":11050882,"avatar_url":"https://avatars.githubusercontent.com/u/11050882?v=3","gravatar_id":"","url":"https://api.github.com/users/dclim","html_url":"https://github.com/dclim","followers_url":"https://api.github.com/users/dclim/followers","following_url":"https://api.github.com/users/dclim/following{/other_user}","gists_url":"https://api.github.com/users/dclim/gists{/gist_id}","starred_url":"https://api.github.com/users/dclim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dclim/subscriptions","organizations_url":"https://api.github.com/users/dclim/orgs","repos_url":"https://api.github.com/users/dclim/repos","events_url":"https://api.github.com/users/dclim/events{/privacy}","received_events_url":"https://api.github.com/users/dclim/received_events","type":"User","site_admin":false},"body":"This PR introduces a number of improvements to the IndexTask (non-Hadoop batch indexer). The primary motivation of improving IndexTask is with the view that it can be used with something like #1998 for automatic merging and splitting of segments for users without Hadoop. This would replace the functionality of the MergeTask which is limited by the fact that it can only operate on NoneShardSpec-type segments. Secondary motivation is to improve the performance/stability of IndexTask for users who don't run a Hadoop cluster.\n\nMain changes:\n- Overall performance: Old implementation iterated through entire data set once to determine intervals, once to determine shards for that interval, and once to generate index for each shard; hence if your segmentGranularity resulted in 12 time intervals and each interval had 3 shards, the _entire dataset_ was read from 1 + 12 + 36 times. This has been reduced to either 1 time if `numShards` and `intervals` is provided, otherwise 2 times. Performance gains will vary based on number of intervals/shards, but in all cases are better than the previous implementation and typically significantly better. Performance compared to Hadoop index task running locally is typically equivalent or better. The Appenderator implementation is now being used for index generation.\n- ReplayableFirehose: Introduced a new firehose wrapper type that entirely consumes the wrapped firehose into temporary files and then serves read requests from the cached data. This has a few benefits:\n  - In the case that we need to read the data more than once, it prevents us from fetching from remote resources multiple times (saves time, potentially money)\n  - In the case that we need to read the data more than once, it gives us deterministic results where the wrapped firehose may not\n  - In the case that the underlying firehose is unreliable, it allows us to retriably grab the entire dataset first and only begin serving data after we have a full copy (this addresses a number of issues recently reported with the S3 firehose failing in the middle of reading)\n  - I did not document the ReplayableFirehose, even though it could theoretically wrap any other firehose because I'm not sure how much use it has outside of the index task. IndexTask wraps any provided firehose in a ReplayableFirehose, although this behavior can be disabled by configuration.\n- segmentGranularity `intervals` no longer needs to be explicitly provided but can be determined, which could make things easier for some use cases\n- Append support: Users can set the `appendToExisting` flag which will generate segments that are additional partitions added to the latest version of the existing segments, effectively 'appending data' into the datasource. The set of segments to be appended to must be an extendable-type (i.e. they can't be NoneShardSpecs).\n- Reworked IndexTuningConfig to have similarly-named parameters to other index task types: If people agree with this change, it is backwards-incompatible and would require users to update their ingestion specs.\n\nThis PR depends on #3483 being merged and I don't expect tests to pass until that happens.\n","created_at":"2016-10-26T00:21:27Z","updated_at":"2016-11-02T02:05:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e94e0414df09a40f0bedfbb8c6877fa0d25eda35","assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/druid-io/druid/milestones/21","html_url":"https://github.com/druid-io/druid/milestones/0.9.3","labels_url":"https://api.github.com/repos/druid-io/druid/milestones/21/labels","id":1831000,"number":21,"title":"0.9.3","description":"","creator":{"login":"fjy","id":428325,"avatar_url":"https://avatars.githubusercontent.com/u/428325?v=3","gravatar_id":"","url":"https://api.github.com/users/fjy","html_url":"https://github.com/fjy","followers_url":"https://api.github.com/users/fjy/followers","following_url":"https://api.github.com/users/fjy/following{/other_user}","gists_url":"https://api.github.com/users/fjy/gists{/gist_id}","starred_url":"https://api.github.com/users/fjy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fjy/subscriptions","organizations_url":"https://api.github.com/users/fjy/orgs","repos_url":"https://api.github.com/users/fjy/repos","events_url":"https://api.github.com/users/fjy/events{/privacy}","received_events_url":"https://api.github.com/users/fjy/received_events","type":"User","site_admin":false},"open_issues":55,"closed_issues":40,"state":"open","created_at":"2016-06-15T22:35:58Z","updated_at":"2016-11-02T00:44:06Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/druid-io/druid/pulls/3611/commits","review_comments_url":"https://api.github.com/repos/druid-io/druid/pulls/3611/comments","review_comment_url":"https://api.github.com/repos/druid-io/druid/pulls/comments{/number}","comments_url":"https://api.github.com/repos/druid-io/druid/issues/3611/comments","statuses_url":"https://api.github.com/repos/druid-io/druid/statuses/516b929c63261fb392ee31eed6421c1c6213b580","head":{"label":"dclim:sans-hadoops","ref":"sans-hadoops","sha":"516b929c63261fb392ee31eed6421c1c6213b580","user":{"login":"dclim","id":11050882,"avatar_url":"https://avatars.githubusercontent.com/u/11050882?v=3","gravatar_id":"","url":"https://api.github.com/users/dclim","html_url":"https://github.com/dclim","followers_url":"https://api.github.com/users/dclim/followers","following_url":"https://api.github.com/users/dclim/following{/other_user}","gists_url":"https://api.github.com/users/dclim/gists{/gist_id}","starred_url":"https://api.github.com/users/dclim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dclim/subscriptions","organizations_url":"https://api.github.com/users/dclim/orgs","repos_url":"https://api.github.com/users/dclim/repos","events_url":"https://api.github.com/users/dclim/events{/privacy}","received_events_url":"https://api.github.com/users/dclim/received_events","type":"User","site_admin":false},"repo":{"id":42546365,"name":"druid","full_name":"dclim/druid","owner":{"login":"dclim","id":11050882,"avatar_url":"https://avatars.githubusercontent.com/u/11050882?v=3","gravatar_id":"","url":"https://api.github.com/users/dclim","html_url":"https://github.com/dclim","followers_url":"https://api.github.com/users/dclim/followers","following_url":"https://api.github.com/users/dclim/following{/other_user}","gists_url":"https://api.github.com/users/dclim/gists{/gist_id}","starred_url":"https://api.github.com/users/dclim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dclim/subscriptions","organizations_url":"https://api.github.com/users/dclim/orgs","repos_url":"https://api.github.com/users/dclim/repos","events_url":"https://api.github.com/users/dclim/events{/privacy}","received_events_url":"https://api.github.com/users/dclim/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dclim/druid","description":"Column oriented distributed data store ideal for powering interactive applications","fork":true,"url":"https://api.github.com/repos/dclim/druid","forks_url":"https://api.github.com/repos/dclim/druid/forks","keys_url":"https://api.github.com/repos/dclim/druid/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dclim/druid/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dclim/druid/teams","hooks_url":"https://api.github.com/repos/dclim/druid/hooks","issue_events_url":"https://api.github.com/repos/dclim/druid/issues/events{/number}","events_url":"https://api.github.com/repos/dclim/druid/events","assignees_url":"https://api.github.com/repos/dclim/druid/assignees{/user}","branches_url":"https://api.github.com/repos/dclim/druid/branches{/branch}","tags_url":"https://api.github.com/repos/dclim/druid/tags","blobs_url":"https://api.github.com/repos/dclim/druid/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dclim/druid/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dclim/druid/git/refs{/sha}","trees_url":"https://api.github.com/repos/dclim/druid/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dclim/druid/statuses/{sha}","languages_url":"https://api.github.com/repos/dclim/druid/languages","stargazers_url":"https://api.github.com/repos/dclim/druid/stargazers","contributors_url":"https://api.github.com/repos/dclim/druid/contributors","subscribers_url":"https://api.github.com/repos/dclim/druid/subscribers","subscription_url":"https://api.github.com/repos/dclim/druid/subscription","commits_url":"https://api.github.com/repos/dclim/druid/commits{/sha}","git_commits_url":"https://api.github.com/repos/dclim/druid/git/commits{/sha}","comments_url":"https://api.github.com/repos/dclim/druid/comments{/number}","issue_comment_url":"https://api.github.com/repos/dclim/druid/issues/comments{/number}","contents_url":"https://api.github.com/repos/dclim/druid/contents/{+path}","compare_url":"https://api.github.com/repos/dclim/druid/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dclim/druid/merges","archive_url":"https://api.github.com/repos/dclim/druid/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dclim/druid/downloads","issues_url":"https://api.github.com/repos/dclim/druid/issues{/number}","pulls_url":"https://api.github.com/repos/dclim/druid/pulls{/number}","milestones_url":"https://api.github.com/repos/dclim/druid/milestones{/number}","notifications_url":"https://api.github.com/repos/dclim/druid/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dclim/druid/labels{/name}","releases_url":"https://api.github.com/repos/dclim/druid/releases{/id}","deployments_url":"https://api.github.com/repos/dclim/druid/deployments","created_at":"2015-09-15T21:02:07Z","updated_at":"2016-02-01T17:26:20Z","pushed_at":"2016-11-01T01:23:47Z","git_url":"git://github.com/dclim/druid.git","ssh_url":"git@github.com:dclim/druid.git","clone_url":"https://github.com/dclim/druid.git","svn_url":"https://github.com/dclim/druid","homepage":"http://druid.io","size":110539,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"druid-io:master","ref":"master","sha":"89d9c61894f122b4973ad5d8d325e114b0a7ebf4","user":{"login":"druid-io","id":4203474,"avatar_url":"https://avatars.githubusercontent.com/u/4203474?v=3","gravatar_id":"","url":"https://api.github.com/users/druid-io","html_url":"https://github.com/druid-io","followers_url":"https://api.github.com/users/druid-io/followers","following_url":"https://api.github.com/users/druid-io/following{/other_user}","gists_url":"https://api.github.com/users/druid-io/gists{/gist_id}","starred_url":"https://api.github.com/users/druid-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/druid-io/subscriptions","organizations_url":"https://api.github.com/users/druid-io/orgs","repos_url":"https://api.github.com/users/druid-io/repos","events_url":"https://api.github.com/users/druid-io/events{/privacy}","received_events_url":"https://api.github.com/users/druid-io/received_events","type":"Organization","site_admin":false},"repo":{"id":6358188,"name":"druid","full_name":"druid-io/druid","owner":{"login":"druid-io","id":4203474,"avatar_url":"https://avatars.githubusercontent.com/u/4203474?v=3","gravatar_id":"","url":"https://api.github.com/users/druid-io","html_url":"https://github.com/druid-io","followers_url":"https://api.github.com/users/druid-io/followers","following_url":"https://api.github.com/users/druid-io/following{/other_user}","gists_url":"https://api.github.com/users/druid-io/gists{/gist_id}","starred_url":"https://api.github.com/users/druid-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/druid-io/subscriptions","organizations_url":"https://api.github.com/users/druid-io/orgs","repos_url":"https://api.github.com/users/druid-io/repos","events_url":"https://api.github.com/users/druid-io/events{/privacy}","received_events_url":"https://api.github.com/users/druid-io/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/druid-io/druid","description":"Column oriented distributed data store ideal for powering interactive applications","fork":false,"url":"https://api.github.com/repos/druid-io/druid","forks_url":"https://api.github.com/repos/druid-io/druid/forks","keys_url":"https://api.github.com/repos/druid-io/druid/keys{/key_id}","collaborators_url":"https://api.github.com/repos/druid-io/druid/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/druid-io/druid/teams","hooks_url":"https://api.github.com/repos/druid-io/druid/hooks","issue_events_url":"https://api.github.com/repos/druid-io/druid/issues/events{/number}","events_url":"https://api.github.com/repos/druid-io/druid/events","assignees_url":"https://api.github.com/repos/druid-io/druid/assignees{/user}","branches_url":"https://api.github.com/repos/druid-io/druid/branches{/branch}","tags_url":"https://api.github.com/repos/druid-io/druid/tags","blobs_url":"https://api.github.com/repos/druid-io/druid/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/druid-io/druid/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/druid-io/druid/git/refs{/sha}","trees_url":"https://api.github.com/repos/druid-io/druid/git/trees{/sha}","statuses_url":"https://api.github.com/repos/druid-io/druid/statuses/{sha}","languages_url":"https://api.github.com/repos/druid-io/druid/languages","stargazers_url":"https://api.github.com/repos/druid-io/druid/stargazers","contributors_url":"https://api.github.com/repos/druid-io/druid/contributors","subscribers_url":"https://api.github.com/repos/druid-io/druid/subscribers","subscription_url":"https://api.github.com/repos/druid-io/druid/subscription","commits_url":"https://api.github.com/repos/druid-io/druid/commits{/sha}","git_commits_url":"https://api.github.com/repos/druid-io/druid/git/commits{/sha}","comments_url":"https://api.github.com/repos/druid-io/druid/comments{/number}","issue_comment_url":"https://api.github.com/repos/druid-io/druid/issues/comments{/number}","contents_url":"https://api.github.com/repos/druid-io/druid/contents/{+path}","compare_url":"https://api.github.com/repos/druid-io/druid/compare/{base}...{head}","merges_url":"https://api.github.com/repos/druid-io/druid/merges","archive_url":"https://api.github.com/repos/druid-io/druid/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/druid-io/druid/downloads","issues_url":"https://api.github.com/repos/druid-io/druid/issues{/number}","pulls_url":"https://api.github.com/repos/druid-io/druid/pulls{/number}","milestones_url":"https://api.github.com/repos/druid-io/druid/milestones{/number}","notifications_url":"https://api.github.com/repos/druid-io/druid/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/druid-io/druid/labels{/name}","releases_url":"https://api.github.com/repos/druid-io/druid/releases{/id}","deployments_url":"https://api.github.com/repos/druid-io/druid/deployments","created_at":"2012-10-23T19:08:07Z","updated_at":"2016-11-01T22:52:07Z","pushed_at":"2016-11-02T01:07:04Z","git_url":"git://github.com/druid-io/druid.git","ssh_url":"git@github.com:druid-io/druid.git","clone_url":"https://github.com/druid-io/druid.git","svn_url":"https://github.com/druid-io/druid","homepage":"http://druid.io","size":163804,"stargazers_count":3890,"watchers_count":3890,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":926,"mirror_url":null,"open_issues_count":560,"forks":926,"open_issues":560,"watchers":3890,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3611"},"html":{"href":"https://github.com/druid-io/druid/pull/3611"},"issue":{"href":"https://api.github.com/repos/druid-io/druid/issues/3611"},"comments":{"href":"https://api.github.com/repos/druid-io/druid/issues/3611/comments"},"review_comments":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3611/comments"},"review_comment":{"href":"https://api.github.com/repos/druid-io/druid/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3611/commits"},"statuses":{"href":"https://api.github.com/repos/druid-io/druid/statuses/516b929c63261fb392ee31eed6421c1c6213b580"}}}}
{"actor":{"display_login":"dclim"}}