apache/druid · warning

Node[ ] discovered but already exists [ ].

Error message

Node[%s] discovered but already exists [%s].

What it means

Log warning in DruidNodeDiscoveryProvider.NodeDiscoveryListener when a newly announced node's hostAndPort key already exists in the concurrent node map. The putIfAbsent returned an existing entry, so the duplicate announcement is discarded and listeners only see the first-seen node instance.

Solutions

  1. Ensure each node has a unique druid.host configuration across the cluster.
  2. Wait for the old ZK ephemeral node to expire and verify the entry disappears, or restart the affected node after cleanup.
  3. Check for ZK session expiry/reconnect storms in the logs; stabilize ZK connectivity.
  4. Verify no stale ZK znodes remain under the announcements path after abnormal shutdowns.

Example fix

// before
druid.host=historical1:8083  # duplicated on two nodes
// after
# node A
druid.host=historical1:8083
# node B
druid.host=historical2:8083
Defensive patterns

Strategy: validation

Validate before calling

// Ensure unique host:port per node before deployment
Set<String> hosts = allNodes.stream().map(NodeConfig::getHost).collect(toSet());
if (hosts.size() != allNodes.size()) throw new ConfigException("Duplicate druid.host values");

Prevention

When it happens

Trigger: A node re-announces at the same hostAndPort while the old entry is still present (e.g. fast restart where ZK session expiry has not yet removed the old ephemeral entry), or two processes are configured with the same host:port.

Common situations: Service restart faster than the ZK session/ephemeral-node timeout; duplicate configuration (two historicals with the same druid.host); ZK watcher lag delivering an add before the corresponding remove.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/51f6d025a56b893a. Report an issue: GitHub.

Appendix: source

Thrown at server/src/main/java/org/apache/druid/discovery/DruidNodeDiscoveryProvider.java:182

    /**
     * Listens for all node updates and filters them based on {@link #service}. Note: this listener is registered with
     * the objects returned from {@link #getForNodeRole(NodeRole)}, NOT with {@link ServiceDruidNodeDiscovery} itself.
     */
    class FilteringUpstreamListener implements DruidNodeDiscovery.Listener
    {
      @Override
      public void nodesAdded(Collection<DiscoveryDruidNode> nodesDiscovered)
      {
        synchronized (lock) {
          List<DiscoveryDruidNode> nodesAdded = new ArrayList<>();
          for (DiscoveryDruidNode node : nodesDiscovered) {
            if (node.getServices().containsKey(service)) {
              DiscoveryDruidNode prev = nodes.putIfAbsent(node.getDruidNode().getHostAndPortToUse(), node);

              if (prev == null) {
                nodesAdded.add(node);
              } else {
                log.warn("Node[%s] discovered but already exists [%s].", node, prev);
              }
            } else {
              log.warn("Node[%s] discovered but doesn't have service[%s]. Ignored.", node, service);
            }
          }

          if (nodesAdded.isEmpty()) {
            // Don't bother listeners with an empty update, it doesn't make sense.
            return;
          }

          Collection<DiscoveryDruidNode> unmodifiableNodesAdded = Collections.unmodifiableCollection(nodesAdded);
          for (Listener listener : listeners) {
            try {
              listener.nodesAdded(unmodifiableNodesAdded);
            }
            catch (Exception ex) {
              log.error(ex, "Listener[%s].nodesAdded(%s) threw exception. Ignored.", listener, nodesAdded);

View on GitHub (pinned to 9b90983fd2)