Skip to content

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

Merged
hubcio merged 10 commits into
masterfrom
fix/slow-replica-transfer
Oct 31, 2026
Merged

hubcio merged 10 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 94.48399% with 186 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.91%. Comparing base (af99cd9) to head (d8ccfa8).

Files with missing lines Patch % Lines
core/simulator/src/lib.rs 82.88% 50 Missing and 1 partial ⚠️
core/partitions/src/journal.rs 95.41% 23 Missing and 21 partials ⚠️
core/consensus/src/replication.rs 97.15% 20 Missing and 1 partial ⚠️
core/binary_protocol/src/consensus/header.rs 72.46% 19 Missing ⚠️
core/consensus/src/impls.rs 98.57% 8 Missing and 1 partial ⚠️
core/consensus/src/observability.rs 0.00% 9 Missing ⚠️
core/consensus/src/plane_helpers.rs 93.89% 2 Missing and 6 partials ⚠️
core/server_common/src/consensus_message.rs 76.47% 6 Missing and 2 partials ⚠️
core/partitions/src/io.rs 83.33% 7 Missing ⚠️
core/server/src/boot/mod.rs 62.50% 2 Missing and 1 partial ⚠️
... and 5 more
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #4658      +/-   ##
============================================
+ Coverage     90.71%   90.91%   +0.20%     
+ Complexity     3857     3854       -3     
============================================
  Files          1548     1551       +3     
  Lines        386919   395228    +8309     
  Branches     321059   329369    +8310     
============================================
+ Hits         350998   359341    +8343     
+ Misses        28739    28661      -78     
- Partials       7182     7226      +44     
Components Coverage Δ
Rust Core 92.17% <94.48%> (+0.21%) ⬆️
Java SDK 79.35% <ø> (ø)
C# SDK 84.34% <ø> (-0.08%) ⬇️
Python SDK 86.63% <ø> (ø)
PHP SDK 92.62% <ø> (ø)
Node SDK 96.57% <ø> (ø)
Go SDK 71.77% <ø> (+0.17%) ⬆️
C++ SDK 86.83% <ø> (ø)
Files with missing lines Coverage Δ
core/binary_protocol/src/consensus/command.rs 100.00% <100.00%> (ø)
core/configs/src/server_config/cluster.rs 99.20% <ø> (ø)
core/configs/src/server_config/defaults.rs 100.00% <100.00%> (ø)
core/configs/src/server_config/partition.rs 97.65% <100.00%> (+0.25%) ⬆️
core/consensus/src/lib.rs 21.42% <ø> (ø)
core/metadata/src/applied_frontier.rs 99.32% <100.00%> (+0.11%) ⬆️
core/metadata/src/impls/metadata.rs 94.15% <100.00%> (-0.05%) ⬇️
core/partitions/src/iggy_partition.rs 94.74% <ø> (+0.33%) ⬆️
core/partitions/src/iggy_partitions.rs 92.12% <100.00%> (+0.14%) ⬆️
core/partitions/src/install_backup.rs 95.34% <100.00%> (+0.08%) ⬆️
... and 29 more

... and 44 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 2 times, most recently from 625971f to 05b1522 Compare October 30, 2026 07:39
numinnex
numinnex previously approved these changes Oct 30, 2026
spetz
spetz previously approved these changes Oct 30, 2026
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.
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. Each read that went to disk stalled the
frames, ticks and replies of every group on that shard. A replica
that catches up after a long outage can need gigabytes of such reads
from its peers.

The pump now decides what to serve, and a detached task reads the
bytes. When the bytes land, the pump serves the request again
against the current state. Reads run one at a time per shard, and
the next starts only after the pump took the last result.
@hubcio
hubcio dismissed stale reviews from numinnex and spetz via f608e66 October 31, 2026 07:43
@hubcio
hubcio force-pushed the fix/slow-replica-transfer branch from cf88620 to f608e66 Compare October 31, 2026 07:43
The callout cap test held its slow callouts for 30 s, which is
also the operation deadline of the overflow login. When a late
retry found a free slot, it reached the mock service and the
test counted 3 requests, not 2. Hold the callouts for twice
that deadline.

The Swift backlog test allowed four reads for the message on
the other partition, but TLS runs needed up to seven. Each
fetch in flight takes a data connection of its own, so the
backlog can answer while the other fetch opens one. Read until
the message arrives, and fail if half of the backlog came first.
@hubcio
hubcio merged commit f9ba48a into master Oct 31, 2026
116 checks passed
@hubcio
hubcio deleted the fix/slow-replica-transfer branch October 31, 2026 12:20
@github-actions github-actions Bot removed the S-waiting-on-review PR is waiting on a reviewer label Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants