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
- Ensure each node has a unique druid.host configuration across the cluster.
- Wait for the old ZK ephemeral node to expire and verify the entry disappears, or restart the affected node after cleanup.
- Check for ZK session expiry/reconnect storms in the logs; stabilize ZK connectivity.
- 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
- Assign unique druid.host to every node
- Avoid restarts faster than the ZK ephemeral session timeout, or use distinct ports during rollout
- Monitor ZK session expiry storms
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
- Cache for node role [ ] could not be initialized before…
- Cache initialization for node role
- Node[ ] disappeared but was unknown for service listener […
- Node [ ] of role [ ] went offline.
- Watcher for node role
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)