fix(shard): FLUSHALL inside MULTI/EXEC clears every shard, not just one (c10k E2) - #437
Conversation
|
Warning Review limit reached
Next review available in: 20 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoFix sharded MULTI/EXEC FLUSHALL/FLUSHDB to broadcast and report partial failures
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
Code Review by Qodo
1.
|
ab552ea to
ebe5786
Compare
…ne (c10k E2) A keyless FLUSHDB/FLUSHALL queued inside a transaction cleared only the slice the transaction body ran on, while EXEC still answered +OK. At --shards 4 that leaves roughly three quarters of the keyspace alive after the client has been told the database was emptied: a silent wrong answer to a destructive command, typically noticed much later via a non-zero DBSIZE. Measured before the fix: 45 of 64 keys survived a transaction that reported success. The live (non-MULTI) path has broadcast since D-2 via `coordinate_flush_broadcast`, which also turns any failed leg into an explicit partial-flush error rather than a silent success. The transactional executor never got the same treatment — `execute_transaction_sharded` runs the queued body against the LOCAL slice with no per-key routing and no fan-out. The fix follows the `exec_publishes` contract already established for deferred transactional work: the executor RECORDS each flush as `(result_index, command, db)` and the ORIGINATOR performs the fan-out. The executor cannot do it itself for two reasons — it is synchronous while the broadcast awaits, and for a routed transaction it runs on the OWNER shard, where fanning out from inside that shard's own message loop risks a shard-to-shard wait cycle. `broadcast_txn_flushes` skips the leg that already ran and patches `result[result_index]` with the partial-flush error if any remaining leg fails, so a +OK for a flush inside a transaction can be trusted exactly as on the live path. Both handlers are covered (monoio and sharded/tokio) and both transaction shapes (local body and routed-to-owner body). The per-entry `selected` db is recorded, so a queued SELECT before the flush is honoured. Cross-shard atomicity is unchanged and unchangeable: a concurrent reader can still observe shard A flushed before shard B. MULTI bounds the report, not the visibility, in a shared-nothing engine. Test asserts through DBSIZE rather than per-key GETs so it fails on ANY surviving key. Verified 4/4 under both runtimes: DBSIZE went Int(45) -> Int(0). The test harness in this suite also gained a `Drop` guard: `Moon::kill9` only runs on the happy path, so a failed assertion unwound past it and stranded the server. One such leak ran for four hours holding its port and data dir before it was noticed. Refs: c10k hardening review, finding E2 author: Tin Dang
f9122e0 to
c4d39f8
Compare
The bug (c10k hardening finding E2)
A keyless
FLUSHDB/FLUSHALLqueued inside a transaction cleared only the slice the transaction body ran on, whileEXECstill answered+OK. At--shards 4that leaves ~¾ of the keyspace alive after the client was told the database was emptied — a silent wrong answer to a destructive command, typically noticed much later via a non-zeroDBSIZE. Measured before the fix: 45 of 64 keys survived a transaction that reported success.Why
The live (non-MULTI) path has broadcast since D-2 via
coordinate_flush_broadcast, which also turns any failed leg into an explicit partial-flush error rather than a silent success. The transactional executor never got the same treatment —execute_transaction_shardedruns the queued body against the local slice with no per-key routing and no fan-out.Fix
Follows the
exec_publishescontract already established for deferred transactional work: the executor records each flush as(result_index, command, db)and the originator performs the fan-out (broadcast_txn_flushes). The executor can't do it itself — it's synchronous while the broadcast awaits, and for a routed transaction it runs on the owner shard where fanning out from inside that shard's own message loop risks a shard-to-shard wait cycle.broadcast_txn_flushesskips the leg that already ran and patchesresult[result_index]with the partial-flush error if any remaining leg fails, so a+OKfor a flush inside a transaction can be trusted exactly as on the live path.Both handlers covered (monoio + sharded/tokio) and both transaction shapes (local body, routed-to-owner body). The per-entry
selecteddb is recorded, so a queuedSELECTbefore the flush is honoured.Cross-shard atomicity is unchanged and unchangeable: a concurrent reader can still observe shard A flushed before shard B. MULTI bounds the report, not the visibility, in a shared-nothing engine.
Test (red/green)
flushall_inside_multi_clears_every_shard— SETs 64 keys at--shards 4, assertsDBSIZE == 64, runsMULTI; FLUSHALL; EXEC, then asserts eitherDBSIZE == 0(if EXEC claimed success) or an explicit error. Asserts throughDBSIZErather than per-key GETs so it fails on ANY surviving key. Verified 4/4 under both runtimes:DBSIZEwentInt(45)→Int(0).The suite's
Moonharness also gained aDropguard:kill9only runs on the happy path, so a failed assertion unwound past it and stranded the server (one such leak ran four hours holding its port + data dir).Refs: c10k hardening review, finding E2