{"action":"created","comment":{"url":"https://api.github.com/repos/druid-io/druid/pulls/comments/110678859","pull_request_review_id":31867264,"id":110678859,"diff_hunk":"@@ -506,137 +265,441 @@ public boolean deleteLookup(final String tier, final String lookup, AuditInfo au\n    *\n    * @return The lookupName spec if found or null if not found or if no lookups at all are found\n    */\n-  public\n   @Nullable\n-  Map<String, Object> getLookup(final String tier, final String lookupName)\n+  public LookupExtractorFactoryMapContainer getLookup(final String tier, final String lookupName)\n   {\n-    final Map<String, Map<String, Map<String, Object>>> prior = getKnownLookups();\n+    final Map<String, Map<String, LookupExtractorFactoryMapContainer>> prior = getKnownLookups();\n     if (prior == null) {\n       LOG.warn(\"Requested tier [%s] lookupName [%s]. But no lookups exist!\", tier, lookupName);\n       return null;\n     }\n-    final Map<String, Map<String, Object>> tierLookups = prior.get(tier);\n+    final Map<String, LookupExtractorFactoryMapContainer> tierLookups = prior.get(tier);\n     if (tierLookups == null) {\n       LOG.warn(\"Tier [%s] does not exist\", tier);\n       return null;\n     }\n     return tierLookups.get(lookupName);\n   }\n \n-\n-  @LifecycleStart\n+  // start() and stop() are synchronized so that they never run in parallel in case of ZK acting funny or drui bug and\n+  // coordinator becomes leader and drops leadership in quick succession.\n   public void start()\n   {\n-    synchronized (startStopSync) {\n-      if (started) {\n-        return;\n+    synchronized(lifecycleLock) {\n+      if (!lifecycleLock.canStart()) {\n+        throw new ISE(\"LookupCoordinatorManager can't start.\");\n+      }\n+\n+      try {\n+        LOG.debug(\"Starting.\");\n+\n+        if (executorService != null &&\n+            !executorService.awaitTermination(\n+                lookupCoordinatorManagerConfig.getHostTimeout().getMillis() * 10,\n+                TimeUnit.MILLISECONDS\n+            )) {\n+          throw new ISE(\"WTF! LookupCoordinatorManager executor from last start() hasn't finished. Failed to Start.\");\n+        }\n+\n+        executorService = MoreExecutors.listeningDecorator(\n+            Executors.newScheduledThreadPool(\n+                lookupCoordinatorManagerConfig.getThreadPoolSize(),\n+                Execs.makeThreadFactory(\"LookupCoordinatorManager--%s\")\n+            )\n+        );\n+\n+        initializeLookupsConfigWatcher();\n+\n+        this.backgroundManagerExitedLatch = new CountDownLatch(1);\n+        this.backgroundManagerFuture = executorService.scheduleWithFixedDelay(\n+            this::lookupManagementLoop,\n+            2000, //initial delay to start management after a little while\n+            lookupCoordinatorManagerConfig.getPeriod(),\n+            TimeUnit.MILLISECONDS\n+        );\n+        Futures.addCallback(\n+            backgroundManagerFuture, new FutureCallback<Object>()\n+            {\n+              @Override\n+              public void onSuccess(@Nullable Object result)\n+              {\n+                backgroundManagerExitedLatch.countDown();\n+                LOG.debug(\"Exited background lookup manager\");\n+              }\n+\n+              @Override\n+              public void onFailure(Throwable t)\n+              {\n+                backgroundManagerExitedLatch.countDown();\n+                if (backgroundManagerFuture.isCancelled()) {\n+                  LOG.debug(\"Exited background lookup manager due to cancellation.\");\n+                } else {\n+                  LOG.makeAlert(t, \"Background lookup manager exited with error!\").emit();\n+                }\n+              }\n+            }\n+        );\n+\n+        LOG.debug(\"Started\");\n+      }\n+      catch (Exception ex) {\n+        LOG.makeAlert(ex, \"Got Exception while start()\").emit();\n       }\n-      if (executorService.isShutdown()) {\n-        throw new ISE(\"Cannot restart after stop!\");\n+      finally {\n+        //so that subsequent stop() would happen, even if start() failed with exception\n+        lifecycleLock.started();\n+        lifecycleLock.exitStart();\n       }\n-      lookupMapConfigRef = configManager.watch(\n-          LOOKUP_CONFIG_KEY,\n+    }\n+  }\n+\n+  public void stop()\n+  {\n+    synchronized (lifecycleLock) {\n+      if (!lifecycleLock.canStop()) {\n+        throw new ISE(\"LookupCoordinatorManager can't stop.\");\n+      }\n+\n+      try {\n+        LOG.debug(\"Stopping\");\n+\n+        if (backgroundManagerFuture != null && !backgroundManagerFuture.cancel(true)) {\n+          LOG.warn(\"Background lookup manager thread could not be cancelled\");\n+        }\n+\n+        if (executorService != null) {\n+          executorService.shutdownNow();\n+        }\n+\n+        LOG.debug(\"Stopped\");\n+      }\n+      catch (Exception ex) {\n+        LOG.makeAlert(ex, \"Got Exception while stop()\").emit();\n+      }\n+      finally {\n+        //so that subsequent start() would happen, even if stop() failed with exception\n+        lifecycleLock.exitStop();\n+        lifecycleLock.reset();\n+      }\n+    }\n+  }\n+\n+  private void initializeLookupsConfigWatcher()\n+  {\n+    //Note: this call is idempotent, so multiple start() would not cause any problems.\n+    lookupMapConfigRef = configManager.watch(\n+        LOOKUP_CONFIG_KEY,\n+        new TypeReference<Map<String, Map<String, LookupExtractorFactoryMapContainer>>>()\n+        {\n+        },\n+        null\n+    );\n+\n+    // backward compatibility with 0.10.0\n+    if (lookupMapConfigRef.get() == null) {\n+      Map<String, Map<String, Map<String, Object>>> oldLookups = configManager.watch(\n+          OLD_LOOKUP_CONFIG_KEY,\n           new TypeReference<Map<String, Map<String, Map<String, Object>>>>()\n           {\n           },\n           null\n-      );\n-      final ListenableScheduledFuture backgroundManagerFuture = this.backgroundManagerFuture = executorService.scheduleWithFixedDelay(\n-          new Runnable()\n-          {\n-            @Override\n-            public void run()\n-            {\n-              final Map<String, Map<String, Map<String, Object>>> allLookupTiers = lookupMapConfigRef.get();\n-              // Sanity check for if we are shutting down\n-              if (Thread.currentThread().isInterrupted()) {\n-                LOG.info(\"Not updating lookups because process was interrupted\");\n-                return;\n-              }\n-              if (!started) {\n-                LOG.info(\"Not started. Returning\");\n-                return;\n-              }\n-              if (allLookupTiers == null) {\n-                LOG.info(\"Not updating lookups because no data exists\");\n-                return;\n+      ).get();\n+\n+      if (oldLookups != null) {\n+        Map<String, Map<String, LookupExtractorFactoryMapContainer>> converted = new HashMap<>();\n+        oldLookups.forEach(\n+            (tier, oldTierLookups) -> {\n+              if (oldTierLookups != null && !oldTierLookups.isEmpty()) {\n+                converted.put(tier, convertTierLookups(oldTierLookups));\n               }\n-              for (final String tier : allLookupTiers.keySet()) {\n-                try {\n-                  final Map<String, Map<String, Object>> allLookups = allLookupTiers.get(tier);\n-                  final Map<String, Map<String, Object>> oldLookups = prior_update.get(tier);\n-                  final Collection<String> drops;\n-                  if (oldLookups == null) {\n-                    drops = ImmutableList.of();\n-                  } else {\n-                    drops = Sets.difference(oldLookups.keySet(), allLookups.keySet());\n-                  }\n-                  if (allLookupTiers == prior_update) {\n-                    LOG.debug(\"No updates\");\n-                    updateAllNewOnTier(tier, allLookups);\n-                  } else {\n-                    updateAllOnTier(tier, allLookups);\n-                    deleteAllOnTier(tier, drops);\n-                  }\n-                }\n-                catch (InterruptedException e) {\n-                  Thread.currentThread().interrupt();\n-                  throw Throwables.propagate(e);\n-                }\n-                catch (Exception e) {\n-                  LOG.error(e, \"Error updating lookups for tier [%s]. Will try again soon\", tier);\n-                }\n-              }\n-              prior_update = allLookupTiers;\n             }\n-          },\n-          0,\n-          lookupCoordinatorManagerConfig.getPeriod(),\n-          TimeUnit.MILLISECONDS\n-      );\n-      Futures.addCallback(backgroundManagerFuture, new FutureCallback<Object>()\n-      {\n-        @Override\n-        public void onSuccess(@Nullable Object result)\n-        {\n-          backgroundManagerExitedLatch.countDown();\n-          LOG.debug(\"Exited background lookup manager\");\n+        );\n+\n+        configManager.set(\n+            LOOKUP_CONFIG_KEY,\n+            converted,\n+            new AuditInfo(\"autoConversion\", \"autoConversion\", \"127.0.0.1\")\n+        );\n+      }\n+    }\n+  }\n+\n+  private Map<String, LookupExtractorFactoryMapContainer> convertTierLookups(\n+      Map<String, Map<String, Object>> oldTierLookups\n+  )\n+  {\n+    Map<String, LookupExtractorFactoryMapContainer> convertedTierLookups = new HashMap<>();\n+    oldTierLookups.forEach(\n+        (lookup, lookupExtractorFactory) -> {\n+          convertedTierLookups.put(lookup, new LookupExtractorFactoryMapContainer(null, lookupExtractorFactory));\n         }\n+    );\n+    return convertedTierLookups;\n+  }\n \n-        @Override\n-        public void onFailure(Throwable t)\n-        {\n-          backgroundManagerExitedLatch.countDown();\n-          if (backgroundManagerFuture.isCancelled()) {\n-            LOG.info(\"Background lookup manager exited\");\n-            LOG.trace(t, \"Background lookup manager exited with throwable\");\n-          } else {\n-            LOG.makeAlert(t, \"Background lookup manager exited with error!\").emit();\n-          }\n+  private void lookupManagementLoop()\n+  {\n+    // Sanity check for if we are shutting down\n+    if (Thread.currentThread().isInterrupted() || !lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS)) {\n+      LOG.info(\"Not updating lookups because process was interrupted or not finished starting yet.\");\n+      return;\n+    }\n+\n+    final Map<String, Map<String, LookupExtractorFactoryMapContainer>> allLookupTiers = lookupMapConfigRef.get();\n+\n+    if (allLookupTiers == null) {\n+      LOG.info(\"Not updating lookups because no data exists\");\n+      return;\n+    }\n+\n+    LOG.debug(\"Starting lookup sync for on all nodes.\");\n+\n+    final Map<HostAndPort, LookupsStateWithMap> currState = new ConcurrentHashMap<>();\n+\n+    try {\n+      List<ListenableFuture<?>> futures = new ArrayList<>();\n+      for (Map.Entry<String, Map<String, LookupExtractorFactoryMapContainer>> tierEntry : allLookupTiers.entrySet()) {\n+\n+        LOG.debug(\"Starting lookup mgmt for tier [%s].\", tierEntry.getKey());\n+\n+        final Map<String, LookupExtractorFactoryMapContainer> tierLookups = tierEntry.getValue();\n+        for (final HostAndPort node : listenerDiscoverer.getNodes(LookupModule.getTierListenerPath(tierEntry.getKey()))) {\n+\n+          LOG.debug(\n+              \"Starting lookup mgmt for tier [%s] and host [%s:%s].\",\n+              tierEntry.getKey(),\n+              node.getHostText(),\n+              node.getPort()\n+          );\n+\n+          futures.add(\n+              executorService.submit(\n+                  () -> {\n+                    try {\n+                      currState.put(node, this.doLookupManagementOnNode(node, tierLookups));\n+                    }\n+                    catch (Exception ex) {\n+                      LOG.makeAlert(\n+                          ex,\n+                          \"Failed to finish lookup management on node [%s:%s]\",\n+                          node.getHostText(),\n+                          node.getPort()\n+                      );\n+                    }\n+                  }\n+              )\n+          );\n         }\n-      });\n-      started = true;\n-      LOG.debug(\"Started\");\n+      }\n+\n+      final ListenableFuture allFuture = Futures.allAsList(futures);\n+      try {\n+        allFuture.get(lookupCoordinatorManagerConfig.getAllHostTimeout().getMillis(), TimeUnit.MILLISECONDS);\n+        knownOldState.set(currState);\n+      }\n+      catch (InterruptedException ex) {\n+        allFuture.cancel(true);\n+        Thread.currentThread().interrupt();\n+        throw ex;\n+      } catch (Exception ex) {\n+        allFuture.cancel(true);\n+        throw ex;\n+      }\n+\n+    } catch (Exception ex) {\n+      LOG.makeAlert(ex, \"Failed to finish lookup management loop.\").emit();\n     }\n+\n+    LOG.debug(\"Finished lookup sync for on all nodes.\");\n   }\n \n-  @LifecycleStop\n-  public void stop()\n+  private LookupsStateWithMap doLookupManagementOnNode(\n+      HostAndPort node,\n+      Map<String, LookupExtractorFactoryMapContainer> nodeTierLookupsToBe\n+  ) throws IOException, InterruptedException, ExecutionException\n+  {\n+    LOG.debug(\"Starting lookup sync for node [%s].\", node);\n+\n+    LookupsStateWithMap currLookupsStateOnNode = getLookupStateForNode(node);\n+    LOG.debug(\"Received lookups state from node [%s].\", node);\n+\n+\n+    // Compare currLookupsStateOnNode with nodeTierLookupsToBe to find what are the lookups\n+    // we need to further ask node to load/drop\n+    Map<String, LookupExtractorFactoryMapContainer> toLoad = getToBeLoadedOnNode(\n+        currLookupsStateOnNode,\n+        nodeTierLookupsToBe\n+    );\n+    Set<String> toDrop = getToBeDroppedFromNode(currLookupsStateOnNode, nodeTierLookupsToBe);\n+\n+    if (!toLoad.isEmpty() || !toDrop.isEmpty()) {\n+      // Send POST request to the node asking to load and drop the lookups necessary.\n+      // no need to send \"current\" in the LookupsStateWithMap , that is not required\n+      currLookupsStateOnNode = updateNode(node, new LookupsStateWithMap(null, toLoad, toDrop));\n+\n+      LOG.debug(\n+          \"Sent lookup toAdd[%s] and toDrop[%s] updates to node [%s].\",\n+          toLoad.keySet(),\n+          toDrop,\n+          node\n+      );\n+    }\n+\n+    LOG.debug(\"Finished lookup sync for node [%s].\", node);\n+    return currLookupsStateOnNode;\n+  }\n+\n+  // Returns the Map<lookup-name, lookup-spec> that needs to be loaded by the node and it does not know about\n+  // those already.\n+  private Map<String, LookupExtractorFactoryMapContainer> getToBeLoadedOnNode(\n+      LookupsStateWithMap currLookupsStateOnNode,\n+      Map<String, LookupExtractorFactoryMapContainer> nodeTierLookupsToBe\n+  )\n+  {\n+    Map<String, LookupExtractorFactoryMapContainer> toLoad = new HashMap<>();\n+    for (Map.Entry<String, LookupExtractorFactoryMapContainer> e : nodeTierLookupsToBe.entrySet()) {\n+      String name = e.getKey();\n+      LookupExtractorFactoryMapContainer lookupToBe = e.getValue();\n+\n+      // get it from the current pending notices list on the node\n+      LookupExtractorFactoryMapContainer current = currLookupsStateOnNode.getToLoad().get(name);\n+\n+      if (current == null) {\n+        //ok, not on pending list, get from currently loaded lookups on node\n+        current = currLookupsStateOnNode.getCurrent().get(name);\n+      }\n+\n+      if (current == null || //lookup is neither pending nor already loaded on the node OR\n+          lookupToBe.replaces(current) //lookup is already know to node, but lookupToBe overrides that\n+          ) {\n+        toLoad.put(name, lookupToBe);\n+      }\n+    }\n+    return toLoad;\n+  }\n+\n+  // Returns Set<lookup-name> that should be dropped from the node which has them already either in pending to load\n+  // state or loaded\n+  private Set<String> getToBeDroppedFromNode(\n+      LookupsStateWithMap currLookupsStateOnNode,\n+      Map<String, LookupExtractorFactoryMapContainer> nodeTierLookupsToBe\n+  )\n+  {\n+    Set<String> toDrop = new HashSet<>();\n+\n+    // {currently loading/loaded on the node} - {currently pending deletion on node} - {lookups node should actually have}\n+    toDrop.addAll(currLookupsStateOnNode.getCurrent().keySet());\n+    toDrop.addAll(currLookupsStateOnNode.getToLoad().keySet());\n+    toDrop = Sets.difference(toDrop, currLookupsStateOnNode.getToDrop());\n+    toDrop = Sets.difference(toDrop, nodeTierLookupsToBe.keySet());","path":"server/src/main/java/io/druid/server/lookup/cache/LookupCoordinatorManager.java","position":931,"original_position":931,"commit_id":"351f256f5897aae3359966446c00d8539f5ddf6a","original_commit_id":"351f256f5897aae3359966446c00d8539f5ddf6a","user":{"login":"himanshug","id":246308,"avatar_url":"https://avatars2.githubusercontent.com/u/246308?v=3","gravatar_id":"","url":"https://api.github.com/users/himanshug","html_url":"https://github.com/himanshug","followers_url":"https://api.github.com/users/himanshug/followers","following_url":"https://api.github.com/users/himanshug/following{/other_user}","gists_url":"https://api.github.com/users/himanshug/gists{/gist_id}","starred_url":"https://api.github.com/users/himanshug/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/himanshug/subscriptions","organizations_url":"https://api.github.com/users/himanshug/orgs","repos_url":"https://api.github.com/users/himanshug/repos","events_url":"https://api.github.com/users/himanshug/events{/privacy}","received_events_url":"https://api.github.com/users/himanshug/received_events","type":"User","site_admin":false},"body":"its not required ... toDrop is list of lookup names. let me know if you see a bug there.","created_at":"2017-04-10T14:58:00Z","updated_at":"2017-04-10T14:58:00Z","html_url":"https://github.com/druid-io/druid/pull/3855#discussion_r110678859","pull_request_url":"https://api.github.com/repos/druid-io/druid/pulls/3855","_links":{"self":{"href":"https://api.github.com/repos/druid-io/druid/pulls/comments/110678859"},"html":{"href":"https://github.com/druid-io/druid/pull/3855#discussion_r110678859"},"pull_request":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3855"}}},"pull_request":{"url":"https://api.github.com/repos/druid-io/druid/pulls/3855","id":101904786,"html_url":"https://github.com/druid-io/druid/pull/3855","diff_url":"https://github.com/druid-io/druid/pull/3855.diff","patch_url":"https://github.com/druid-io/druid/pull/3855.patch","issue_url":"https://api.github.com/repos/druid-io/druid/issues/3855","number":3855,"state":"open","locked":false,"title":"improvements to coordinator lookups management","user":{"login":"himanshug","id":246308,"avatar_url":"https://avatars2.githubusercontent.com/u/246308?v=3","gravatar_id":"","url":"https://api.github.com/users/himanshug","html_url":"https://github.com/himanshug","followers_url":"https://api.github.com/users/himanshug/followers","following_url":"https://api.github.com/users/himanshug/following{/other_user}","gists_url":"https://api.github.com/users/himanshug/gists{/gist_id}","starred_url":"https://api.github.com/users/himanshug/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/himanshug/subscriptions","organizations_url":"https://api.github.com/users/himanshug/orgs","repos_url":"https://api.github.com/users/himanshug/repos","events_url":"https://api.github.com/users/himanshug/events{/privacy}","received_events_url":"https://api.github.com/users/himanshug/received_events","type":"User","site_admin":false},"body":"PR for https://github.com/druid-io/druid/issues/3821\r\n\r\nMost major changes are done to LookupCoordinatorManager and LookupReferencesManager. This will not be backward compatible in that users will now have to specify version explicitly in addition to lookup specs when making requests for lookup adds/updates to coordinator.\r\n\r\nwhat changed?\r\n- LookupExtractorFactory.replaces(LookupExtractorFactory other) is deprecated and kept only to allow smooth transition from 0.9.x to 0.10.x\r\n- Versioning of the lookups is managed via wrapping LookupExtractorFactory and is managed by druid core. LookupReferencesManager now stores LookupExtractorFactoryContainer which is just a wrapper on LookupExtractorFactory with a version. Also Coordinator would accept LookupExtractorFactoryContainer in user requests, so user would explicitly provide a version.\r\n- Improvements to LookupCoordinatorManager loop that manages lookups\r\n- New Lookup Listener endpoint to accept request for batch add and drop of lookups\r\n- LookupReferencesManager now manages add/delete requests asynchronously in single thread\r\n- provide backward compatibility with older lookup specs stored in db, this code would read them from \"lookups\" key in config table, convert them and store them in new \"lookupsConfig\" key in the config table\r\n\r\nAlso Fixes #3975\r\n\r\nRelease Notes -\r\nAll coordinators should be stopped and then started with newer druid version. There should never be a situation where one coordinator is running 0.10.0 while other coordinator is running 0.10.1\r\n\r\n","created_at":"2017-01-17T17:01:58Z","updated_at":"2017-04-10T14:58:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4d75f149d7d2c5c2aced06650a08a38577cef9cc","assignee":{"login":"leventov","id":609240,"avatar_url":"https://avatars3.githubusercontent.com/u/609240?v=3","gravatar_id":"","url":"https://api.github.com/users/leventov","html_url":"https://github.com/leventov","followers_url":"https://api.github.com/users/leventov/followers","following_url":"https://api.github.com/users/leventov/following{/other_user}","gists_url":"https://api.github.com/users/leventov/gists{/gist_id}","starred_url":"https://api.github.com/users/leventov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leventov/subscriptions","organizations_url":"https://api.github.com/users/leventov/orgs","repos_url":"https://api.github.com/users/leventov/repos","events_url":"https://api.github.com/users/leventov/events{/privacy}","received_events_url":"https://api.github.com/users/leventov/received_events","type":"User","site_admin":false},"assignees":[{"login":"leventov","id":609240,"avatar_url":"https://avatars3.githubusercontent.com/u/609240?v=3","gravatar_id":"","url":"https://api.github.com/users/leventov","html_url":"https://github.com/leventov","followers_url":"https://api.github.com/users/leventov/followers","following_url":"https://api.github.com/users/leventov/following{/other_user}","gists_url":"https://api.github.com/users/leventov/gists{/gist_id}","starred_url":"https://api.github.com/users/leventov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leventov/subscriptions","organizations_url":"https://api.github.com/users/leventov/orgs","repos_url":"https://api.github.com/users/leventov/repos","events_url":"https://api.github.com/users/leventov/events{/privacy}","received_events_url":"https://api.github.com/users/leventov/received_events","type":"User","site_admin":false},{"login":"drcrallen","id":8213081,"avatar_url":"https://avatars1.githubusercontent.com/u/8213081?v=3","gravatar_id":"","url":"https://api.github.com/users/drcrallen","html_url":"https://github.com/drcrallen","followers_url":"https://api.github.com/users/drcrallen/followers","following_url":"https://api.github.com/users/drcrallen/following{/other_user}","gists_url":"https://api.github.com/users/drcrallen/gists{/gist_id}","starred_url":"https://api.github.com/users/drcrallen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drcrallen/subscriptions","organizations_url":"https://api.github.com/users/drcrallen/orgs","repos_url":"https://api.github.com/users/drcrallen/repos","events_url":"https://api.github.com/users/drcrallen/events{/privacy}","received_events_url":"https://api.github.com/users/drcrallen/received_events","type":"User","site_admin":false}],"milestone":{"url":"https://api.github.com/repos/druid-io/druid/milestones/19","html_url":"https://github.com/druid-io/druid/milestone/19","labels_url":"https://api.github.com/repos/druid-io/druid/milestones/19/labels","id":1571408,"number":19,"title":"0.10.1","description":"","creator":{"login":"fjy","id":428325,"avatar_url":"https://avatars3.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":38,"closed_issues":40,"state":"open","created_at":"2016-02-08T22:01:01Z","updated_at":"2017-04-10T04:06:42Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/druid-io/druid/pulls/3855/commits","review_comments_url":"https://api.github.com/repos/druid-io/druid/pulls/3855/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/3855/comments","statuses_url":"https://api.github.com/repos/druid-io/druid/statuses/351f256f5897aae3359966446c00d8539f5ddf6a","head":{"label":"himanshug:qtl_is_ready","ref":"qtl_is_ready","sha":"351f256f5897aae3359966446c00d8539f5ddf6a","user":{"login":"himanshug","id":246308,"avatar_url":"https://avatars2.githubusercontent.com/u/246308?v=3","gravatar_id":"","url":"https://api.github.com/users/himanshug","html_url":"https://github.com/himanshug","followers_url":"https://api.github.com/users/himanshug/followers","following_url":"https://api.github.com/users/himanshug/following{/other_user}","gists_url":"https://api.github.com/users/himanshug/gists{/gist_id}","starred_url":"https://api.github.com/users/himanshug/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/himanshug/subscriptions","organizations_url":"https://api.github.com/users/himanshug/orgs","repos_url":"https://api.github.com/users/himanshug/repos","events_url":"https://api.github.com/users/himanshug/events{/privacy}","received_events_url":"https://api.github.com/users/himanshug/received_events","type":"User","site_admin":false},"repo":{"id":23076411,"name":"druid","full_name":"himanshug/druid","owner":{"login":"himanshug","id":246308,"avatar_url":"https://avatars2.githubusercontent.com/u/246308?v=3","gravatar_id":"","url":"https://api.github.com/users/himanshug","html_url":"https://github.com/himanshug","followers_url":"https://api.github.com/users/himanshug/followers","following_url":"https://api.github.com/users/himanshug/following{/other_user}","gists_url":"https://api.github.com/users/himanshug/gists{/gist_id}","starred_url":"https://api.github.com/users/himanshug/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/himanshug/subscriptions","organizations_url":"https://api.github.com/users/himanshug/orgs","repos_url":"https://api.github.com/users/himanshug/repos","events_url":"https://api.github.com/users/himanshug/events{/privacy}","received_events_url":"https://api.github.com/users/himanshug/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/himanshug/druid","description":"Real²time Exploratory Analytics on Large Datasets","fork":true,"url":"https://api.github.com/repos/himanshug/druid","forks_url":"https://api.github.com/repos/himanshug/druid/forks","keys_url":"https://api.github.com/repos/himanshug/druid/keys{/key_id}","collaborators_url":"https://api.github.com/repos/himanshug/druid/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/himanshug/druid/teams","hooks_url":"https://api.github.com/repos/himanshug/druid/hooks","issue_events_url":"https://api.github.com/repos/himanshug/druid/issues/events{/number}","events_url":"https://api.github.com/repos/himanshug/druid/events","assignees_url":"https://api.github.com/repos/himanshug/druid/assignees{/user}","branches_url":"https://api.github.com/repos/himanshug/druid/branches{/branch}","tags_url":"https://api.github.com/repos/himanshug/druid/tags","blobs_url":"https://api.github.com/repos/himanshug/druid/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/himanshug/druid/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/himanshug/druid/git/refs{/sha}","trees_url":"https://api.github.com/repos/himanshug/druid/git/trees{/sha}","statuses_url":"https://api.github.com/repos/himanshug/druid/statuses/{sha}","languages_url":"https://api.github.com/repos/himanshug/druid/languages","stargazers_url":"https://api.github.com/repos/himanshug/druid/stargazers","contributors_url":"https://api.github.com/repos/himanshug/druid/contributors","subscribers_url":"https://api.github.com/repos/himanshug/druid/subscribers","subscription_url":"https://api.github.com/repos/himanshug/druid/subscription","commits_url":"https://api.github.com/repos/himanshug/druid/commits{/sha}","git_commits_url":"https://api.github.com/repos/himanshug/druid/git/commits{/sha}","comments_url":"https://api.github.com/repos/himanshug/druid/comments{/number}","issue_comment_url":"https://api.github.com/repos/himanshug/druid/issues/comments{/number}","contents_url":"https://api.github.com/repos/himanshug/druid/contents/{+path}","compare_url":"https://api.github.com/repos/himanshug/druid/compare/{base}...{head}","merges_url":"https://api.github.com/repos/himanshug/druid/merges","archive_url":"https://api.github.com/repos/himanshug/druid/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/himanshug/druid/downloads","issues_url":"https://api.github.com/repos/himanshug/druid/issues{/number}","pulls_url":"https://api.github.com/repos/himanshug/druid/pulls{/number}","milestones_url":"https://api.github.com/repos/himanshug/druid/milestones{/number}","notifications_url":"https://api.github.com/repos/himanshug/druid/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/himanshug/druid/labels{/name}","releases_url":"https://api.github.com/repos/himanshug/druid/releases{/id}","deployments_url":"https://api.github.com/repos/himanshug/druid/deployments","created_at":"2014-08-18T15:28:53Z","updated_at":"2015-02-23T20:08:38Z","pushed_at":"2017-04-06T16:46:24Z","git_url":"git://github.com/himanshug/druid.git","ssh_url":"git@github.com:himanshug/druid.git","clone_url":"https://github.com/himanshug/druid.git","svn_url":"https://github.com/himanshug/druid","homepage":"http://druid.io","size":118012,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"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":"84fe91ba0b332c31b94ce5ca63a9bb63c2ec756a","user":{"login":"druid-io","id":4203474,"avatar_url":"https://avatars3.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://avatars3.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":"2017-04-10T12:53:20Z","pushed_at":"2017-04-10T11:08:13Z","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":119372,"stargazers_count":4692,"watchers_count":4692,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1111,"mirror_url":null,"open_issues_count":645,"forks":1111,"open_issues":645,"watchers":4692,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3855"},"html":{"href":"https://github.com/druid-io/druid/pull/3855"},"issue":{"href":"https://api.github.com/repos/druid-io/druid/issues/3855"},"comments":{"href":"https://api.github.com/repos/druid-io/druid/issues/3855/comments"},"review_comments":{"href":"https://api.github.com/repos/druid-io/druid/pulls/3855/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/3855/commits"},"statuses":{"href":"https://api.github.com/repos/druid-io/druid/statuses/351f256f5897aae3359966446c00d8539f5ddf6a"}}}}
{"actor":{"display_login":"himanshug"}}