Skip to content

fix(replication): stream SELECT for multi-db writes + serve SELECT on replicas#281

Merged
pilotspacex-byte merged 1 commit into
mainfrom
feat/v0.7-select-stream
Jul 11, 2026
Merged

fix(replication): stream SELECT for multi-db writes + serve SELECT on replicas#281
pilotspacex-byte merged 1 commit into
mainfrom
feat/v0.7-select-stream

Conversation

@pilotspacex-byte

Copy link
Copy Markdown
Contributor

Summary

Multi-db replication correctness (HIGH-2, tasks #22 + #23): the replica-side drain already understood in-stream SELECT context, but the master never emitted it — every replicated write applied to the replica's db 0 regardless of which db it executed in on the master. And a client couldn't even read a replica's non-zero dbs: SELECT is flagged W in the metadata table, so the READONLY guard rejected it.

Master streams SELECT

  • ReplicationState grows per-shard stream_db (-1 = unknown): the db context of the last data command recorded into the shard's backlog.
  • New record_local_write_db(ctx, db, bytes) prepends a SELECT <db> record whenever the writing connection's db differs — both records in the same synchronous stretch as the mutation (exactly-once contract unchanged).
  • All db-scoped record sites switched: the three KV legs (main/MOVE/COPY), MULTI/EXEC bodies, GRAPH.* WAL records, FT.* index defs. TEMPORAL.INVALIDATE-AT keeps the db-agnostic primitive.
  • FULLRESYNC snapshot capture resets stream_db to -1 in the same synchronous stretch as the RDB capture (Redis's slaveseldb = -1 idiom): post-snapshot bytes always re-establish context, so a fresh replica (drain starts at db 0) can never bind a write to the wrong db.

Replica preserves context across +CONTINUE

Resumed backlog bytes only carry SELECT at db changes — if the stream was in db N when the link dropped, a 0-reset drain would misapply resumed bytes to db 0. ReplicaTaskConfig::stream_db now persists the drain-side db across reconnects (reset to 0 on FULLRESYNC; in-memory only — a process restart starts at offset 0 and always full-resyncs).

Replicas serve SELECT (#23)

All three dispatch paths (monoio, tokio sharded, single) exempt SELECT from the READONLY guard — connection-state only, Redis parity.

Verification

  • New e2e replica_applies_multi_db_stream — RED before the fix (failed first at the READONLY SELECT assertion; the master also misapplied db-2 writes to db 0), GREEN after. Asserts the db-2 write lands in db 2, did not leak into db 0, and the db-0 sentinel did not leak into db 2.
  • lib 4111 green; replication_streaming 5/5; replication_graph 3/3; replication_hardening 5/5; tokio replication_test 5/5; fmt + clippy clean both feature sets. Re-verified after rebase onto main (post-feat(replication): v0.7 graph-plane replication — live GRAPH.* stream + PSYNC snapshot backfill #279).

… replicas

HIGH-2 (tasks #22 + #23): the replica-side drain already understood
in-stream SELECT context (drain_replicated_commands binds each command
to the selected db), but the master never emitted it — every replicated
write applied to the replica's db 0 regardless of which db it executed
in on the master. And a client could not even READ a replica's non-zero
dbs: SELECT is flagged W in the metadata table, so the READONLY guard
rejected it.

Master side:
- ReplicationState grows per-shard `stream_db: Vec<AtomicI64>` (-1 =
  unknown): the logical-db context of the LAST data command recorded
  into the shard's replication backlog.
- New `record_local_write_db(ctx, db, bytes)` prepends a `SELECT <db>`
  record via the existing `record_local_write` primitive whenever the
  writing connection's db differs from the stream context. Both records
  stay in the same synchronous stretch as the mutation (exactly-once
  contract unchanged; SELECT bytes advance the offset like any stream
  bytes).
- All db-scoped record sites switched to the db-aware variant: the
  three KV legs (main, MOVE, COPY), the MULTI/EXEC body loop, the
  GRAPH.* WAL-record leg, and the FT.* index-def leg. The
  TEMPORAL.INVALIDATE-AT leg keeps the primitive (db-agnostic record,
  leaves the context untouched — which stays truthful).
- FULLRESYNC snapshot capture resets the shard's stream_db to -1 in the
  SAME synchronous stretch as the RDB capture (Redis's `slaveseldb = -1`
  idiom): every byte at offset >= snapshot_offset starts from "db
  unknown", so the first post-snapshot write re-establishes the context
  with an explicit SELECT and a fresh replica (whose drain starts at
  db 0) can never bind a write to the wrong db. Redundant re-SELECTs
  for already-attached replicas are idempotent.

Replica side:
- `ReplicaTaskConfig::stream_db` (AtomicUsize) preserves the drain's db
  context ACROSS reconnects: a +CONTINUE partial resync replays backlog
  bytes that only carry SELECT at db CHANGES — if the stream was in
  db N when the link dropped, the resumed bytes have no fresh SELECT
  and a 0-reset drain would misapply them to db 0. Seeded into
  stream_commands on entry, persisted after every drain, reset to 0 on
  +FULLRESYNC. In-memory only: a replica process restart starts at
  offset 0 and always full-resyncs.
- READONLY guards (monoio dispatch, tokio sharded dispatch,
  handler_single) now serve SELECT on replicas — connection-state only,
  Redis parity.

New e2e replica_applies_multi_db_stream (RED before the fix — failed at
the READONLY SELECT assertion, then would misapply db-2 writes to
db 0): db-0 write flows, SELECT 2 + SET on the master, db-0 hop-back
sentinel; asserts the db-2 write lands in db 2 on the replica, did NOT
leak into db 0, and the db-0 sentinel did not leak into db 2.

Verification: lib 4111 green, replication_streaming 5/5 (incl. new
test), replication_graph 3/3, replication_hardening 5/5, tokio
replication_test 5/5, fmt clean, clippy clean on default and
runtime-tokio,jemalloc.

author: Tin Dang
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@pilotspacex-byte, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9944cd0d-761c-46ca-a46d-0adba39aadd7

📥 Commits

Reviewing files that changed from the base of the PR and between 0a8511e and 80f7779.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • src/replication/master.rs
  • src/replication/replica.rs
  • src/replication/state.rs
  • src/server/conn/handler_monoio/dispatch.rs
  • src/server/conn/handler_monoio/ft.rs
  • src/server/conn/handler_monoio/mod.rs
  • src/server/conn/handler_monoio/txn.rs
  • src/server/conn/handler_monoio/write.rs
  • src/server/conn/handler_sharded/dispatch.rs
  • src/server/conn/handler_single.rs
  • tests/replication_streaming.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v0.7-select-stream

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pilotspacex-byte pilotspacex-byte merged commit 25657c5 into main Jul 11, 2026
10 checks passed
@TinDang97 TinDang97 deleted the feat/v0.7-select-stream branch July 11, 2026 05:04
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.

2 participants