Skip to content

Commit 330c8a9

Browse files
committed
[improve][broker] Make AvgShedder the default load shedding and placement strategy
### Motivation The modular load manager still defaults to ThresholdShedder for shedding and LeastLongTermMessageRate for placement. The two score brokers differently (resource usage vs. message rate), so a bundle shed from a hot broker is routinely placed on another hot broker and shed again. ThresholdShedder's leader-side EMA also lags actual load by minutes, which over-unloads after scale-outs and rolling restarts, and it never sheds towards an idle broker unless lowerBoundarySheddingEnabled is set. AvgShedder (PIP-364) pairs the highest and lowest loaded brokers, pre-plans the destination of every bundle it unloads, and only acts after a gap has persisted for several consecutive checks, which removes the shed/place mismatch and the oscillation. ### Modifications - ServiceConfiguration, conf/broker.conf, conf/standalone.conf: loadBalancerLoadSheddingStrategy and loadBalancerLoadPlacementStrategy default to AvgShedder; maxUnloadPercentage defaults to 0.5 so AvgShedder equalizes a broker pair in one cycle; loadBalancerDistributeBundlesEvenlyEnabled defaults to false and is now documented in broker.conf, since the per-namespace bundle-count filter runs before placement and can discard the destination AvgShedder planned for a bundle. standalone.conf gains the strategy keys and the AvgShedder settings it did not list. - ModularLoadManagerImpl: strategy pairing is extracted into createLoadBalanceStrategies(). A configuration that sets a classic shedder (ThresholdShedder, UniformLoadShedder, OverloadShedder) while leaving the placement strategy at the new AvgShedder default no longer fails to start: the configured shedder is kept and placement falls back to LeastLongTermMessageRate with a warning, preserving the previous behavior for existing configurations. AvgShedder shedding with a different placement strategy logs a warning that its planned destinations are not honored. - ModularLoadManagerImplStrategyTest covers the defaults, the fallback, an explicit classic pairing, and AvgShedder shedding with other placement. Assisted-by: Claude Code (Opus 5)
1 parent 5df2b0d commit 330c8a9

5 files changed

Lines changed: 227 additions & 33 deletions

File tree

conf/broker.conf

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,14 +1755,33 @@ supportedNamespaceBundleSplitAlgorithms=range_equally_divide,topic_count_equally
17551755
# Default algorithm name for namespace bundle split
17561756
defaultNamespaceBundleSplitAlgorithm=range_equally_divide
17571757

1758-
# load shedding strategy, support OverloadShedder and ThresholdShedder, default is ThresholdShedder since 2.10.0
1759-
loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder
1758+
# Load shedding strategy for the modular load manager (ModularLoadManagerImpl).
1759+
# Supported: AvgShedder, ThresholdShedder, UniformLoadShedder and OverloadShedder.
1760+
# Default is AvgShedder since 5.0.0 (ThresholdShedder was the default from 2.10.0 to 4.x).
1761+
# AvgShedder implements both the shedding and the placement strategy: it pairs the highest and the lowest
1762+
# loaded brokers, moves load between them once their score gap has exceeded a threshold for several
1763+
# consecutive checks, and places the unloaded bundles on the broker it chose for them. It must be paired
1764+
# with loadBalancerLoadPlacementStrategy=AvgShedder (see below).
1765+
# When the extensible load manager (ExtensibleLoadManagerImpl) is used, set this to
1766+
# org.apache.pulsar.broker.loadbalance.extensions.scheduler.TransferShedder instead.
1767+
loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
17601768

17611769
# If enabled, when current usage < average usage - threshold, the broker with the highest load will be triggered to unload.
17621770
lowerBoundarySheddingEnabled=false
17631771

