Skip to content

fix(cluster)!: let slow replicas catch up under load - #4658

Open
hubcio wants to merge 12 commits into
masterfrom
fix/slow-replica-transfer
Open

hubcio wants to merge 12 commits into
masterfrom
fix/slow-replica-transfer

Conversation

@hubcio

@hubcio hubcio commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

A slow replica could throttle healthy peers, churn views and repeat
snapshots after losing their required repair tails. Repair windows
could also fill the WAL before a durable prefix was applied. Builds on
the regressions in #4653.

Fan out prepares independently so healthy replicas can keep
progressing. Replay peers in bounded batches with fenced progress
replies. Bound replay work per shard tick and charge timeout retries
to the same peer window. Apply durable repair prefixes and wait for
WAL headroom before further fetches. Recheck busy peers without
releasing their batch credit and drain full replay windows before
backing off unavailable history.

Reuse verified segments and fetch missing suffixes. Move hashing,
index reconstruction and spills outside the shard pump with owned
cancellation and installation guards. Pin admitted snapshot tails
under the shared configurable cap and charge full retained
allocations. The cap defaults to 1 GiB and bounds extra retained
repair memory, not total process RSS. Keep recovering nodes out of new
primary placements.

Require PreVote quorum before changing views and step down when fresh
heartbeat replies no longer establish quorum. Preserve acknowledged
operations without a WAL and reject superseded owner installs before
sequencing.

Local validation passed 3,252 affected tests, 1,047 non-connector
integration tests and 46 doctests. Workspace build, Clippy,
formatting, manifest order, documentation and pre-commit checks
passed. The final rebase preserves the feature patch unchanged. Codex
implemented the changes and tests, ran validation and performed the
four-role review.

The pre-review AWS delay and restart test at 150,000 messages/s
recovered all 12 topics under continued load with zero publish errors.
Later review fixes have local validation and CI; that AWS run predates
them.

BREAKING CHANGE: CommitReply and PrepareProgress require a coordinated
server upgrade. The client protocol is unchanged.

@hubcio
hubcio force-pushed the fix/slow-replica-transfer branch from f563662 to 1823eef Compare October 27, 2026 07:59
@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Oct 27, 2026
@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.01542% with 539 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.16%. Comparing base (4eccc5e) to head (cf88620).

Files with missing lines Patch % Lines
core/partitions/src/journal.rs 81.23% 150 Missing and 30 partials ⚠️
core/consensus/src/replication.rs 87.94% 89 Missing ⚠️
core/consensus/src/impls.rs 86.57% 84 Missing and 1 partial ⚠️
core/simulator/src/lib.rs 79.19% 61 Missing and 1 partial ⚠️
core/consensus/src/plane_helpers.rs 60.30% 46 Missing and 6 partials ⚠️
core/binary_protocol/src/consensus/header.rs 63.76% 25 Missing ⚠️
core/server/src/dispatch/submit.rs 0.00% 11 Missing ⚠️
core/consensus/src/observability.rs 0.00% 9 Missing ⚠️
core/server_common/src/consensus_message.rs 76.47% 6 Missing and 2 partials ⚠️
core/partitions/src/io.rs 83.33% 7 Missing ⚠️
... and 6 more

❗ There is a different number of reports uploaded between BASE (4eccc5e) and HEAD (cf88620). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (4eccc5e) HEAD (cf88620)
rust 4 3
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #4658      +/-   ##
============================================
- Coverage     90.87%   81.16%   -9.72%     
- Complexity     3104     3105       +1     
============================================
  Files          1482     1485       +3     
  Lines        362179   370488    +8309     
  Branches     306049   314361    +8312     
============================================
- Hits         329117   300690   -28427     
- Misses        26144    62814   +36670     
- Partials       6918     6984      +66     
Components Coverage Δ
Rust Core 80.54% <84.01%> (-11.66%) ⬇️
Java SDK 78.14% <ø> (+<0.01%) ⬆️
C# SDK 83.48% <ø> (-0.12%) ⬇️
Python SDK 85.67% <ø> (ø)
PHP SDK 90.71% <ø> (ø)
Node SDK 96.44% <ø> (ø)
Go SDK 69.33% <ø> (+0.03%) ⬆️
C++ SDK 85.55% <ø> (ø)
Files with missing lines Coverage Δ
core/binary_protocol/src/consensus/command.rs 100.00% <100.00%> (ø)
core/configs/src/server_config/cluster.rs 84.05% <ø> (-15.16%) ⬇️
core/configs/src/server_config/defaults.rs 100.00% <100.00%> (ø)
core/configs/src/server_config/partition.rs 75.00% <100.00%> (-22.41%) ⬇️
core/consensus/src/lib.rs 21.42% <ø> (ø)
core/metadata/src/applied_frontier.rs 85.76% <100.00%> (-13.45%) ⬇️
core/metadata/src/impls/metadata.rs 82.61% <100.00%> (-11.85%) ⬇️
core/partitions/src/iggy_partition.rs 80.38% <ø> (-13.97%) ⬇️
core/partitions/src/iggy_partitions.rs 81.42% <100.00%> (-10.81%) ⬇️
core/partitions/src/install_backup.rs 88.37% <100.00%> (-6.89%) ⬇️
... and 29 more

