c/scheduling: fix allocation underflow on no-op reallocate#31071
Conversation
9b68942 to
8e3449c
Compare
2409a0e to
e61c38a
Compare
e61c38a to
01db72c
Compare
01db72c to
93eb7f7
Compare
Adds unit tests for allocated partition. Why? We found a bug in it that could reasonably have been caught with unit test coverage.
Adds a convenience wrapper on allocated partition's _original_node2shard map. There is a nuance in the logic where nullopt is not the same as empty which is not terribly apparent to a code reader. This change allows for the mistake to be self evident.
Regression test which simulates what happens when a reallocate is called with the same replication factor that the partition had prior. The long and short: allocated partition would mistakenly consider all replicas on the partition to be new replicas, and delete all of the allocations. This would eventually bubble up as a node crash on double freed allocations.
There was a problem hiding this comment.
Pull request overview
Fixes an allocation-state underflow/double-free triggered by “no-op” reallocations (requested RF equals current replica count), where existing replicas were incorrectly treated as newly allocated and then released.
Changes:
- Add a regression test covering allocator behavior when allocating with RF equal to the existing replica count.
- Refactor
allocated_partitionto track an “original placement snapshot” via a helper type, and adjust release/destructor logic to only release truly new replicas. - Add a focused
allocated_partitionunit test suite and Bazel target to exercise allocation counter behavior (no-op, new partition, move, revert, destructor reconciliation).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/v/cluster/tests/topic_updates_dispatcher_test.cc | Adds an end-to-end regression test ensuring no-op reallocate does not deallocate existing replicas. |
| src/v/cluster/scheduling/types.h | Introduces allocated_partition::original_node2shard helper and test friendship to make original-placement handling explicit. |
| src/v/cluster/scheduling/types.cc | Implements snapshot helper, fixes “added replicas” reporting for no-op reallocations, and adjusts destructor reconciliation behavior. |
| src/v/cluster/scheduling/tests/BUILD | Adds Bazel redpanda_cc_gtest target for the new allocated_partition unit tests. |
| src/v/cluster/scheduling/tests/allocated_partition_test.cc | Adds unit tests that validate allocation/final-count accounting across key allocated_partition scenarios. |
| #include "base/format_to.h" | ||
| #include "base/vassert.h" | ||
| #include "cluster/scheduling/allocation_state.h" | ||
| #include "utils/exceptions.h" |
There was a problem hiding this comment.
will fix if this gets another pass
93eb7f7 to
c13eb6f
Compare
Fixes the logic for yielding new replicas, if there were no changes, there are very clearly no new replicas.
Use is_original() + one pass instead of find/erase scratch; get() is now only used for full iteration.
c13eb6f to
7d32db4
Compare
CI test resultstest results on build#87085 |
| auto find(this Self&& self, model::node_id node) { | ||
| vassert(self._snapshot, "original placement snapshot not captured"); | ||
| return std::forward<Self>(self)._snapshot->find(node); | ||
| } | ||
|
|
||
| template<typename Self> | ||
| auto end(this Self&& self) { | ||
| vassert(self._snapshot, "original placement snapshot not captured"); | ||
| return std::forward<Self>(self)._snapshot->end(); | ||
| } |
There was a problem hiding this comment.
can we club these both into contains(node).. end() API is a little odd.
There was a problem hiding this comment.
are you thinking like find -> optional where nullopt implies not found?
the deducing this nonsense is for const correctness on the returned iterator (cend & normal end for mutable)
There was a problem hiding this comment.
all the callers of find() are essentially doing foo.find() != foo.end() .. which is effectively equivalent to contains(), so I was wondering if we even need find() and and end() as separate methods.
There was a problem hiding this comment.
theres a few
find
found == end
erase
There was a problem hiding this comment.
actually, no I think you're right, the find end erase gets folded away in the destructor commit. I'll add another cleanup round and remove the unused apis
There was a problem hiding this comment.
add has one legitimate usage of find with the it
|
/backport v26.1.x |
|
/backport v25.3.x |
|
/backport v25.2.x |
|
Failed to create a backport PR to v25.3.x branch. I tried: |
|
Failed to create a backport PR to v25.2.x branch. I tried: |
Attempts to deflake the nodewise recovery tests led us to more agressively change partition replication factors. This surfaced a bug on reconfigurations where if the reconfiguration was a no-op, we would actually accidentally delete all of that partitions allocations.
This pr does a few things.
The actual bug:
Backports Required
Release Notes
Bug Fixes