1764-
# load balance placement strategy, support LeastLongTermMessageRate and LeastResourceUsageWithWeight
1765-
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.LeastLongTermMessageRate
1772+
# Load balance placement strategy for the modular load manager.
1773+
# Supported: AvgShedder, LeastLongTermMessageRate and LeastResourceUsageWithWeight.
1774+
# Default is AvgShedder since 5.0.0 (LeastLongTermMessageRate before), which only takes effect together with
1775+
# loadBalancerLoadSheddingStrategy=AvgShedder. If a different shedding strategy is configured, the broker falls
1776+
# back to LeastLongTermMessageRate placement and logs a warning; set this key explicitly in that case
1777+
# (LeastResourceUsageWithWeight is the recommended pairing for ThresholdShedder).
1778+
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
1779+
1780+
# Whether to narrow the candidate brokers for a new bundle assignment to those owning the fewest bundles of the
1781+
# same namespace before the placement strategy runs. This distributes bundle counts evenly per namespace but
1782+
# overrides load-aware placement and can discard the destination AvgShedder planned for an unloaded bundle.
1783+
# Disabled by default since 5.0.0 (it was enabled before). System namespace bundles are always distributed evenly.
1784+
loadBalancerDistributeBundlesEvenlyEnabled=false
17661785

17671786
# The broker resource usage threshold.
17681787
# When the broker resource usage is greater than the pulsar cluster average resource usage,
@@ -1814,7 +1833,7 @@ loadBalancerBundleUnloadMinThroughputThreshold=10
18141833
# Time to wait for the unloading of a namespace bundle
18151834
namespaceBundleUnloadingTimeoutMs=60000
18161835

1817-
# configuration for AvgShedder, a new shedding and placement strategy
1836+
# Configuration for AvgShedder, the default shedding and placement strategy since 5.0.0
18181837
# The low threshold for the difference between the highest and lowest loaded brokers.
18191838
loadBalancerAvgShedderLowThreshold = 15
18201839

@@ -1827,10 +1846,10 @@ loadBalancerAvgShedderHitCountLowThreshold = 8
18271846
# The number of times the high threshold is triggered before the bundle is unloaded.
18281847
loadBalancerAvgShedderHitCountHighThreshold = 2
18291848

1830-
# In the UniformLoadShedder and AvgShedder strategy, the maximum unload ratio.
1831-
# For AvgShedder, recommend to set to 0.5, so that it will distribute the load evenly
1832-
# between the highest and lowest brokers.
1833-
maxUnloadPercentage = 0.2
1849+
# In the UniformLoadShedder and AvgShedder strategy, the maximum unload ratio: the share of the load
1850+
# difference between the highest and the lowest loaded broker that is moved in one shedding cycle.
1851+
# Default is 0.5 since 5.0.0 (0.2 before), which lets AvgShedder equalize the two brokers in a single cycle.
1852+
maxUnloadPercentage = 0.5
18341853

18351854

18361855
### --- Load balancer extension --- ###

conf/standalone.conf

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,47 @@ loadBalancerNamespaceBundleMaxBandwidthMbytes=100
12011201
# maximum number of bundles in a namespace
12021202
loadBalancerNamespaceMaximumBundles=128
12031203