... and 491 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hubcio
hubcio force-pushed the fix/slow-replica-transfer branch 2 times, most recently from a8fc4be to 29297d4 Compare October 28, 2026 11:13
@hubcio hubcio changed the title fix(partitions): let a slow replica rejoin the cluster fix(cluster)!: recover slow replicas and step down without quorum Oct 28, 2026
@hubcio
hubcio force-pushed the fix/slow-replica-transfer branch 2 times, most recently from 1f948d8 to 9120482 Compare October 29, 2026 09:21
@hubcio hubcio changed the title fix(cluster)!: recover slow replicas and step down without quorum fix(cluster)!: let slow replicas catch up under load Oct 29, 2026
@hubcio hubcio mentioned this pull request Oct 29, 2026
4 tasks
@hubcio
hubcio force-pushed the fix/slow-replica-transfer branch from 6bf131e to ed4bb40 Compare October 29, 2026 20:42
A slow replica could throttle healthy peers, churn views and repeat
snapshots after losing their required repair tails. Repair windows
could also fill the WAL before a durable prefix was applied.

Fan out prepares independently and replay peers in bounded batches
with fenced progress replies. Bound replay work per shard tick and
charge timeout retries to the same peer window. Apply durable repair
prefixes and wait for WAL headroom before further trusted fetches.
Recheck busy peers promptly without releasing their batch credit and
drain full replay windows before backing off unavailable history.

Reuse verified segments and fetch missing suffixes. Move hashing,
index reconstruction and spills outside the shard pump with owned
cancellation and installation guards. Pin admitted snapshot tails
under the shared configurable cap, charge full retained allocations,
and keep recovering nodes out of new primary placements.

Require PreVote quorum before changing views and step down when fresh
heartbeat replies no longer establish quorum. Preserve acknowledged
operations without a WAL and reject superseded owner installs before
sequencing.

BREAKING CHANGE: CommitReply and PrepareProgress require a coordinated
server upgrade. The client protocol is unchanged.
The primary stops sending prepares to a backup when its send
window fills or while a progress probe is out, and resumes only
on the next 10 ms consensus tick. The default simulated network
reorders packets, so gap repair hides this stall.

Add an ordered network mode, off by default, that delivers each
link in send order like a TCP link. Two tests send one produce
per tick and require each answer within its four hops plus one
probe round trip. Both fail here and pass on c92d2bd.
The window to each backup was stop-and-wait: sends paused while a
progress probe was out and resumed on the next 10 ms tick. Both
backup windows filled at the same op, so fault-free produce p99 rose
from 2.25 to 11.34 ms.

The flow now pipelines credit. A checkpoint probe follows the frame
that fills half the window on the same FIFO link. Its reply credits
only the bytes sent before it, so no send waits. Replies carry a new
held_op, taken from reserved header bytes, so an unflushed persisted
WAL no longer reads as a gap. A retransmit no longer starts a
replay. Counters for refused and replayed prepares stay at zero
while backups keep up, and the simulator tests now require that.
It runs for about 125 s locally and 127 to 131 s in CI, the only
cluster test over 60 s. Run it with --run-ignored only.
A ready progress reply lowered the replay cursor but never raised
it. Busy replies during a state transfer parked the cursor on an op
that the primary later dropped. After the backup caught up, the
replay from that op failed on every try, and the cursor refused
every live prepare. Under load, the backup stayed on pull repair
and never got its primaries back.

If the backup holds the op at the cursor, a ready reply now moves
the cursor to the op after the held prefix. A gap already moved it
there. The seeded flow model now drops old ops and repairs the
backup.

A live replay also read up to 8 MiB of bodies before the window
admitted the first frame. It now asks the window before each read.
A prefix read that outlives its bound is now asked to stop and kept
until it lands, so a transfer never holds two artifact buffers.
A lagging backup can hold hundreds of thousands of unapplied
entries. On every tick, committed_headers_from looped over all of
them to pick at most 64 ops. One shard then spent most of its CPU
in this walk and applied entries slower than new prepares arrived.
Its journal grew without limit, and the replica never caught up.

Look up each op in the first-occurrence index instead. A walk now
costs one lookup per returned header.
A lagging backup holds hundreds of thousands of journal entries and
flushes after almost every commit walk. Each eviction drained the
journal and appended every retained entry again, which decoded each
batch and rebuilt the op, offset and timestamp indexes. A shard with
three catching-up partitions stayed at 100% CPU and the backup did
not catch up while producers kept writing.

The entries and headers now live in deques with a front offset, so an
eviction pops the prefix and removes its index keys in O(evicted).
The journal keeps running message and byte totals for journal.info.
A journal out of op order, or a prefix op with a copy in the retained
tail, still rebuilds the tail. Both shapes are rare.
@hubcio
hubcio force-pushed the fix/slow-replica-transfer branch from 625971f to 05b1522 Compare October 30, 2026 07:39
The rebase onto #4687 kept a second shutdown of the fast consumer
after master already shuts it down and drops it, so the integration
tests did not compile.
A serving shard read released prepare bodies and transfer chunks
inline on its message pump, so each cold read stalled the frames,
ticks and replies of every group on that shard. After a 10 minute
delay on one node, publish p99 stayed near 470 ms until that node
caught up.

The pump now decides what to serve, a detached task reads the bytes,
and the pump serves the request again with them against the state of
that moment. Reads run one at a time per shard, and the next starts
only after the pump took the last result. In the same test p99
during catch-up drops to 27 ms.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review PR is waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants