You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by "mshahid6 (via GitHub)" <gi...@apache.org> on 2026/03/27 23:25:20 UTC
[PR] feat: Weighted Query Laning Strategy (druid)
mshahid6 opened a new pull request, #19225:
URL: https://github.com/apache/druid/pull/19225
### Description
Adds a new WeightedQueryLaningStrategy (strategy: "weighted") that scores queries by how many configured thresholds they breach and assigns them to graduated lanes with different capacity limits.
The existing HiLoQueryLaningStrategy uses a simple binary high/low split — any single threshold breach sends a query to the low lane. This misclassifies queries that are expensive on one dimension but cheap on others (e.g., many segments but cheap per-segment). The weighted strategy provides more nuanced lane assignment: a query breaching 1 threshold gets a different lane than one breaching 4.
Same threshold types as ThresholdBasedQueryPrioritizationStrategy:
- periodThreshold — how far in the past the data is
- durationThreshold — how large the query interval is
- segmentCountThreshold — number of segments involved
- segmentRangeThreshold — total segment time range
Each breach adds 1 to the query's score (this can be changed to make scoring different for each threshold). The query is assigned to the lane with the highest minScore it meets. Score 0 = no lane (runs in default pool). Existing caller-set lanes in query context are preserved.
**Example config**
{
"laning": {
"strategy": "weighted",
"periodThreshold": "P1M",
"durationThreshold": "P1D",
"segmentCountThreshold": 1000,
"segmentRangeThreshold": "P6M",
"lanes": {
"low": { "minScore": 1, "maxPercent": 30 },
"very-low": { "minScore": 3, "maxPercent": 10 }
}
}
}
A query breaching 2 thresholds (score=2) goes to low (30% capacity cap). A query breaching all 4 (score=4) goes to very-low (10% cap). When a lane is full, excess queries get HTTP 429.
#### Release note
New query laning strategy weighted that scores queries by how many thresholds they breach (segment count, interval duration, data age, segment range) and assigns them to configurable graduated lanes with different capacity limits, providing more nuanced lane assignment than the existing binary hilo strategy.
<hr>
##### Key changed/added classes in this PR
- WeightedQueryLaningStrategy
- QueryLaningStrategy
- WeightedQueryLaningStrategyTest
- QuerySchedulerTest
[can add to docs once reviewed]
This PR has:
- [ ] been self-reviewed.
- [ ] using the [concurrency checklist](https://github.com/apache/druid/blob/master/dev/code-review/concurrency.md) (Remove this item if the PR doesn't have any relation to concurrency.)
- [ ] added documentation for new or modified features or behaviors.
- [ ] a release note entry in the PR description.
- [ ] added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
- [ ] added or updated version, license, or notice information in [licenses.yaml](https://github.com/apache/druid/blob/master/dev/license.md)
- [ ] added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
- [ ] 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.
- [ ] added integration tests.
- [ ] 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] feat: Weighted Query Laning Strategy (druid)
Posted by "FrankChen021 (via GitHub)" <gi...@apache.org>.
FrankChen021 commented on code in PR #19225:
URL: https://github.com/apache/druid/pull/19225#discussion_r3141492745
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,227 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
+ "At least one of periodThreshold, durationThreshold, segmentCountThreshold, or segmentRangeThreshold must be set"
+ );
+ Preconditions.checkArgument(
+ lanes != null && !lanes.isEmpty(),
Review Comment:
[P2] Reject reserved lane names
Unlike ManualQueryLaningStrategy, this constructor accepts lanes named total or default. total collides with QueryScheduler's global bulkhead key and can overwrite the total capacity config; if a query is then assigned to lane total, acquireLanes tries to acquire the same bulkhead as both lane and total, consuming two permits or failing unexpectedly. default is also explicitly reserved for the scheduler bulkhead behavior. Please reject these reserved names before storing the lanes.
--
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] feat: Weighted Query Laning Strategy (druid)
Posted by "mshahid6 (via GitHub)" <gi...@apache.org>.
mshahid6 commented on code in PR #19225:
URL: https://github.com/apache/druid/pull/19225#discussion_r3290093788
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,236 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.apache.druid.server.QueryScheduler;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
+ "At least one of periodThreshold, durationThreshold, segmentCountThreshold, or segmentRangeThreshold must be set"
+ );
+ Preconditions.checkArgument(
+ lanes != null && !lanes.isEmpty(),
+ "At least one lane must be defined"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch(QueryScheduler.TOTAL::equals),
+ "Lane cannot be named 'total'"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch("default"::equals),
+ "Lane cannot be named 'default'"
+ );
+
+ this.segmentCountThreshold = segmentCountThreshold == null ? DEFAULT_SEGMENT_THRESHOLD : segmentCountThreshold;
+ this.periodThreshold = periodThreshold == null ? null : new Period(periodThreshold);
+ this.durationThreshold = durationThreshold == null
+ ? null
+ : new Period(durationThreshold).toStandardDuration();
+ this.segmentRangeThreshold = segmentRangeThreshold == null
+ ? null
+ : new Period(segmentRangeThreshold).toStandardDuration();
+ this.lanes = lanes;
+ }
+
+ @Override
+ public Object2IntMap<String> getLaneLimits(int totalLimit)
+ {
+ Object2IntMap<String> limits = new Object2IntArrayMap<>(lanes.size());
+ for (Map.Entry<String, LaneConfig> entry : lanes.entrySet()) {
+ limits.put(entry.getKey(), computeLimitFromPercent(totalLimit, entry.getValue().maxPercent));
+ }
+ return limits;
+ }
+
+ @Override
+ public <T> Optional<String> computeLane(QueryPlus<T> query, Set<SegmentServerSelector> segments)
+ {
+ final String existingLane = query.getQuery().context().getLane();
+ if (existingLane != null) {
+ return Optional.of(existingLane);
+ }
+
+ int score = computeScore(query.getQuery(), segments);
+ if (score == 0) {
+ return Optional.empty();
+ }
+
+ // Find the lane with the highest minScore that this query meets
+ String bestLane = null;
+ int bestMinScore = 0;
+ for (Map.Entry<String, LaneConfig> entry : lanes.entrySet()) {
+ int minScore = entry.getValue().minScore;
+ if (score >= minScore && minScore > bestMinScore) {
+ bestLane = entry.getKey();
+ bestMinScore = minScore;
+ }
+ }
+ return Optional.ofNullable(bestLane);
+ }
+
+ private <T> int computeScore(Query<T> query, Set<SegmentServerSelector> segments)
Review Comment:
SG
--
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] feat: Weighted Query Laning Strategy (druid)
Posted by "jtuglu1 (via GitHub)" <gi...@apache.org>.
jtuglu1 merged PR #19225:
URL: https://github.com/apache/druid/pull/19225
--
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] feat: Weighted Query Laning Strategy (druid)
Posted by "jtuglu1 (via GitHub)" <gi...@apache.org>.
jtuglu1 commented on code in PR #19225:
URL: https://github.com/apache/druid/pull/19225#discussion_r3132258755
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,227 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
Review Comment:
maybe also do some sanity checks on the integer/string inputs? e.g. non-null, non-negative, etc.
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,236 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.apache.druid.server.QueryScheduler;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
Review Comment:
nit: can we uses Configs.valueOrDefault for all these null comparisons where possible?
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,236 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.apache.druid.server.QueryScheduler;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
+ "At least one of periodThreshold, durationThreshold, segmentCountThreshold, or segmentRangeThreshold must be set"
+ );
+ Preconditions.checkArgument(
+ lanes != null && !lanes.isEmpty(),
+ "At least one lane must be defined"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch(QueryScheduler.TOTAL::equals),
+ "Lane cannot be named 'total'"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch("default"::equals),
+ "Lane cannot be named 'default'"
+ );
+
+ this.segmentCountThreshold = segmentCountThreshold == null ? DEFAULT_SEGMENT_THRESHOLD : segmentCountThreshold;
+ this.periodThreshold = periodThreshold == null ? null : new Period(periodThreshold);
+ this.durationThreshold = durationThreshold == null
+ ? null
+ : new Period(durationThreshold).toStandardDuration();
+ this.segmentRangeThreshold = segmentRangeThreshold == null
+ ? null
+ : new Period(segmentRangeThreshold).toStandardDuration();
+ this.lanes = lanes;
+ }
+
+ @Override
+ public Object2IntMap<String> getLaneLimits(int totalLimit)
+ {
+ Object2IntMap<String> limits = new Object2IntArrayMap<>(lanes.size());
+ for (Map.Entry<String, LaneConfig> entry : lanes.entrySet()) {
+ limits.put(entry.getKey(), computeLimitFromPercent(totalLimit, entry.getValue().maxPercent));
+ }
+ return limits;
+ }
+
+ @Override
+ public <T> Optional<String> computeLane(QueryPlus<T> query, Set<SegmentServerSelector> segments)
+ {
+ final String existingLane = query.getQuery().context().getLane();
+ if (existingLane != null) {
+ return Optional.of(existingLane);
+ }
+
+ int score = computeScore(query.getQuery(), segments);
+ if (score == 0) {
+ return Optional.empty();
+ }
+
+ // Find the lane with the highest minScore that this query meets
+ String bestLane = null;
+ int bestMinScore = 0;
+ for (Map.Entry<String, LaneConfig> entry : lanes.entrySet()) {
+ int minScore = entry.getValue().minScore;
+ if (score >= minScore && minScore > bestMinScore) {
+ bestLane = entry.getKey();
+ bestMinScore = minScore;
+ }
+ }
+ return Optional.ofNullable(bestLane);
+ }
+
+ private <T> int computeScore(Query<T> query, Set<SegmentServerSelector> segments)
Review Comment:
might be worth converging this at some point to a shared method with `ThresholdBasedQueryPrioritizationStrategy.computePriority`, we can do that later though.
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,236 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.apache.druid.server.QueryScheduler;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
Review Comment:
let's maybe add JsonProperty annotations on these other private methods, since they are also important if this strategy was to be serialized/returned somewhere.
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,236 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.apache.druid.server.QueryScheduler;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
+ "At least one of periodThreshold, durationThreshold, segmentCountThreshold, or segmentRangeThreshold must be set"
+ );
+ Preconditions.checkArgument(
+ lanes != null && !lanes.isEmpty(),
+ "At least one lane must be defined"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch(QueryScheduler.TOTAL::equals),
+ "Lane cannot be named 'total'"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch("default"::equals),
+ "Lane cannot be named 'default'"
Review Comment:
can be `!lanes.containsKey("default")`
##########
server/src/main/java/org/apache/druid/server/scheduling/WeightedQueryLaningStrategy.java:
##########
@@ -0,0 +1,236 @@
+/*
+ * 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.server.scheduling;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.apache.druid.client.SegmentServerSelector;
+import org.apache.druid.java.util.common.DateTimes;
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryPlus;
+import org.apache.druid.server.QueryLaningStrategy;
+import org.apache.druid.server.QueryScheduler;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+import org.joda.time.base.AbstractInterval;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Query laning strategy that scores queries by how many configured thresholds they breach,
+ * then assigns them to the most restrictive matching lane. This provides more nuanced lane
+ * assignment than {@link HiLoQueryLaningStrategy}, which uses a binary high/low split.
+ *
+ * <p>Configuration example:
+ * <pre>{@code
+ * {
+ * "strategy": "weighted",
+ * "periodThreshold": "P1M",
+ * "segmentCountThreshold": 1000,
+ * "lanes": {
+ * "low": { "minScore": 1, "maxPercent": 30 },
+ * "very-low": { "minScore": 3, "maxPercent": 10 }
+ * }
+ * }
+ * }</pre>
+ */
+public class WeightedQueryLaningStrategy implements QueryLaningStrategy
+{
+ private static final int DEFAULT_SEGMENT_THRESHOLD = Integer.MAX_VALUE;
+
+ private final int segmentCountThreshold;
+ @Nullable
+ private final Period periodThreshold;
+ @Nullable
+ private final Duration durationThreshold;
+ @Nullable
+ private final Duration segmentRangeThreshold;
+
+ @JsonProperty
+ private final Map<String, LaneConfig> lanes;
+
+ @JsonCreator
+ public WeightedQueryLaningStrategy(
+ @JsonProperty("periodThreshold") @Nullable String periodThreshold,
+ @JsonProperty("durationThreshold") @Nullable String durationThreshold,
+ @JsonProperty("segmentCountThreshold") @Nullable Integer segmentCountThreshold,
+ @JsonProperty("segmentRangeThreshold") @Nullable String segmentRangeThreshold,
+ @JsonProperty("lanes") Map<String, LaneConfig> lanes
+ )
+ {
+ Preconditions.checkArgument(
+ segmentCountThreshold != null || periodThreshold != null || durationThreshold != null || segmentRangeThreshold != null,
+ "At least one of periodThreshold, durationThreshold, segmentCountThreshold, or segmentRangeThreshold must be set"
+ );
+ Preconditions.checkArgument(
+ lanes != null && !lanes.isEmpty(),
+ "At least one lane must be defined"
+ );
+ Preconditions.checkArgument(
+ lanes.keySet().stream().noneMatch(QueryScheduler.TOTAL::equals),
Review Comment:
this can be `!lanes.containsKey(QueryScheduler.TOTAL)`
--
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