1204+
# Load shedding strategy for the modular load manager (ModularLoadManagerImpl).
1205+
# Supported: AvgShedder, ThresholdShedder, UniformLoadShedder and OverloadShedder.
1206+
# Default is AvgShedder since 5.0.0 (ThresholdShedder was the default from 2.10.0 to 4.x).
1207+
# AvgShedder implements both the shedding and the placement strategy and must be paired with
1208+
# loadBalancerLoadPlacementStrategy=AvgShedder (see below).
1209+
loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
1210+
1211+
# If enabled, when current usage < average usage - threshold, the broker with the highest load will be triggered to unload.
1212+
# It only takes effect in the ThresholdShedder strategy.
1213+
lowerBoundarySheddingEnabled=false
1214+
1215+
# Load balance placement strategy for the modular load manager.
1216+
# Supported: AvgShedder, LeastLongTermMessageRate and LeastResourceUsageWithWeight.
1217+
# Default is AvgShedder since 5.0.0 (LeastLongTermMessageRate before), which only takes effect together with
1218+
# loadBalancerLoadSheddingStrategy=AvgShedder. If a different shedding strategy is configured, the broker falls
1219+
# back to LeastLongTermMessageRate placement and logs a warning; set this key explicitly in that case.
1220+
loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder
1221+
1222+
# Whether to narrow the candidate brokers for a new bundle assignment to those owning the fewest bundles of the
1223+
# same namespace before the placement strategy runs. Disabled by default since 5.0.0 (it was enabled before)
1224+
# because it overrides load-aware placement and can discard the destination AvgShedder planned for a bundle.
1225+
loadBalancerDistributeBundlesEvenlyEnabled=false
1226+
1227+
# Configuration for AvgShedder, the default shedding and placement strategy since 5.0.0
1228+
# The low threshold for the difference between the highest and lowest loaded brokers.
1229+
loadBalancerAvgShedderLowThreshold = 15
1230+
1231+
# The high threshold for the difference between the highest and lowest loaded brokers.
1232+
loadBalancerAvgShedderHighThreshold = 40
1233+
1234+
# The number of times the low threshold is triggered before the bundle is unloaded.
1235+
loadBalancerAvgShedderHitCountLowThreshold = 8
1236+
1237+
# The number of times the high threshold is triggered before the bundle is unloaded.
1238+
loadBalancerAvgShedderHitCountHighThreshold = 2
1239+
1240+
# In the UniformLoadShedder and AvgShedder strategy, the maximum unload ratio: the share of the load
1241+
# difference between the highest and the lowest loaded broker that is moved in one shedding cycle.
1242+
# Default is 0.5 since 5.0.0 (0.2 before), which lets AvgShedder equalize the two brokers in a single cycle.
1243+
maxUnloadPercentage = 0.5
1244+
12041245
# The broker resource usage threshold.
12051246
# When the broker resource usage is greater than the pulsar cluster average resource usage,
12061247
# the threshold shedder will be triggered to offload bundles from the broker.

pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3028,9 +3028,12 @@ in the Admin API message inspection endpoints (getMessageById, peekNthMessage,
30283028
dynamic = true,
30293029
doc = "load balance load shedding strategy "
30303030
+ "(It requires broker restart if value is changed using dynamic config). "
3031-
+ "Default is ThresholdShedder since 2.10.0"
3031+
+ "Default is AvgShedder since 5.0.0 (ThresholdShedder was the default from 2.10.0 to 4.x). "
3032+
+ "AvgShedder implements both the shedding and the placement strategy and must be paired with "
3033+
+ "loadBalancerLoadPlacementStrategy=AvgShedder; when a different shedding strategy is configured, "
3034+
+ "an AvgShedder placement strategy falls back to LeastLongTermMessageRate."
30323035
)
3033-
private String loadBalancerLoadSheddingStrategy = "org.apache.pulsar.broker.loadbalance.impl.ThresholdShedder";
3036+
private String loadBalancerLoadSheddingStrategy = "org.apache.pulsar.broker.loadbalance.impl.AvgShedder";
30343037

