dynamic partitioning: use probability of AddTask to root partition to estimate overall add task rate - #11699
dynamic partitioning: use probability of AddTask to root partition to estimate overall add task rate#11699carlydf wants to merge 5 commits into
Conversation
|
Claude finished @carlydf's task in 6m 52s —— View job Reviewed 9 files, 3 findings. |
| "google.golang.org/grpc/metadata" | ||
| ) | ||
|
|
||
| func TestEstimatedTasksAllPartitionsMetadata(t *testing.T) { |
f35ea5e to
92599a7
Compare
92599a7 to
095eb9d
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 9a4ee05. Configure here.
| if err != nil { | ||
| return nil, err | ||
| } | ||
| ctx = appendEstimatedTasksAllPartitions(ctx, estimatedTasksAllPartitions, p.IsRoot()) |
There was a problem hiding this comment.
Root check uses wrong partition
Low Severity
appendEstimatedTasksAllPartitions is gated on p.IsRoot(), but p is still the pre-balance root partition. pickClientForWrite only reassigns its local p when choosing a write partition, so the caller always sees the original root. The estimate header is therefore attached on every load-balanced write, including requests sent to non-root partitions, even though the value is only meaningful for root samples.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 9a4ee05. Configure here.
| // scale target batch size by numTasks (since numTasks is scaled by partitions) | ||
| batchSize := int64(numTasks) * sm.batchSize | ||
| if sm.batch.Add(int64(numTasks)) < batchSize { | ||
| // wait until the batch has accumulated ~batchSize tasks per partition |
There was a problem hiding this comment.
Batch threshold is disconnected from the per-call estimate - scaler fires on every root hit when the 1% floor is active
When rootProbability < 0.01, estimatedTasksAllPartitions becomes 100 (from math.Round(1/0.01)). But the batch threshold in AddedTasks is still based on batchSize * currentWritePartitions:
temporal/service/matching/scale_manager.go
Lines 133 to 139 in 9a4ee05
With batchSize=5 and currentWrite=4, the threshold is 5 * 4 = 20, but each root hit adds 100. The threshold is exceeded on the very first call - the batch never accumulates.
The old code kept the threshold proportional to the per-call value:
batchSize := int64(numTasks) * sm.batchSize
if sm.batch.Add(int64(numTasks)) < batchSize {This always took batchSize calls to fire, regardless of the magnitude of numTasks. The new code breaks that proportionality by using partitionCount on the threshold side and 1/rootProbability on the addend side.
Downstream impact: In
temporal/service/matching/scale_manager.go
Lines 170 to 195 in 9a4ee05
instead of 5, NumTasks per scaler call is ~100 instead of the intended ~500 (batchSize * estimate). The scaler sees a 5x weaker rate signal, which could cause under-scaling.
Suggested fix - restore the proportional relationship:
func (sm *scaleManager) AddedTasks(estimatedTasksAllPartitions int) {
if sm == nil {
return
}
batchThreshold := int64(estimatedTasksAllPartitions) * sm.batchSize
if sm.batch.Add(int64(estimatedTasksAllPartitions)) < batchThreshold {
return
}
select {
case sm.wakeup <- struct{}{}:
default:
}
}
This matches the old behavior: always takes batchSize samples to fire, regardless of the estimate magnitude. The floor case (estimate=100) would need 5 root hits to accumulate 500 before waking, just like the non-floor case (estimate=4) needs 5 hits to reach 20.
or
Option B: Use max of both to handle mixed estimates
Since different calls might have different estimates (100 from floor, 4 from normal routing), use the larger:
batchThreshold := max(
int64(estimatedTasksAllPartitions) * sm.batchSize,
sm.batchSize * int64(sm.getLatestWritePartitions()),
)
| rootGap := max(int64(0), backlogCap-number.DecodeCompact8(counts[0])) | ||
| rootProbability := float64(rootGap) / float64(total) | ||
| if rootProbability < writePartitionRootProbabilityFloor { | ||
| if rand.Float64() < writePartitionRootProbabilityFloor { |
There was a problem hiding this comment.
No metric when the root-probability floor activates
When rootProbability < 0.01, the load balancer overrides normal backlog-aware routing to force 1% of traffic to root - even though root is nearly full. It's the only branch in pickWritePartitionByGap that actively pushes traffic against the natural routing decision, and it does so silently.
If root backlog starts growing unexpectedly in production, an operator today would have to read the source code and manually calculate rootGap / totalGap across all partitions to figure out whether the floor is the cause. A simple counter (e.g. write_partition_root_floor_engaged) would make this immediately visible on a dashboard and let them separate "root is getting forced traffic for sampling" from "root is getting traffic because something else is wrong."


What changed?
Why?
A previous PR changed AddTask load-balancing from uniform random to backlog-aware
How did you test it?
Potential risks
Changes AddTask computation and AddTask load balancing, but improves the estimation of the former, and we are ok with the tradeoff for the latter
Note
Medium Risk
Changes write-partition routing (1% root floor) and the task-rate signal that drives partition scaling. Incorrect estimates could delay scale-up/down, but this is not auth or data-path security.
Overview
Replaces the uniform-write assumption used by dynamic partition scaling with an estimate derived from the probability that an AddTask lands on the root.
PickWritePartitionnow returnsestimatedTasksAllPartitionsasround(1 / rootProbability). Write routing still prefers partitions with more gap to the backlog cap, but forces at least a 1% chance of hitting the root so the scaler keeps a sample of overall write rate even when the root is full. That estimate is sent only on root writes via theetap-bingRPC header; the rootscaleManageruses it (falling back to write-partition count if missing) and batches byBatchSize * currentWritePartitions.Reviewed by Cursor Bugbot for commit 4014f36. Bugbot is set up for automated code reviews on this repo. Configure here.