You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by "abhishekrb19 (via GitHub)" <gi...@apache.org> on 2026/03/05 17:37:16 UTC
[PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
abhishekrb19 opened a new pull request, #19094:
URL: https://github.com/apache/druid/pull/19094
### Summary
Adds two new Broker `TierSelectorStrategy` implementations to provide finer control over how Brokers select Historical and Realtime servers for query execution.
- **strict** – Only selects servers whose priorities match the configured list.
Unlike other existing strategies, there is no fallback to servers with other priorities if the configured priorities are unavailable. This also addresses a current limitation with watched tiers: when multiple tiers are configured, Brokers can still retain visibility into the state of the cluster, while enforcing query isolation at the time of server selection rather than filtering things at the time of server view.
- **pooled** – Pools servers across the configured priorities and selects among them, allowing queries to utilize multiple priority tiers for improved availability. This is particularly useful for querying realtime servers where the number of task replicas per tier may be limited for cost reasons.
Both strategies require the configured set of priorities to be non-empty. Similar to queries routed to tiers that are not part of the watched tiers, these strategies may result in queries returning no data if the configured priorities are unavailable.
### Release note
Operators can now configure two new Broker `TierSelectorStrategy` implementations:
- strict: Only selects servers whose priorities match the configured list. Example configuration: `druid.broker.select.tier=strict` and `druid.broker.select.tier.strict.priorities=[1]`.
- pooled: Pools servers across the configured priorities and selects among them, allowing queries to utilize multiple priority tiers for improved availability. Example configuration: `druid.broker.select.tier=pooled` and `druid.broker.select.tier.pooled.priorities=[2,1]`.
These strategies can also be configured for Realtime servers using `druid.broker.realtime.select.tier`.
This PR has:
- [x] been self-reviewed.
- [ ] added documentation for new or modified features or behaviors. (Will add docs in a follow up along with previous realtime tier configuration)
- [x] a release note entry in the PR description.
- [x] added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
- [x] added unit tests or modified existing tests to cover new code paths, ensuring the threshold for [code coverage](https://github.com/apache/druid/blob/master/dev/code-review/code-coverage.md) is met.
- [x] been tested in a test Druid cluster.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "abhishekrb19 (via GitHub)" <gi...@apache.org>.
abhishekrb19 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2909166474
##########
server/src/main/java/org/apache/druid/client/selector/StrictTierSelectorStrategy.java:
##########
@@ -0,0 +1,183 @@
+/*
+ * 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.selector;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
+import org.apache.druid.client.QueryableDruidServer;
+import org.apache.druid.java.util.emitter.EmittingLogger;
+import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.metadata.metadata.SegmentMetadataQuery;
+import org.apache.druid.timeline.DataSegment;
+
+import javax.annotation.Nullable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link TierSelectorStrategy} that only considers servers whose priorities
+ * are explicitly listed in {@link StrictTierSelectorStrategyConfig#getPriorities()}.
+ * <p>
+ * Unlike other strategies like {@link CustomTierSelectorStrategy} that falls back to servers with different priorities,
+ * this strategy strictly filters the available servers to the configured priorities.
+ * If no servers match the configured priorities, an empty server list is returned from {@link #pick(Query, Int2ObjectRBTreeMap, DataSegment, int)},
+ * which may cause queries to return partial or no data.
+ * <p>
+ * When multiple priorities are configured, they are evaluated in the order specified, with earlier priorities
+ * preferred over later ones.
+ * <p>
+ * Example configuration:
+ * <li> <code> druid.broker.select.tier=strict </code> </li>
+ * <li> <code> druid.broker.select.tier.strict.priorities=[2,1] </code> </li>
+ * <p>
+ * With this configuration, servers with priority 2 are preferred over servers with priority 1.
+ * Servers with any other tier priority are not considered.
+ * <p>
+ * This strategy is useful when query isolation is required between different server priority tiers. Brokers may still
+ * be configured to watch all server tiers, allowing them to retain visibility into the overall cluster state
+ * while enforcing isolation at query time.
+ */
+public class StrictTierSelectorStrategy extends AbstractTierSelectorStrategy
+{
+ private static final EmittingLogger log = new EmittingLogger(StrictTierSelectorStrategy.class);
+ public static final String TYPE = "strict";
+
+ private final StrictTierSelectorStrategyConfig config;
+ private final ServiceEmitter emitter;
+ private final Map<Integer, Integer> configuredPriorities;
+ private final Comparator<Integer> comparator;
+
+ @JsonCreator
+ public StrictTierSelectorStrategy(
+ @JacksonInject final ServerSelectorStrategy serverSelectorStrategy,
+ @JacksonInject final StrictTierSelectorStrategyConfig config,
+ @JacksonInject final ServiceEmitter emitter
+ )
+ {
+ super(serverSelectorStrategy);
+ this.config = config;
+ this.emitter = emitter;
+
+ configuredPriorities = new HashMap<>();
+ for (int i = 0; i < config.getPriorities().size(); i++) {
+ configuredPriorities.put(config.getPriorities().get(i), i);
+ }
+
+ this.comparator = (p1, p2) -> {
+ final Integer rank1 = configuredPriorities.get(p1);
+ final Integer rank2 = configuredPriorities.get(p2);
+
+ if (rank1 != null && rank2 != null) {
+ return Integer.compare(rank1, rank2);
+ }
+ if (rank1 != null) {
+ return -1;
+ }
+ if (rank2 != null) {
+ return 1;
+ }
+
+ // Priorities outside configuredPriorities don't matter and won't be selected in pick() for this strategy.
+ // This fallback ordering can be anything and is needed because the comparator may be used by ServerSelector/ServerView
+ // which maintains views of all servers, including those with unconfigured priorities.
+ return Integer.compare(p2, p1);
+ };
+ }
+
+ @Override
+ public <T> List<QueryableDruidServer> pick(
+ @Nullable final Query<T> query,
+ final Int2ObjectRBTreeMap<Set<QueryableDruidServer>> prioritizedServers,
+ final DataSegment segment,
+ final int numServersToPick
+ )
+ {
+ final Int2ObjectRBTreeMap<Set<QueryableDruidServer>> candidatePrioritizedServers = new Int2ObjectRBTreeMap<>(getComparator());
+
+ for (Int2ObjectMap.Entry<Set<QueryableDruidServer>> entry : prioritizedServers.int2ObjectEntrySet()) {
+ final int priority = entry.getIntKey();
+ final Set<QueryableDruidServer> servers = entry.getValue();
+
+ if (configuredPriorities.containsKey(priority)) {
+ candidatePrioritizedServers.put(priority, servers);
+ } else {
+ log.debug(
+ "Server priority[%d] not in the configured list of priorities[%s] so ignore servers[%s] for query[%s]",
+ priority, config.getPriorities(), servers, query
+ );
+ }
+ }
+
+ if (candidatePrioritizedServers.isEmpty()) {
+ if (query == null || query instanceof SegmentMetadataQuery) {
Review Comment:
I think the legacy `dataSourceMetadata` queries can be treated the same as other query types. Unlike system-generated queries such as `segmentMetadata`, these would be user-initiated and won't likely cause alert/log spam
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "github-advanced-security[bot] (via GitHub)" <gi...@apache.org>.
github-advanced-security[bot] commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2891595932
##########
server/src/test/java/org/apache/druid/client/selector/TierSelectorStrategyTest.java:
##########
@@ -568,4 +586,366 @@
preferredTierHighPriority, preferredTierLowPriority, nonPreferredTierHighestPriority
);
}
+
+ @Test
+ public void testStrictTierSelectorStrategyAllConfigured()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ testTierSelectorStrategy(
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(2, 0, -1)),
+ serviceEmitter
+ ),
+ p2, p0, pNeg1
+ );
+ }
+
+ @Test
+ public void testStrictTierSelectorStrategyNoMatchingPriorities()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 1),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
Review Comment:
## Deprecated method or constructor invocation
Invoking [DataSegment.DataSegment](1) should be avoided because it has been deprecated.
[Show more details](https://github.com/apache/druid/security/code-scanning/10856)
##########
server/src/test/java/org/apache/druid/client/selector/TierSelectorStrategyTest.java:
##########
@@ -568,4 +586,366 @@
preferredTierHighPriority, preferredTierLowPriority, nonPreferredTierHighestPriority
);
}
+
+ @Test
+ public void testStrictTierSelectorStrategyAllConfigured()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ testTierSelectorStrategy(
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(2, 0, -1)),
+ serviceEmitter
+ ),
+ p2, p0, pNeg1
+ );
+ }
+
+ @Test
+ public void testStrictTierSelectorStrategyNoMatchingPriorities()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 1),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(5, 6)),
+ serviceEmitter
+ ),
+ HistoricalFilter.IDENTITY_FILTER
+ );
+
+ List<QueryableDruidServer> servers = List.of(p0, p1, p2);
+ for (QueryableDruidServer server : servers) {
+ serverSelector.addServerAndUpdateSegment(server, serverSelector.getSegment());
+ }
+
+ // Should return null when no matching priorities
+ Assert.assertNull(serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES));
+ Assert.assertNull(serverSelector.pick(GroupByQuery.builder()
+ .setDataSource("foo3")
+ .setInterval(new MultipleIntervalSegmentSpec(ImmutableList.of(Intervals.of("2000/3000"))))
+ .setGranularity(Granularities.ALL)
+ .setDimensions(new DefaultDimensionSpec("dim2", "d0"))
+ .build(), CloneQueryMode.EXCLUDECLONES));
+
+ serviceEmitter.verifyEmitted("tierSelector/noServer", 1);
+
+ // Should return empty list for getCandidates
+ Assert.assertEquals(Collections.emptyList(), serverSelector.getCandidates(2, CloneQueryMode.EXCLUDECLONES));
+ }
+
+ @Test
+ public void testEmptyStrictTierPrioritiesThrowsException()
+ {
+ DruidException ex = Assert.assertThrows(
+ DruidException.class,
+ () -> new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of()),
+ serviceEmitter
+ )
+ );
+ Assert.assertEquals(
+ "priorities must be non-empty when using strict tier selector on the Broker. Found priorities[[]].",
+ ex.getMessage()
+ );
+ }
+
+ @Test
+ public void testPooledTierSelectorWithRandomServerStrategy()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ TierSelectorStrategy strategy = new PooledTierSelectorStrategy(
+ new RandomServerSelectorStrategy(),
+ new PooledTierSelectorStrategyConfig(Set.of(2, 0, -1))
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
Review Comment:
## Deprecated method or constructor invocation
Invoking [DataSegment.DataSegment](1) should be avoided because it has been deprecated.
[Show more details](https://github.com/apache/druid/security/code-scanning/10857)
##########
server/src/test/java/org/apache/druid/client/selector/TierSelectorStrategyTest.java:
##########
@@ -568,4 +586,366 @@
preferredTierHighPriority, preferredTierLowPriority, nonPreferredTierHighestPriority
);
}
+
+ @Test
+ public void testStrictTierSelectorStrategyAllConfigured()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ testTierSelectorStrategy(
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(2, 0, -1)),
+ serviceEmitter
+ ),
+ p2, p0, pNeg1
+ );
+ }
+
+ @Test
+ public void testStrictTierSelectorStrategyNoMatchingPriorities()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 1),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(5, 6)),
+ serviceEmitter
+ ),
+ HistoricalFilter.IDENTITY_FILTER
+ );
+
+ List<QueryableDruidServer> servers = List.of(p0, p1, p2);
+ for (QueryableDruidServer server : servers) {
+ serverSelector.addServerAndUpdateSegment(server, serverSelector.getSegment());
+ }
+
+ // Should return null when no matching priorities
+ Assert.assertNull(serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES));
+ Assert.assertNull(serverSelector.pick(GroupByQuery.builder()
+ .setDataSource("foo3")
+ .setInterval(new MultipleIntervalSegmentSpec(ImmutableList.of(Intervals.of("2000/3000"))))
+ .setGranularity(Granularities.ALL)
+ .setDimensions(new DefaultDimensionSpec("dim2", "d0"))
+ .build(), CloneQueryMode.EXCLUDECLONES));
+
+ serviceEmitter.verifyEmitted("tierSelector/noServer", 1);
+
+ // Should return empty list for getCandidates
+ Assert.assertEquals(Collections.emptyList(), serverSelector.getCandidates(2, CloneQueryMode.EXCLUDECLONES));
+ }
+
+ @Test
+ public void testEmptyStrictTierPrioritiesThrowsException()
+ {
+ DruidException ex = Assert.assertThrows(
+ DruidException.class,
+ () -> new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of()),
+ serviceEmitter
+ )
+ );
+ Assert.assertEquals(
+ "priorities must be non-empty when using strict tier selector on the Broker. Found priorities[[]].",
+ ex.getMessage()
+ );
+ }
+
+ @Test
+ public void testPooledTierSelectorWithRandomServerStrategy()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ TierSelectorStrategy strategy = new PooledTierSelectorStrategy(
+ new RandomServerSelectorStrategy(),
+ new PooledTierSelectorStrategyConfig(Set.of(2, 0, -1))
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
+ strategy,
+ HistoricalFilter.IDENTITY_FILTER
+ );
+
+ List<QueryableDruidServer> servers = List.of(pNeg1, p0, p2);
+ for (QueryableDruidServer server : servers) {
+ serverSelector.addServerAndUpdateSegment(server, serverSelector.getSegment());
+ }
+
+ // All 3 servers should be configured
+ List<DruidServerMetadata> allServers = serverSelector.getAllServers(CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(3, allServers.size());
+ Set<Integer> priorities = allServers.stream()
+ .map(DruidServerMetadata::getPriority)
+ .collect(Collectors.toSet());
+ Assert.assertEquals(Set.of(-1, 0, 2), priorities);
+
+ // Test getCandidates with different sizes - verify correct count
+ List<DruidServerMetadata> candidates1 = serverSelector.getCandidates(1, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(1, candidates1.size());
+ Assert.assertTrue(Set.of(-1, 0, 2).contains(candidates1.get(0).getPriority()));
+
+ List<DruidServerMetadata> candidates2 = serverSelector.getCandidates(2, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(2, candidates2.size());
+
+ List<DruidServerMetadata> candidates3 = serverSelector.getCandidates(3, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(3, candidates3.size());
+ Set<Integer> candidates3Priorities = candidates3.stream()
+ .map(DruidServerMetadata::getPriority)
+ .collect(Collectors.toSet());
+ Assert.assertEquals(Set.of(-1, 0, 2), candidates3Priorities);
+
+ // Pick should return one of the three servers (any priority - demonstrates flattening)
+ QueryableDruidServer picked = serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertNotNull(picked);
+ Assert.assertTrue(
+ "Picked server should have one of the configured priorities",
+ picked.getServer().getPriority() == -1 ||
+ picked.getServer().getPriority() == 0 ||
+ picked.getServer().getPriority() == 2
+ );
+
+ // Pick multiple times to verify servers from flattened pool are accessible
+ Set<Integer> pickedPriorities = new HashSet<>();
+ for (int i = 0; i < 20; i++) {
+ QueryableDruidServer server = serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertNotNull(server);
+ pickedPriorities.add(server.getServer().getPriority());
+ }
+ // With RandomServerSelectorStrategy and 20 picks from 3 servers, we should see multiple priorities
+ Assert.assertTrue(
+ "Expected to see servers from multiple priorities, but only saw: " + pickedPriorities,
+ pickedPriorities.size() >= 2
+ );
+ }
+
+ @Test
+ public void testPooledTierSelectorWithConnectionCountServerStrategy()
+ {
+ AtomicInteger c0 = new AtomicInteger(20);
+ AtomicInteger c1 = new AtomicInteger(25);
+ AtomicInteger c2a = new AtomicInteger(1);
+ AtomicInteger c2b = new AtomicInteger(2);
+
+ DirectDruidClient client0 = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client0.getNumOpenConnections()).andAnswer(c0::get).anyTimes();
+
+ DirectDruidClient client1 = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client1.getNumOpenConnections()).andAnswer(c1::get).anyTimes();
+
+ DirectDruidClient client2a = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client2a.getNumOpenConnections()).andAnswer(c2a::get).anyTimes();
+
+ DirectDruidClient client2b = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client2b.getNumOpenConnections()).andAnswer(c2b::get).anyTimes();
+
+ EasyMock.replay(client0, client1, client2a, client2b);
+
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("p0", "localhost:8001", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 0),
+ client0
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("p1", "localhost:8002", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 1),
+ client1
+ );
+ QueryableDruidServer p2a = new QueryableDruidServer(
+ new DruidServer("p2a", "localhost:8003", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client2a
+ );
+ QueryableDruidServer p2b = new QueryableDruidServer(
+ new DruidServer("p2b", "localhost:8004", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client2b
+ );
+
+ TierSelectorStrategy strategy = new PooledTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new PooledTierSelectorStrategyConfig(Set.of(0, 1, 2))
+ );
+
+ ServerSelector selector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
Review Comment:
## Deprecated method or constructor invocation
Invoking [DataSegment.DataSegment](1) should be avoided because it has been deprecated.
[Show more details](https://github.com/apache/druid/security/code-scanning/10858)
##########
server/src/test/java/org/apache/druid/client/selector/TierSelectorStrategyTest.java:
##########
@@ -568,4 +586,366 @@
preferredTierHighPriority, preferredTierLowPriority, nonPreferredTierHighestPriority
);
}
+
+ @Test
+ public void testStrictTierSelectorStrategyAllConfigured()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ testTierSelectorStrategy(
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(2, 0, -1)),
+ serviceEmitter
+ ),
+ p2, p0, pNeg1
+ );
+ }
+
+ @Test
+ public void testStrictTierSelectorStrategyNoMatchingPriorities()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 1),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
+ new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of(5, 6)),
+ serviceEmitter
+ ),
+ HistoricalFilter.IDENTITY_FILTER
+ );
+
+ List<QueryableDruidServer> servers = List.of(p0, p1, p2);
+ for (QueryableDruidServer server : servers) {
+ serverSelector.addServerAndUpdateSegment(server, serverSelector.getSegment());
+ }
+
+ // Should return null when no matching priorities
+ Assert.assertNull(serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES));
+ Assert.assertNull(serverSelector.pick(GroupByQuery.builder()
+ .setDataSource("foo3")
+ .setInterval(new MultipleIntervalSegmentSpec(ImmutableList.of(Intervals.of("2000/3000"))))
+ .setGranularity(Granularities.ALL)
+ .setDimensions(new DefaultDimensionSpec("dim2", "d0"))
+ .build(), CloneQueryMode.EXCLUDECLONES));
+
+ serviceEmitter.verifyEmitted("tierSelector/noServer", 1);
+
+ // Should return empty list for getCandidates
+ Assert.assertEquals(Collections.emptyList(), serverSelector.getCandidates(2, CloneQueryMode.EXCLUDECLONES));
+ }
+
+ @Test
+ public void testEmptyStrictTierPrioritiesThrowsException()
+ {
+ DruidException ex = Assert.assertThrows(
+ DruidException.class,
+ () -> new StrictTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new StrictTierSelectorStrategyConfig(List.of()),
+ serviceEmitter
+ )
+ );
+ Assert.assertEquals(
+ "priorities must be non-empty when using strict tier selector on the Broker. Found priorities[[]].",
+ ex.getMessage()
+ );
+ }
+
+ @Test
+ public void testPooledTierSelectorWithRandomServerStrategy()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer pNeg1 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, -1),
+ client
+ );
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ TierSelectorStrategy strategy = new PooledTierSelectorStrategy(
+ new RandomServerSelectorStrategy(),
+ new PooledTierSelectorStrategyConfig(Set.of(2, 0, -1))
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
+ strategy,
+ HistoricalFilter.IDENTITY_FILTER
+ );
+
+ List<QueryableDruidServer> servers = List.of(pNeg1, p0, p2);
+ for (QueryableDruidServer server : servers) {
+ serverSelector.addServerAndUpdateSegment(server, serverSelector.getSegment());
+ }
+
+ // All 3 servers should be configured
+ List<DruidServerMetadata> allServers = serverSelector.getAllServers(CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(3, allServers.size());
+ Set<Integer> priorities = allServers.stream()
+ .map(DruidServerMetadata::getPriority)
+ .collect(Collectors.toSet());
+ Assert.assertEquals(Set.of(-1, 0, 2), priorities);
+
+ // Test getCandidates with different sizes - verify correct count
+ List<DruidServerMetadata> candidates1 = serverSelector.getCandidates(1, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(1, candidates1.size());
+ Assert.assertTrue(Set.of(-1, 0, 2).contains(candidates1.get(0).getPriority()));
+
+ List<DruidServerMetadata> candidates2 = serverSelector.getCandidates(2, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(2, candidates2.size());
+
+ List<DruidServerMetadata> candidates3 = serverSelector.getCandidates(3, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertEquals(3, candidates3.size());
+ Set<Integer> candidates3Priorities = candidates3.stream()
+ .map(DruidServerMetadata::getPriority)
+ .collect(Collectors.toSet());
+ Assert.assertEquals(Set.of(-1, 0, 2), candidates3Priorities);
+
+ // Pick should return one of the three servers (any priority - demonstrates flattening)
+ QueryableDruidServer picked = serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertNotNull(picked);
+ Assert.assertTrue(
+ "Picked server should have one of the configured priorities",
+ picked.getServer().getPriority() == -1 ||
+ picked.getServer().getPriority() == 0 ||
+ picked.getServer().getPriority() == 2
+ );
+
+ // Pick multiple times to verify servers from flattened pool are accessible
+ Set<Integer> pickedPriorities = new HashSet<>();
+ for (int i = 0; i < 20; i++) {
+ QueryableDruidServer server = serverSelector.pick(null, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertNotNull(server);
+ pickedPriorities.add(server.getServer().getPriority());
+ }
+ // With RandomServerSelectorStrategy and 20 picks from 3 servers, we should see multiple priorities
+ Assert.assertTrue(
+ "Expected to see servers from multiple priorities, but only saw: " + pickedPriorities,
+ pickedPriorities.size() >= 2
+ );
+ }
+
+ @Test
+ public void testPooledTierSelectorWithConnectionCountServerStrategy()
+ {
+ AtomicInteger c0 = new AtomicInteger(20);
+ AtomicInteger c1 = new AtomicInteger(25);
+ AtomicInteger c2a = new AtomicInteger(1);
+ AtomicInteger c2b = new AtomicInteger(2);
+
+ DirectDruidClient client0 = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client0.getNumOpenConnections()).andAnswer(c0::get).anyTimes();
+
+ DirectDruidClient client1 = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client1.getNumOpenConnections()).andAnswer(c1::get).anyTimes();
+
+ DirectDruidClient client2a = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client2a.getNumOpenConnections()).andAnswer(c2a::get).anyTimes();
+
+ DirectDruidClient client2b = EasyMock.createMock(DirectDruidClient.class);
+ EasyMock.expect(client2b.getNumOpenConnections()).andAnswer(c2b::get).anyTimes();
+
+ EasyMock.replay(client0, client1, client2a, client2b);
+
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("p0", "localhost:8001", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 0),
+ client0
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("p1", "localhost:8002", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 1),
+ client1
+ );
+ QueryableDruidServer p2a = new QueryableDruidServer(
+ new DruidServer("p2a", "localhost:8003", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client2a
+ );
+ QueryableDruidServer p2b = new QueryableDruidServer(
+ new DruidServer("p2b", "localhost:8004", null, 0, null, ServerType.INDEXER_EXECUTOR, DruidServer.DEFAULT_TIER, 2),
+ client2b
+ );
+
+ TierSelectorStrategy strategy = new PooledTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new PooledTierSelectorStrategyConfig(Set.of(0, 1, 2))
+ );
+
+ ServerSelector selector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
+ strategy,
+ HistoricalFilter.IDENTITY_FILTER
+ );
+
+ for (QueryableDruidServer s : List.of(p0, p1, p2a, p2b)) {
+ selector.addServerAndUpdateSegment(s, selector.getSegment());
+ }
+
+ for (int i = 0; i < 20; i++) {
+ QueryableDruidServer picked = selector.pick(null, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertNotNull(picked);
+
+ // Should always be one of the priority-2 replicas
+ Assert.assertEquals(2, picked.getServer().getPriority());
+
+ if (picked.getServer().equals(p2a.getServer())) {
+ c2a.incrementAndGet();
+ } else if (picked.getServer().equals(p2b.getServer())) {
+ c2b.incrementAndGet();
+ } else {
+ Assert.fail("Expected pick to be either p2a or p2b but got: " + picked);
+ }
+ }
+
+ // Now we just verify that all the servers are picked at least once
+ final Set<DruidServer> pickedServers = new HashSet<>();
+ for (int i = 0; i < 50; i++) {
+ QueryableDruidServer picked = selector.pick(null, CloneQueryMode.EXCLUDECLONES);
+ Assert.assertNotNull(picked);
+
+ DruidServer pickedServer = picked.getServer();
+ pickedServers.add(pickedServer);
+
+ if (pickedServer.equals(p0.getServer())) {
+ c0.incrementAndGet();
+ } else if (pickedServer.equals(p1.getServer())) {
+ c1.incrementAndGet();
+ } else if (pickedServer.equals(p2a.getServer())) {
+ c2a.incrementAndGet();
+ } else {
+ c2b.incrementAndGet();
+ }
+ }
+
+ Assert.assertEquals(4, pickedServers.size());
+
+ EasyMock.verify(client0, client1, client2a, client2b);
+ }
+
+ @Test
+ public void testPooledTierSelectorStrategyWithNoServerCandidatesInPool()
+ {
+ DirectDruidClient client = EasyMock.createMock(DirectDruidClient.class);
+ QueryableDruidServer p0 = new QueryableDruidServer(
+ new DruidServer("test1", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 0),
+ client
+ );
+ QueryableDruidServer p1 = new QueryableDruidServer(
+ new DruidServer("test2", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 1),
+ client
+ );
+ QueryableDruidServer p2 = new QueryableDruidServer(
+ new DruidServer("test3", "localhost", null, 0, null, ServerType.HISTORICAL, DruidServer.DEFAULT_TIER, 2),
+ client
+ );
+
+ TierSelectorStrategy strategy = new PooledTierSelectorStrategy(
+ new ConnectionCountServerSelectorStrategy(),
+ new PooledTierSelectorStrategyConfig(Set.of(5, 6))
+ );
+
+ final ServerSelector serverSelector = new ServerSelector(
+ new DataSegment(
+ "test",
+ Intervals.of("2013-01-01/2013-01-02"),
+ DateTimes.of("2013-01-01").toString(),
+ new HashMap<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ NoneShardSpec.instance(),
+ 0,
+ 0L
+ ),
Review Comment:
## Deprecated method or constructor invocation
Invoking [DataSegment.DataSegment](1) should be avoided because it has been deprecated.
[Show more details](https://github.com/apache/druid/security/code-scanning/10859)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "aho135 (via GitHub)" <gi...@apache.org>.
aho135 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2908713839
##########
server/src/main/java/org/apache/druid/client/selector/PooledTierSelectorStrategy.java:
##########
@@ -0,0 +1,158 @@
+/*
+ * 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.selector;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
+import org.apache.druid.client.QueryableDruidServer;
+import org.apache.druid.java.util.emitter.EmittingLogger;
+import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.metadata.metadata.SegmentMetadataQuery;
+import org.apache.druid.timeline.DataSegment;
+
+import javax.annotation.Nullable;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link TierSelectorStrategy} that pools servers with the configured set of priorities from {@link PooledTierSelectorStrategyConfig#getPriorities()}
+ * and delegates server selection to the configured {@link ServerSelectorStrategy}.
+ * <p>
+ * Unlike other {@link TierSelectorStrategy} like {@link CustomTierSelectorStrategy}
+ * which has a preference for priority order, this strategy treats all configured priorities equally
+ * by combining their servers into a single selection pool and delegates to {@link ServerSelectorStrategy} to do
+ * the server selection. If no servers match the configured priorities in the pool, an empty server list is returned,
+ * which may cause queries to return partial or no data.
+ * <p>
+ * Example configuration:
+ * <li> <code> druid.broker.select.tier=pooled </code> </li>
+ * <li> <code> druid.broker.select.tier.pooled.priorities=[2,1] </code> </li>
+ * <p>
+ * With this configuration, servers with priority 2 and 1 are pooled together and
+ * selection is delegated to the {@link ServerSelectorStrategy}. Servers with other
+ * priorities are ignored.
+ */
+public class PooledTierSelectorStrategy extends AbstractTierSelectorStrategy
+{
+ private static final EmittingLogger log = new EmittingLogger(PooledTierSelectorStrategy.class);
+ public static final String TYPE = "pooled";
+
+ private final ServerSelectorStrategy serverSelectorStrategy;
+ private final PooledTierSelectorStrategyConfig config;
+ private final ServiceEmitter emitter;
+ private final Set<Integer> configuredPriorities;
+
+ @JsonCreator
+ public PooledTierSelectorStrategy(
+ @JacksonInject final ServerSelectorStrategy serverSelectorStrategy,
Review Comment:
nit: We could make ServerSelectorStrategy in AbstractTierSelectorStrategy `protected`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "abhishekrb19 (via GitHub)" <gi...@apache.org>.
abhishekrb19 commented on PR #19094:
URL: https://github.com/apache/druid/pull/19094#issuecomment-4033333962
Thanks for the review @aho135! The Docker test failures `test_runIndexTask_andKillData ` and `test_runIndexParallelTask_andCompactData` are unrelated to this patch (the first one is a usual suspect)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "aho135 (via GitHub)" <gi...@apache.org>.
aho135 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2908695095
##########
server/src/main/java/org/apache/druid/client/selector/PooledTierSelectorStrategy.java:
##########
@@ -0,0 +1,158 @@
+/*
+ * 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.selector;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
+import org.apache.druid.client.QueryableDruidServer;
+import org.apache.druid.java.util.emitter.EmittingLogger;
+import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.metadata.metadata.SegmentMetadataQuery;
+import org.apache.druid.timeline.DataSegment;
+
+import javax.annotation.Nullable;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link TierSelectorStrategy} that pools servers with the configured set of priorities from {@link PooledTierSelectorStrategyConfig#getPriorities()}
+ * and delegates server selection to the configured {@link ServerSelectorStrategy}.
+ * <p>
+ * Unlike other {@link TierSelectorStrategy} like {@link CustomTierSelectorStrategy}
+ * which has a preference for priority order, this strategy treats all configured priorities equally
+ * by combining their servers into a single selection pool and delegates to {@link ServerSelectorStrategy} to do
+ * the server selection. If no servers match the configured priorities in the pool, an empty server list is returned,
+ * which may cause queries to return partial or no data.
+ * <p>
+ * Example configuration:
+ * <li> <code> druid.broker.select.tier=pooled </code> </li>
+ * <li> <code> druid.broker.select.tier.pooled.priorities=[2,1] </code> </li>
+ * <p>
+ * With this configuration, servers with priority 2 and 1 are pooled together and
+ * selection is delegated to the {@link ServerSelectorStrategy}. Servers with other
+ * priorities are ignored.
+ */
+public class PooledTierSelectorStrategy extends AbstractTierSelectorStrategy
+{
+ private static final EmittingLogger log = new EmittingLogger(PooledTierSelectorStrategy.class);
+ public static final String TYPE = "pooled";
+
+ private final ServerSelectorStrategy serverSelectorStrategy;
+ private final PooledTierSelectorStrategyConfig config;
+ private final ServiceEmitter emitter;
+ private final Set<Integer> configuredPriorities;
+
+ @JsonCreator
+ public PooledTierSelectorStrategy(
+ @JacksonInject final ServerSelectorStrategy serverSelectorStrategy,
+ @JacksonInject final PooledTierSelectorStrategyConfig config,
+ @JacksonInject final ServiceEmitter emitter
+ )
+ {
+ super(serverSelectorStrategy);
+ this.serverSelectorStrategy = serverSelectorStrategy;
+ this.config = config;
+ this.emitter = emitter;
+ this.configuredPriorities = config.getPriorities();
+ }
+
+ @Override
+ public <T> List<QueryableDruidServer> pick(
+ @Nullable final Query<T> query,
+ final Int2ObjectRBTreeMap<Set<QueryableDruidServer>> prioritizedServers,
+ final DataSegment segment,
+ final int numServersToPick
+ )
+ {
+ final Set<QueryableDruidServer> candidateServerPool = new LinkedHashSet<>();
+
+ for (Int2ObjectMap.Entry<Set<QueryableDruidServer>> entry : prioritizedServers.int2ObjectEntrySet()) {
+ final int priority = entry.getIntKey();
+ final Set<QueryableDruidServer> servers = entry.getValue();
+
+ if (configuredPriorities.contains(priority)) {
+ candidateServerPool.addAll(servers);
+ } else {
+ log.debug(
+ "Server priority[%d] not in the configured list of priorities[%s] so ignore servers[%s] for query[%s]",
+ priority, config.getPriorities(), servers, query
+ );
+ }
+ }
+
+ if (candidateServerPool.isEmpty()) {
+ if (query == null || query instanceof SegmentMetadataQuery) {
+ // Debug logging to reduce logging spam as these are typically system-generated segment metadata queries
+ log.debug(
+ "No server found for query[%s] from server priorities[%s]. Configured priorities[%s].",
+ query, prioritizedServers.keySet(), config.getPriorities()
+ );
+ } else {
+ log.warn(
+ "No servers found for query[%s] matching configured priorities[%s]. Available priorities[%s].",
+ query, config.getPriorities(), prioritizedServers.keySet()
+ );
+ emitter.emit(
+ ServiceMetricEvent.builder()
+ .setMetric("tierSelector/noServer", 1)
+ .setDimension("dataSource", String.valueOf(query.getDataSource()))
+ .setDimension("tierSelectorType", TYPE)
+ .setDimension("queryType", query.getType())
+ .setDimension("queryPriority", String.valueOf(query.context().getPriority()))
+ .setDimensionIfNotNull("queryId", query.getId())
+ );
+ }
+ return List.of();
+ }
+
+ final List<QueryableDruidServer> selectedServers = serverSelectorStrategy.pick(query, candidateServerPool, segment, numServersToPick);
+ log.debug("Selected servers[%s] for query[%s] from given servers[%s] and candidateServerPool[%s]", selectedServers, query, prioritizedServers, candidateServerPool);
+ return selectedServers;
+ }
+
+ /**
+ * @return the natural order of priorities since priority order doesn't matter for this strategy as the configured set of
+ * priorities in the pool are treated equally and delegated ot {@link #serverSelectorStrategy}.
Review Comment:
```suggestion
* priorities in the pool are treated equally and delegated to {@link #serverSelectorStrategy}.
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "aho135 (via GitHub)" <gi...@apache.org>.
aho135 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2908799961
##########
server/src/test/java/org/apache/druid/client/selector/TierSelectorStrategyTest.java:
##########
@@ -568,4 +594,326 @@ public void testPreferredTierSelectorStrategyDefaultPriority()
preferredTierHighPriority, preferredTierLowPriority, nonPreferredTierHighestPriority
);
}
+
Review Comment:
Maybe one test case where we have partial priority match. e.g. Servers with priority [-1, 0, 2] but StrictTierSelectorStrategy configured with [0,1]
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "abhishekrb19 (via GitHub)" <gi...@apache.org>.
abhishekrb19 merged PR #19094:
URL: https://github.com/apache/druid/pull/19094
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "aho135 (via GitHub)" <gi...@apache.org>.
aho135 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2908783421
##########
server/src/main/java/org/apache/druid/client/selector/StrictTierSelectorStrategy.java:
##########
@@ -0,0 +1,183 @@
+/*
+ * 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.selector;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
+import org.apache.druid.client.QueryableDruidServer;
+import org.apache.druid.java.util.emitter.EmittingLogger;
+import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.metadata.metadata.SegmentMetadataQuery;
+import org.apache.druid.timeline.DataSegment;
+
+import javax.annotation.Nullable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link TierSelectorStrategy} that only considers servers whose priorities
+ * are explicitly listed in {@link StrictTierSelectorStrategyConfig#getPriorities()}.
+ * <p>
+ * Unlike other strategies like {@link CustomTierSelectorStrategy} that falls back to servers with different priorities,
+ * this strategy strictly filters the available servers to the configured priorities.
+ * If no servers match the configured priorities, an empty server list is returned from {@link #pick(Query, Int2ObjectRBTreeMap, DataSegment, int)},
+ * which may cause queries to return partial or no data.
+ * <p>
+ * When multiple priorities are configured, they are evaluated in the order specified, with earlier priorities
+ * preferred over later ones.
+ * <p>
+ * Example configuration:
+ * <li> <code> druid.broker.select.tier=strict </code> </li>
+ * <li> <code> druid.broker.select.tier.strict.priorities=[2,1] </code> </li>
+ * <p>
+ * With this configuration, servers with priority 2 are preferred over servers with priority 1.
+ * Servers with any other tier priority are not considered.
+ * <p>
+ * This strategy is useful when query isolation is required between different server priority tiers. Brokers may still
+ * be configured to watch all server tiers, allowing them to retain visibility into the overall cluster state
+ * while enforcing isolation at query time.
+ */
+public class StrictTierSelectorStrategy extends AbstractTierSelectorStrategy
+{
+ private static final EmittingLogger log = new EmittingLogger(StrictTierSelectorStrategy.class);
+ public static final String TYPE = "strict";
+
+ private final StrictTierSelectorStrategyConfig config;
+ private final ServiceEmitter emitter;
+ private final Map<Integer, Integer> configuredPriorities;
+ private final Comparator<Integer> comparator;
+
+ @JsonCreator
+ public StrictTierSelectorStrategy(
+ @JacksonInject final ServerSelectorStrategy serverSelectorStrategy,
+ @JacksonInject final StrictTierSelectorStrategyConfig config,
+ @JacksonInject final ServiceEmitter emitter
+ )
+ {
+ super(serverSelectorStrategy);
+ this.config = config;
+ this.emitter = emitter;
+
+ configuredPriorities = new HashMap<>();
+ for (int i = 0; i < config.getPriorities().size(); i++) {
+ configuredPriorities.put(config.getPriorities().get(i), i);
+ }
+
+ this.comparator = (p1, p2) -> {
+ final Integer rank1 = configuredPriorities.get(p1);
+ final Integer rank2 = configuredPriorities.get(p2);
+
+ if (rank1 != null && rank2 != null) {
+ return Integer.compare(rank1, rank2);
+ }
+ if (rank1 != null) {
+ return -1;
+ }
+ if (rank2 != null) {
+ return 1;
+ }
+
+ // Priorities outside configuredPriorities don't matter and won't be selected in pick() for this strategy.
+ // This fallback ordering can be anything and is needed because the comparator may be used by ServerSelector/ServerView
+ // which maintains views of all servers, including those with unconfigured priorities.
+ return Integer.compare(p2, p1);
+ };
+ }
+
+ @Override
+ public <T> List<QueryableDruidServer> pick(
+ @Nullable final Query<T> query,
+ final Int2ObjectRBTreeMap<Set<QueryableDruidServer>> prioritizedServers,
+ final DataSegment segment,
+ final int numServersToPick
+ )
+ {
+ final Int2ObjectRBTreeMap<Set<QueryableDruidServer>> candidatePrioritizedServers = new Int2ObjectRBTreeMap<>(getComparator());
+
+ for (Int2ObjectMap.Entry<Set<QueryableDruidServer>> entry : prioritizedServers.int2ObjectEntrySet()) {
+ final int priority = entry.getIntKey();
+ final Set<QueryableDruidServer> servers = entry.getValue();
+
+ if (configuredPriorities.containsKey(priority)) {
+ candidatePrioritizedServers.put(priority, servers);
+ } else {
+ log.debug(
+ "Server priority[%d] not in the configured list of priorities[%s] so ignore servers[%s] for query[%s]",
+ priority, config.getPriorities(), servers, query
+ );
+ }
+ }
+
+ if (candidatePrioritizedServers.isEmpty()) {
+ if (query == null || query instanceof SegmentMetadataQuery) {
Review Comment:
Would we want to include dataSourceMetadata queries here as well?
Maybe a small helper function in Query: isMetadataQuery()?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "aho135 (via GitHub)" <gi...@apache.org>.
aho135 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2908728911
##########
server/src/main/java/org/apache/druid/client/selector/StrictTierSelectorStrategy.java:
##########
@@ -0,0 +1,183 @@
+/*
+ * 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.selector;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
+import org.apache.druid.client.QueryableDruidServer;
+import org.apache.druid.java.util.emitter.EmittingLogger;
+import org.apache.druid.java.util.emitter.service.ServiceEmitter;
+import org.apache.druid.java.util.emitter.service.ServiceMetricEvent;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.metadata.metadata.SegmentMetadataQuery;
+import org.apache.druid.timeline.DataSegment;
+
+import javax.annotation.Nullable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link TierSelectorStrategy} that only considers servers whose priorities
+ * are explicitly listed in {@link StrictTierSelectorStrategyConfig#getPriorities()}.
+ * <p>
+ * Unlike other strategies like {@link CustomTierSelectorStrategy} that falls back to servers with different priorities,
Review Comment:
```suggestion
* Unlike other strategies like {@link CustomTierSelectorStrategy} that fall back to servers with different priorities,
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] Add `strict` and `pooled` Broker tier selector strategies (druid)
Posted by "abhishekrb19 (via GitHub)" <gi...@apache.org>.
abhishekrb19 commented on code in PR #19094:
URL: https://github.com/apache/druid/pull/19094#discussion_r2909167971
##########
server/src/test/java/org/apache/druid/client/selector/TierSelectorStrategyTest.java:
##########
@@ -568,4 +594,326 @@ public void testPreferredTierSelectorStrategyDefaultPriority()
preferredTierHighPriority, preferredTierLowPriority, nonPreferredTierHighestPriority
);
}
+
Review Comment:
Thanks, good call! Added a case for these mixed priorities.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org