30353038
@FieldContext(
30363039
category = CATEGORY_LOAD_BALANCER,
@@ -3041,10 +3044,15 @@ in the Admin API message inspection endpoints (getMessageById, peekNthMessage,
30413044

30423045
@FieldContext(
30433046
category = CATEGORY_LOAD_BALANCER,
3044-
doc = "load balance placement strategy"
3047+
doc = "load balance placement strategy. "
3048+
+ "Default is AvgShedder since 5.0.0 (LeastLongTermMessageRate before), which binds placement to "
3049+
+ "the AvgShedder shedding strategy so that unloaded bundles land on the broker the shedder "
3050+
+ "chose for them. It only takes effect together with "
3051+
+ "loadBalancerLoadSheddingStrategy=AvgShedder; with any other shedding strategy the broker "
3052+
+ "falls back to LeastLongTermMessageRate placement and logs a warning."
30453053
)
30463054
private String loadBalancerLoadPlacementStrategy =
3047-
"org.apache.pulsar.broker.loadbalance.impl.LeastLongTermMessageRate";
3055+
"org.apache.pulsar.broker.loadbalance.impl.AvgShedder";
30483056

30493057
@FieldContext(
30503058
dynamic = true,
@@ -3087,9 +3095,14 @@ in the Admin API message inspection endpoints (getMessageById, peekNthMessage,
30873095
@FieldContext(
30883096
dynamic = true,
30893097
category = CATEGORY_LOAD_BALANCER,
3090-
doc = "enable/disable distribute bundles evenly"
3098+
doc = "Enable/disable distributing bundles evenly across brokers when a bundle is assigned. "
3099+
+ "When enabled, the candidate brokers for a new assignment are first narrowed to those "
3100+
+ "owning the fewest bundles of that namespace, before the placement strategy runs. This "
3101+
+ "overrides load-aware placement and can discard the destination the AvgShedder shedding "
3102+
+ "strategy planned for an unloaded bundle, so it is disabled by default since 5.0.0 "
3103+
+ "(it was enabled before). Bundles of the system namespace are always distributed evenly."
30913104
)
3092-
private boolean loadBalancerDistributeBundlesEvenlyEnabled = true;
3105+
private boolean loadBalancerDistributeBundlesEvenlyEnabled = false;
30933106

30943107
@FieldContext(
30953108
category = CATEGORY_LOAD_BALANCER,
@@ -3194,11 +3207,12 @@ in the Admin API message inspection endpoints (getMessageById, peekNthMessage,
31943207
@FieldContext(
31953208
dynamic = true,
31963209
category = CATEGORY_LOAD_BALANCER,
3197-
doc = "In the UniformLoadShedder and AvgShedder strategy, the maximum unload ratio."
3198-
+ "For AvgShedder, recommend to set to 0.5, so that it will distribute the load "
3199-
+ "evenly between the highest and lowest brokers."
3210+
doc = "In the UniformLoadShedder and AvgShedder strategy, the maximum unload ratio: the share of "
3211+
+ "the load difference between the highest and the lowest loaded broker that is moved in one "
3212+
+ "shedding cycle. Default is 0.5 since 5.0.0 (0.2 before), which lets AvgShedder equalize "
3213+
+ "the load of the two brokers in a single cycle."
32003214
)
3201-
private double maxUnloadPercentage = 0.2;
3215+
private double maxUnloadPercentage = 0.5;
32023216

32033217
@FieldContext(
32043218
dynamic = true,

pulsar-broker/src/main/java/org/apache/pulsar/broker/loadbalance/impl/ModularLoadManagerImpl.java

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,9 @@ public void initialize(final PulsarService pulsar) {
250250
defaultStats.msgRateIn = DEFAULT_MESSAGE_RATE;
251251
defaultStats.msgRateOut = DEFAULT_MESSAGE_RATE;
252252

253-
placementStrategy = ModularLoadManagerStrategy.create(conf);
253+
final LoadBalanceStrategies strategies = createLoadBalanceStrategies(conf);
254+
placementStrategy = strategies.placementStrategy();
255+
loadSheddingStrategy = strategies.loadSheddingStrategy();
254256
sheddingExcludedNamespaceSelectionStrategy = new RoundRobinBrokerSelector();
255257
policies = new SimpleResourceAllocationPolicies(pulsar);
256258
filterPipeline.add(new BrokerLoadManagerClassFilter());
@@ -263,22 +265,53 @@ public void initialize(final PulsarService pulsar) {
263265
executors.execute(
264266
() -> LoadManagerShared.refreshBrokerToFailureDomainMap(pulsar, brokerToFailureDomainMap));
265267
});
268+
}
269+
270+
/**
271+
* The placement strategy and the load shedding strategy resolved from the configuration.
272+
*/
273+
record LoadBalanceStrategies(ModularLoadManagerStrategy placementStrategy,
274+
LoadSheddingStrategy loadSheddingStrategy) {
275+
}
266276

277+
/**
278+
* Creates the placement and the load shedding strategy from the configuration and pairs them.
279+
* <p>
280+
* A placement strategy that is also a load shedding strategy (AvgShedder, the default) plans the destination of
281+
* every bundle it unloads, so it is only used when the same class is configured as the shedding strategy. If a
282+
* different shedding strategy is configured explicitly, the configured shedder is kept and placement falls back
283+
* to {@link LeastLongTermMessageRate}, the default placement strategy before AvgShedder, so configurations that
284+
* only set {@code loadBalancerLoadSheddingStrategy} keep the behavior they had.
285+
*/
286+
@VisibleForTesting
287+
static LoadBalanceStrategies createLoadBalanceStrategies(ServiceConfiguration conf) {
288+
ModularLoadManagerStrategy placementStrategy = ModularLoadManagerStrategy.create(conf);
267289
if (placementStrategy instanceof LoadSheddingStrategy) {
268-
// if the placement strategy is also a load shedding strategy
269-
// we need to check two strategies are the same
270-
if (!conf.getLoadBalancerLoadSheddingStrategy().equals(
271-
conf.getLoadBalancerLoadPlacementStrategy())) {
272-
throw new IllegalArgumentException("The load shedding strategy: "
273-
+ conf.getLoadBalancerLoadSheddingStrategy()
274-
+ " can't work with the placement strategy: "
275-
+ conf.getLoadBalancerLoadPlacementStrategy());
290+
if (conf.getLoadBalancerLoadSheddingStrategy().equals(conf.getLoadBalancerLoadPlacementStrategy())) {
291+
// bind the load shedding strategy and the placement strategy
292+
return new LoadBalanceStrategies(placementStrategy, (LoadSheddingStrategy) placementStrategy);
276293
}
277-
// bind the load shedding strategy and the placement strategy
278-
loadSheddingStrategy = (LoadSheddingStrategy) placementStrategy;
279-
} else {
280-
loadSheddingStrategy = createLoadSheddingStrategy();
294+
log.warn()
295+
.attr("sheddingStrategy", conf.getLoadBalancerLoadSheddingStrategy())
296+
.attr("placementStrategy", conf.getLoadBalancerLoadPlacementStrategy())
297+
.attr("fallbackPlacementStrategy", LeastLongTermMessageRate.class.getName())
298+
.log("The configured load shedding strategy cannot be paired with the placement strategy,"
299+
+ " which requires the same class as the shedding strategy. Using the fallback"
300+
+ " placement strategy instead. Set loadBalancerLoadPlacementStrategy explicitly"
301+
+ " to choose the placement strategy");
302+
return new LoadBalanceStrategies(new LeastLongTermMessageRate(), createLoadSheddingStrategy(conf));
303+
}
304+
LoadSheddingStrategy loadSheddingStrategy = createLoadSheddingStrategy(conf);
305+
if (loadSheddingStrategy instanceof ModularLoadManagerStrategy) {
306+
log.warn()
307+
.attr("sheddingStrategy", conf.getLoadBalancerLoadSheddingStrategy())
308+
.attr("placementStrategy", conf.getLoadBalancerLoadPlacementStrategy())
309+
.log("The load shedding strategy also implements bundle placement but a different"
310+
+ " placement strategy is configured; bundles it unloads are placed by the"
311+
+ " configured placement strategy, not where the shedder planned them. Set"
312+
+ " loadBalancerLoadPlacementStrategy to the same class to pair them");
281313
}
314+
return new LoadBalanceStrategies(placementStrategy, loadSheddingStrategy);
282315
}
283316

284317
public void handleDataNotification(Notification t) {
@@ -328,7 +361,7 @@ private boolean isMetadataSessionConnected() {
328361
return lastMetadataSessionEvent != null && lastMetadataSessionEvent.isConnected();
329362
}
330363

331-
private LoadSheddingStrategy createLoadSheddingStrategy() {
364+
private static LoadSheddingStrategy createLoadSheddingStrategy(ServiceConfiguration conf) {
332365
return Reflections.createInstance(conf.getLoadBalancerLoadSheddingStrategy(), LoadSheddingStrategy.class,
333366
Thread.currentThread().getContextClassLoader());
334367
}

0 commit comments

Comments
 (0)