This is an automated email from the ASF dual-hosted git repository.
kgyrtkirk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 63db6623bfc feat: Make Coordinator client maxAttempts configurable
(#20442)
63db6623bfc is described below
commit 63db6623bfcf17712bf00369b76f0d8e3e2bf3fe
Author: Zoltan Haindrich <[email protected]>
AuthorDate: Tue Oct 27 10:08:13 2026 +0100
feat: Make Coordinator client maxAttempts configurable (#20442)
---
docs/configuration/index.md | 1 +
.../druid/client/BrokerViewOfBrokerConfig.java | 31 -------------
.../client/BrokerViewOfCoordinatorConfig.java | 28 ------------
.../coordinator/CoordinatorClientConfig.java | 41 +++++++++++++++++
.../druid/rpc/guice/ServiceClientModule.java | 9 ++--
.../druid/rpc/guice/ServiceClientModuleTest.java | 52 +++++++++++++++++++++-
.../testing/embedded/EmbeddedServiceClient.java | 4 +-
website/.spelling | 1 +
8 files changed, 103 insertions(+), 64 deletions(-)
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 7745e41bd00..de05cec50d4 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -707,6 +707,7 @@ All Druid components can communicate with each other over
HTTP.
|`druid.global.http.connectTimeout`|Connect timeout for the HTTP client used
for most direct RPC between Druid services. This covers, among other things,
Overlord-to-task and supervisor-to-task calls in the indexing service,
Coordinator lookup management, dynamic config sync between services, MSQ tasks
reading from data servers, and general Coordinator/Overlord/Broker service
clients. Does not affect Broker-to-Historical query dispatch (see
`druid.broker.http.connectTimeout`) or request [...]
|`druid.global.http.allocator`|Netty memory allocator used by the direct-RPC
HTTP client. Accepts `adaptive` (adaptive between `pooled` and `unpooled` based
on load), `pooled`, or `unpooled`.|`adaptive`|
|`druid.global.http.poolImplementation`|How the connection pool tracks demand,
never exceeding `numConnections` either way. With `adaptive`, a request
discards every stale or broken connection it walks past and opens a new one
only once none is left, so the pool falls back to the number of connections the
traffic actually needs. With `retaining`, the pool holds on to every connection
it has opened, replacing a stale or broken one by a fresh one, one for one, so
it stays at its high-water [...]
+|`druid.client.coordinator.maxAttempts`|Maximum number of attempts, including
the first one, that a service makes for a request to the Coordinator before
giving up on a retryable error. Must be at least 1.|`15`|
### Common endpoints configuration
diff --git
a/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
b/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
index 3129a020393..0831092b088 100644
--- a/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
+++ b/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
@@ -19,22 +19,12 @@
package org.apache.druid.client;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import jakarta.validation.constraints.NotNull;
-import org.apache.druid.client.coordinator.Coordinator;
import org.apache.druid.client.coordinator.CoordinatorClient;
-import org.apache.druid.client.coordinator.CoordinatorClientImpl;
-import org.apache.druid.discovery.NodeRole;
-import org.apache.druid.guice.annotations.EscalatedGlobal;
-import org.apache.druid.guice.annotations.Json;
import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContext;
-import org.apache.druid.rpc.ServiceClientFactory;
-import org.apache.druid.rpc.ServiceLocator;
-import org.apache.druid.rpc.StandardRetryPolicy;
import org.apache.druid.server.broker.BrokerDynamicConfig;
import org.apache.druid.server.broker.QueryConfigSnapshot;
@@ -63,27 +53,6 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
private volatile QueryConfigSnapshot querySnapshot;
@Inject
- public BrokerViewOfBrokerConfig(
- @Json final ObjectMapper jsonMapper,
- @EscalatedGlobal final ServiceClientFactory clientFactory,
- @Coordinator final ServiceLocator serviceLocator,
- final DefaultQueryConfig defaultQueryConfig
- )
- {
- this.defaultQueryConfig = defaultQueryConfig;
- this.querySnapshot = new
QueryConfigSnapshot(QueryContext.of(defaultQueryConfig.getContext()).asMap(),
null);
- this.coordinatorClient =
- new CoordinatorClientImpl(
- clientFactory.makeClient(
- NodeRole.COORDINATOR.getJsonName(),
- serviceLocator,
- StandardRetryPolicy.builder().maxAttempts(15).build()
- ),
- jsonMapper
- );
- }
-
- @VisibleForTesting
public BrokerViewOfBrokerConfig(
final CoordinatorClient coordinatorClient,
final DefaultQueryConfig defaultQueryConfig
diff --git
a/server/src/main/java/org/apache/druid/client/BrokerViewOfCoordinatorConfig.java
b/server/src/main/java/org/apache/druid/client/BrokerViewOfCoordinatorConfig.java
index 20f25784086..404323da853 100644
---
a/server/src/main/java/org/apache/druid/client/BrokerViewOfCoordinatorConfig.java
+++
b/server/src/main/java/org/apache/druid/client/BrokerViewOfCoordinatorConfig.java
@@ -19,24 +19,14 @@
package org.apache.druid.client;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import jakarta.validation.constraints.NotNull;
-import org.apache.druid.client.coordinator.Coordinator;
import org.apache.druid.client.coordinator.CoordinatorClient;
-import org.apache.druid.client.coordinator.CoordinatorClientImpl;
import org.apache.druid.client.selector.HistoricalFilter;
-import org.apache.druid.discovery.NodeRole;
import org.apache.druid.error.DruidException;
-import org.apache.druid.guice.annotations.EscalatedGlobal;
-import org.apache.druid.guice.annotations.Json;
import org.apache.druid.query.CloneQueryMode;
-import org.apache.druid.rpc.ServiceClientFactory;
-import org.apache.druid.rpc.ServiceLocator;
-import org.apache.druid.rpc.StandardRetryPolicy;
import org.apache.druid.server.BrokerDynamicConfigResource;
import org.apache.druid.server.coordinator.CoordinatorDynamicConfig;
@@ -60,24 +50,6 @@ public class BrokerViewOfCoordinatorConfig extends
BaseBrokerViewOfConfig<Coordi
private volatile Set<String> sourceCloneServers = Set.of();
@Inject
- public BrokerViewOfCoordinatorConfig(
- @Json final ObjectMapper jsonMapper,
- @EscalatedGlobal final ServiceClientFactory clientFactory,
- @Coordinator final ServiceLocator serviceLocator
- )
- {
- this.coordinatorClient =
- new CoordinatorClientImpl(
- clientFactory.makeClient(
- NodeRole.COORDINATOR.getJsonName(),
- serviceLocator,
- StandardRetryPolicy.builder().maxAttempts(15).build()
- ),
- jsonMapper
- );
- }
-
- @VisibleForTesting
public BrokerViewOfCoordinatorConfig(CoordinatorClient coordinatorClient)
{
this.coordinatorClient = coordinatorClient;
diff --git
a/server/src/main/java/org/apache/druid/client/coordinator/CoordinatorClientConfig.java
b/server/src/main/java/org/apache/druid/client/coordinator/CoordinatorClientConfig.java
new file mode 100644
index 00000000000..448727c6931
--- /dev/null
+++
b/server/src/main/java/org/apache/druid/client/coordinator/CoordinatorClientConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.client.coordinator;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.Min;
+
+/**
+ * Configuration of the client that other services use to call the Coordinator.
+ */
+public class CoordinatorClientConfig
+{
+ /**
+ * Attempts per request to the Coordinator, the first one included, before
giving up on a retryable error.
+ */
+ @JsonProperty
+ @Min(1)
+ private int maxAttempts = 15;
+
+ public int getMaxAttempts()
+ {
+ return maxAttempts;
+ }
+}
diff --git
a/server/src/main/java/org/apache/druid/rpc/guice/ServiceClientModule.java
b/server/src/main/java/org/apache/druid/rpc/guice/ServiceClientModule.java
index 203886f99fe..1dbca72c24b 100644
--- a/server/src/main/java/org/apache/druid/rpc/guice/ServiceClientModule.java
+++ b/server/src/main/java/org/apache/druid/rpc/guice/ServiceClientModule.java
@@ -27,10 +27,12 @@ import org.apache.druid.client.broker.BrokerClient;
import org.apache.druid.client.broker.BrokerClientImpl;
import org.apache.druid.client.coordinator.Coordinator;
import org.apache.druid.client.coordinator.CoordinatorClient;
+import org.apache.druid.client.coordinator.CoordinatorClientConfig;
import org.apache.druid.client.coordinator.CoordinatorClientImpl;
import org.apache.druid.client.indexing.IndexingService;
import org.apache.druid.discovery.DruidNodeDiscoveryProvider;
import org.apache.druid.discovery.NodeRole;
+import org.apache.druid.guice.JsonConfigProvider;
import org.apache.druid.guice.LazySingleton;
import org.apache.druid.guice.ManageLifecycle;
import org.apache.druid.guice.annotations.EscalatedGlobal;
@@ -57,7 +59,7 @@ public class ServiceClientModule implements DruidModule
@Override
public void configure(Binder binder)
{
- // Nothing to do.
+ JsonConfigProvider.bind(binder, "druid.client.coordinator",
CoordinatorClientConfig.class);
}
@Provides
@@ -112,13 +114,14 @@ public class ServiceClientModule implements DruidModule
@Coordinator
public ServiceClient makeServiceClientForCoordinator(
@EscalatedGlobal final ServiceClientFactory clientFactory,
- @Coordinator final ServiceLocator serviceLocator
+ @Coordinator final ServiceLocator serviceLocator,
+ final CoordinatorClientConfig config
)
{
return clientFactory.makeClient(
NodeRole.COORDINATOR.getJsonName(),
serviceLocator,
- StandardRetryPolicy.builder().maxAttempts(CLIENT_MAX_ATTEMPTS).build()
+
StandardRetryPolicy.builder().maxAttempts(config.getMaxAttempts()).build()
);
}
diff --git
a/server/src/test/java/org/apache/druid/rpc/guice/ServiceClientModuleTest.java
b/server/src/test/java/org/apache/druid/rpc/guice/ServiceClientModuleTest.java
index f667cb8db5f..033485dc575 100644
---
a/server/src/test/java/org/apache/druid/rpc/guice/ServiceClientModuleTest.java
+++
b/server/src/test/java/org/apache/druid/rpc/guice/ServiceClientModuleTest.java
@@ -23,12 +23,16 @@ import com.google.common.collect.ImmutableList;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
+import com.google.inject.ProvisionException;
import org.apache.druid.client.broker.Broker;
import org.apache.druid.client.broker.BrokerClient;
import org.apache.druid.client.coordinator.Coordinator;
import org.apache.druid.client.coordinator.CoordinatorClient;
+import org.apache.druid.client.coordinator.CoordinatorClientConfig;
import org.apache.druid.client.indexing.IndexingService;
import org.apache.druid.discovery.DruidNodeDiscoveryProvider;
+import org.apache.druid.discovery.NodeRole;
+import org.apache.druid.guice.ConfigModule;
import org.apache.druid.guice.DruidGuiceExtensions;
import org.apache.druid.guice.LifecycleModule;
import org.apache.druid.guice.annotations.EscalatedGlobal;
@@ -37,16 +41,22 @@ import org.apache.druid.java.util.http.client.HttpClient;
import org.apache.druid.rpc.ServiceClient;
import org.apache.druid.rpc.ServiceClientFactory;
import org.apache.druid.rpc.ServiceLocator;
+import org.apache.druid.rpc.ServiceRetryPolicy;
import org.apache.druid.rpc.indexing.OverlordClient;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
+import java.util.Properties;
+
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.WARN)
public class ServiceClientModuleTest
@@ -68,9 +78,17 @@ public class ServiceClientModuleTest
@BeforeEach
public void setUp()
{
- injector = Guice.createInjector(
+ injector = makeInjector("4");
+ }
+
+ private Injector makeInjector(final String coordinatorMaxAttempts)
+ {
+ final Properties properties = new Properties();
+ properties.setProperty("druid.client.coordinator.maxAttempts",
coordinatorMaxAttempts);
+ return Guice.createInjector(
ImmutableList.of(
new DruidGuiceExtensions(),
+ new ConfigModule(),
new LifecycleModule(),
new JacksonModule(),
new ServiceClientModule(),
@@ -79,6 +97,7 @@ public class ServiceClientModuleTest
binder.bind(ServiceLocator.class).toInstance(serviceLocator);
binder.bind(DruidNodeDiscoveryProvider.class).toInstance(discoveryProvider);
binder.bind(ServiceClientFactory.class).toInstance(serviceClientFactory);
+ binder.bind(Properties.class).toInstance(properties);
}
)
);
@@ -102,6 +121,37 @@ public class ServiceClientModuleTest
Assertions.assertNotNull(injector.getInstance(CoordinatorClient.class));
}
+ @Test
+ public void testCoordinatorClientConfigRejectsNonPositiveMaxAttempts()
+ {
+ Assertions.assertThrows(
+ ProvisionException.class,
+ () -> makeInjector("0").getInstance(CoordinatorClientConfig.class)
+ );
+ Assertions.assertThrows(
+ ProvisionException.class,
+ () -> makeInjector("-1").getInstance(CoordinatorClientConfig.class)
+ );
+ }
+
+ @Test
+ public void testCoordinatorRetryPolicyUsesConfiguredMaxAttempts()
+ {
+ new ServiceClientModule().makeServiceClientForCoordinator(
+ serviceClientFactory,
+ serviceLocator,
+ injector.getInstance(CoordinatorClientConfig.class)
+ );
+
+ final ArgumentCaptor<ServiceRetryPolicy> retryPolicy =
ArgumentCaptor.forClass(ServiceRetryPolicy.class);
+ Mockito.verify(serviceClientFactory).makeClient(
+ ArgumentMatchers.eq(NodeRole.COORDINATOR.getJsonName()),
+ ArgumentMatchers.eq(serviceLocator),
+ retryPolicy.capture()
+ );
+ Assertions.assertEquals(4, retryPolicy.getValue().maxAttempts());
+ }
+
@Test
public void testGetBrokerClient()
{
diff --git
a/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedServiceClient.java
b/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedServiceClient.java
index 932b82cb41a..c8f3c24eee6 100644
---
a/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedServiceClient.java
+++
b/services/src/test/java/org/apache/druid/testing/embedded/EmbeddedServiceClient.java
@@ -28,6 +28,7 @@ import org.apache.druid.client.broker.BrokerClient;
import org.apache.druid.client.broker.BrokerClientImpl;
import org.apache.druid.client.coordinator.Coordinator;
import org.apache.druid.client.coordinator.CoordinatorClient;
+import org.apache.druid.client.coordinator.CoordinatorClientConfig;
import org.apache.druid.client.indexing.IndexingService;
import org.apache.druid.common.guava.FutureUtils;
import org.apache.druid.discovery.NodeRole;
@@ -105,7 +106,8 @@ public class EmbeddedServiceClient
);
this.coordinatorServiceClient = module.makeServiceClientForCoordinator(
factory,
- anyServer.bindings().getInstance(ServiceLocator.class,
Coordinator.class)
+ anyServer.bindings().getInstance(ServiceLocator.class,
Coordinator.class),
+ new CoordinatorClientConfig()
);
}
diff --git a/website/.spelling b/website/.spelling
index 14b88a809d2..8c913436eb6 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -2650,6 +2650,7 @@ SRE
reachability
ownership_mismatch
giveup
+retryable
- ../docs/development/extensions-contrib/spectator-histogram.md
SpectatorHistogram
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]