Skip to content

feat(replication): v0.7 R0+R0.5 — replica stream apply + vector/text index-plane sync#278

Merged
pilotspacex-byte merged 5 commits into
mainfrom
feat/v0.7-r0-replica-apply
Jul 11, 2026
Merged

feat(replication): v0.7 R0+R0.5 — replica stream apply + vector/text index-plane sync#278
pilotspacex-byte merged 5 commits into
mainfrom
feat/v0.7-r0-replica-apply

Conversation

@pilotspacex-byte

@pilotspacex-byte pilotspacex-byte commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Foundation for v0.7 replication GA: makes a REPLICAOF replica actually apply the stream end-to-end, then synchronizes the vector + text index plane (definitions and contents), and closes two PSYNC attach races found by adversarial review. Single-shard master scope (multi-shard PSYNC is R2).

Four commits, each self-contained:

1. faed291b — replica applies the replication stream (R0)

Before this, a REPLICAOF replica completed the PSYNC2 handshake but applied nothing — the FULLRESYNC RDB was discarded and the live stream was buf.clear()ed after advancing the offset. A freshly attached replica reported DBSIZE 0. Hidden because every replication_hardening test was #[ignore]d.

  • Replica now loads the full-resync RDB and parses+applies the live RESP stream (tracking SELECT), synchronously on the shard thread via the thread-local ShardSlice.
  • Fixes a wire bug: diskless full-resync RDB bulk was read as len + 2 (assuming a trailing \r\n diskless never sends), stealing the first two stream bytes.
  • MOVE / cross-db COPY … DB n apply via the same two-db cores the master intercept uses. New pure stream router (replication::apply, 8 unit tests) + black-box e2e.

2. e346d0e0--repl-backlog-size + kill-based hardening teardown

  • Implements --repl-backlog-size (Redis parity: raw bytes, default 1 MiB, clamped to the 16 KiB floor). Single source of truth in ReplicationState.backlog_capacity; carried in RegisterReplica so the SPSC lazy fallback can't diverge from the handshake allocation; reported by INFO.
  • Rewrites the replication_hardening teardown as a kill-on-drop Guard (moon has no working SHUTDOWN yet — tracked separately). Full hardening suite passes for the first time: 5/5.

3. a7aeeb61 — vector/text index-plane sync (R0.5)

Before this, a replica synced only the KV keyspace: FT index defs never left the master, and the replica's apply skipped the master's auto-index parity hooks — FT._LIST empty, FT.SEARCH erroring, even while hashes matched.

  • Snapshot leg: FULLRESYNC RDB carries vector+text index definitions as moon-private RDB AUX fields (moon-vector-defs / moon-text-defs, reusing the sidecar codecs). Standard RDB loaders skip AUX, so snapshots stay Redis-tool-compatible and the offset math is untouched. On load the replica drops all local indexes (full resync = authoritative replace), installs the master's defs, and backfills by rescanning matching HASH keys (restart semantics).
  • Live leg: successful FT.CREATE / FT.DROPINDEX / FT.CONFIG SET fan out verbatim via a new ShardMessage::ReplicateVerbatim (replication legs only — durability stays with the sidecar). The replica applies them through the same handlers and runs the master's parity hooks (HSET auto-index, DEL/UNLINK + HDEL tombstone, FLUSH* content clear) after every applied KV write.

4. f648c367 — close PSYNC attach races (adversarial-review C1/M1/m2/m3)

  • C1 — silent replica gap: the master registered the replica after the backlog catch-up read; a write drained in between reached neither RDB, catch-up, nor live fan-out (worst for FT.* which always crosses the SPSC queue). Now registers first; RegisterReplica carries a reply channel and the event loop answers with the exact offset where live fan-out begins. Catch-up sends precisely [snapshot_offset, registration_offset).
  • C1b — double-apply: the snapshot offset was read before an awaited FULLRESYNC write, letting a write land inside the RDB and above the advertised offset (INCR double-applied). Offset read + RDB capture now share one synchronous stretch.
  • Backlog eviction during catch-up now aborts loudly (replica retries a fresh resync) instead of silently skipping bytes.
  • Aux-less master (pre-R0.5) now warns before wiping local indexes; read_moon_aux validates RDB version bytes; the FT.* fanout hook skips the serialize+SPSC round trip until a replica/backlog exists.
  • New race-guard e2e replica_attach_races_live_ft_create: 30 FT.CREATEs racing a mid-stream attach, exact FT._LIST parity required.

Verification

  • 3/3 streaming e2e (replication_streaming), 5/5 hardening (replication_hardening), race guard 3/3.
  • 40 replication + 27 redis_rdb unit tests.
  • cargo fmt --check, clippy -D warnings clean on default (monoio) + runtime-tokio,jemalloc.
  • Full --no-fail-fast suite: only pre-existing macOS-only failures (memory_prometheus_kinds, control-confirmed at merge base) — no regressions from this branch.

Scope / follow-ups

Single-shard master (R0/R0.5). Multi-shard PSYNC (R2, #20), real WAIT/ACK (R1, #19), full graph-plane sync (#25), multi-db SELECT streaming (#22), and read-only-replica SELECT (#23) are tracked separately.

Depends on #277 (CLIENT TRACKING monoio fix) landing on main — will rebase after.

Summary by CodeRabbit

  • New Features
    • Added configurable replication backlog sizing with a 1 MiB default.
    • Replicas now apply full snapshots and live updates, including cross-database operations.
    • Vector and text index definitions and updates are replicated and restored during synchronization.
  • Bug Fixes
    • Improved replica attach ordering and backlog recovery to prevent missed updates.
    • Fixed diskless full-resynchronization payload handling and index consistency issues.
  • Tests
    • Added end-to-end coverage for snapshot loading, streaming replication, index synchronization, and attach race conditions.

@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 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 50 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: f031799b-c4b3-48cc-8a6b-2f20292c3403

📥 Commits

Reviewing files that changed from the base of the PR and between b06b438 and 1ea05f9.

📒 Files selected for processing (22)
  • CHANGELOG.md
  • src/config.rs
  • src/main.rs
  • src/persistence/redis_rdb.rs
  • src/replication/apply.rs
  • src/replication/handshake.rs
  • src/replication/master.rs
  • src/replication/mod.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/embedded.rs
  • src/server/listener.rs
  • src/shard/dispatch.rs
  • src/shard/spsc_handler.rs
  • tests/mq_integration.rs
  • tests/observability_resident_bytes.rs
  • tests/replication_hardening.rs
  • tests/replication_streaming.rs
  • tests/txn_kv_wiring.rs
  • tests/workspace_integration.rs
📝 Walkthrough

Walkthrough

The PR adds configurable replication backlog sizing, closes inline PSYNC registration races, carries vector/text index definitions through RDB AUX fields, and implements single-shard replica application of full-resync snapshots and live RESP streams with index-maintenance hooks.

Changes

Replication configuration and state

Layer / File(s) Summary
Configurable backlog capacity
src/config.rs, src/replication/state.rs, src/main.rs, src/server/..., src/replication/handshake.rs, tests/...
Adds --repl-backlog-size, clamps and stores the value in ReplicationState, uses it for backlog allocation, and reports it through INFO replication.
Ordered registration and catch-up
src/replication/master.rs, src/shard/..., tests/replication_hardening.rs
Registers replicas before bounded backlog catch-up, returns the registration offset, aborts on eviction or short reads, and updates process teardown guards.
RDB index metadata and live FT replication
src/persistence/redis_rdb.rs, src/replication/master.rs, src/server/conn/handler_monoio/ft.rs, src/shard/...
Writes and reads vector/text index definitions as AUX fields, recreates and backfills indexes during snapshot loading, and streams FT definition mutations verbatim.
Replica snapshot and stream apply
src/replication/apply.rs, src/replication/replica.rs, tests/replication_streaming.rs
Loads diskless RDB payloads, parses complete RESP frames with SELECT tracking, applies commands locally, handles MOVE/COPY and index hooks, and adds ignored end-to-end streaming tests.

Estimated code review effort: 5 (Critical) | ~90+ minutes

Sequence Diagram(s)

sequenceDiagram
  participant Master
  participant Replica
  participant Shard
  participant Database
  Master->>Replica: FULLRESYNC RDB payload
  Replica->>Shard: load_snapshot
  Shard->>Database: load RDB and index definitions
  Master->>Replica: live RESP command stream
  Replica->>Shard: drain and apply commands
  Shard->>Database: update keys and indexes
Loading

Possibly related PRs

  • pilotspace/moon#100: Related MOVE and cross-database COPY replication application behavior.
  • pilotspace/moon#175: Related inline PSYNC FULLRESYNC snapshot generation in src/replication/master.rs.

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it omits the required Checklist, Performance Impact, and Notes sections from the template. Add the missing template sections with test status, performance impact or "None", and reviewer notes or follow-ups.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main replication and index-plane sync changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v0.7-r0-replica-apply

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.

TinDang97 added a commit that referenced this pull request Jul 11, 2026
…zers

PR #278 CI (Check + Check macOS) failed with E0063: the new
`repl_backlog_size` ServerConfig field (--repl-backlog-size, default
1 MiB) was missing from four struct literals that only compile under
--features runtime-tokio,jemalloc — invisible to default-feature
builds:

- tests/mq_integration.rs
- tests/txn_kv_wiring.rs
- tests/workspace_integration.rs (x2)

All four now set `repl_backlog_size: 1024 * 1024` (the CLI default).
Verified: `cargo check --tests --no-default-features --features
runtime-tokio,jemalloc` and default-feature `cargo check --tests`
both clean.

author: Tin Dang
…nd-to-end

Foundational fix for the v0.7 replication milestone: before this change a
REPLICAOF replica completed the PSYNC2 handshake but applied NOTHING —
run_handshake_and_stream discarded the FULLRESYNC RDB bulk (logged
"received … bytes" only) and stream_commands did buf.clear() after
advancing the offset. A freshly attached replica reported DBSIZE 0. The
gap was invisible because every replication_hardening.rs test is
#[ignore]d and replication_test.rs only covers handshake/INFO/READONLY.

What the replica now does (both runtimes, monoio + tokio):
- Loads the full-resync RDB snapshot into its local shard (clear + load).
- Parses the live RESP command stream incrementally and applies each
  write on the shard thread via the thread-local ShardSlice — no SPSC
  self-hop (the ChannelMesh has no self-slot). Tracks SELECT; skips
  PING/REPLCONF chatter. Bypasses the connection-layer read-only guard
  by construction, as a replica must.
- MOVE and cross-db COPY ... DB n are applied through the same two-db
  core helpers the master's handler-level intercept uses (generic
  dispatch cannot apply them; it errors on MOVE and mis-targets
  COPY..DB). No eviction gate on apply — the replica follows the master.
- A replicated command that fails to apply is logged loudly (silent
  divergence is the failure mode to fear), and a malformed frame drops
  the connection for a clean resync instead of guessing at resync points.

Wire bug fixed: the replica read the diskless FULLRESYNC RDB bulk as
len + 2 bytes, assuming a trailing CRLF that diskless replication does
not send — stealing the first two bytes of the command stream and
desyncing it. Now reads exactly len (capped at 64 GiB). The replication
offset advances by bytes CONSUMED by complete frames, not the raw socket
read count, so a partial trailing frame never skews the offset.

Guards: a multi-shard replica refuses to start replication loudly
(R0 topology is --shards 1) rather than diverging silently.

Scope: single-shard, logical db 0. The master's live fanout does not yet
stream SELECT (multi-db is task #22); per-shard WAIT/ACK (R1) and
multi-shard PSYNC (R2) build on this foundation.

Tests: new pure stream router replication::apply with 8 unit tests
(framing, partial frames, SELECT tracking, chatter skip, fatal parse);
new black-box e2e tests/replication_streaming.rs covering snapshot +
live SET/DEL + MOVE/COPY-DB with an in-order stream sentinel.

Part of v0.7 replication milestone (R0 → R1 → R2).
author: Tin Dang
…ardown

Two independent root causes made the replication_hardening suite unable
to pass, discovered by running it against the R0 binary with stderr
captured and stack sampling (the harness nulls server stderr):

1. --repl-backlog-size did not exist. full_resync_outside_backlog spawns
   its master with that flag, so the master exited at spawn with a clap
   error and the test failed its first sync assert against a dead server
   — a startup failure masquerading as a replication bug. The flag is
   Redis repl-backlog-size parity: per-shard backlog capacity in raw
   bytes (like --maxmemory), default 1 MiB, clamped to Redis's 16 KiB
   floor (the floor also guards ServerConfig's derived Default of 0).

   ReplicationState.backlog_capacity is now the single source of truth:
   - ensure_backlogs_allocated() reads it (capacity param removed; the
     two handshake-path callers previously hardcoded 1 MiB),
   - ShardMessage::RegisterReplica carries it so the SPSC lazy
     fallback-init cannot silently diverge from the handshake path,
   - INFO replication repl_backlog_size reports the real value instead
     of a hardcoded 1048576.

2. The harness teardown could never complete: it sent SHUTDOWN NOSAVE
   and blocked on Child::wait(), but moon has no working SHUTDOWN on the
   production path — the inline parse answers "unknown command", the
   dispatch arm at command/mod.rs:767 is an error stub, and no connection
   handler intercepts it. Every test therefore hung forever at cleanup,
   and a mid-test assert panic leaked live servers that wedged later
   tests. Teardown is now a kill-on-drop Guard (SIGKILL + wait), which is
   also panic-safe. Implementing a real SHUTDOWN [NOSAVE|SAVE] is tracked
   as follow-up work.

With both fixes plus R0, the full hardening suite passes for the first
time: 5/5 (partial resync, full resync outside backlog, network
partition recovery, replica kill-restart parity, replica promotion).

Tests: state.rs unit tests for the capacity default/clamp and for
eviction kicking in exactly at the configured capacity.

Part of v0.7 replication milestone (R0 → R1 → R2).
author: Tin Dang
…ontents)

Before this change a replica synchronized only the KV keyspace: FT index
definitions never left the master (FT.CREATE/FT.DROPINDEX/FT.CONFIG are
connection-layer commands that never reach wal_append_and_fanout), and the
replica's apply path ran generic dispatch only, skipping the master's
auto-index parity hooks. A replica answered FT._LIST empty and FT.SEARCH
with errors even while its hashes matched the master byte-for-byte.

Snapshot leg:
- The FULLRESYNC RDB now carries the master's vector + text index
  DEFINITIONS as moon-private RDB AUX fields (opcode 0xFA, keys
  moon-vector-defs / moon-text-defs), reusing the sidecar codecs
  (serialize_index_metas_v5 / serialize_text_index_metas). Standard RDB
  loaders skip unknown AUX keys, so the snapshot stays Redis-compatible
  and the replication offset math is untouched.
- On load the replica drops ALL local indexes (full resync = authoritative
  replace), installs the master's definitions, and backfills them by
  rescanning matching HASH keys — the same "restart semantics" rescan
  restart recovery performs (event_loop.rs phase 2).

Live leg:
- New ShardMessage::ReplicateVerbatim fans a pre-serialized command into
  the replication plane only (backlog + offset + replica streams; WAL/AOF
  deliberately None — defs are durable via the sidecars, an AOF copy would
  double-apply on recovery).
- The monoio connection layer pushes successful FT.CREATE / FT.DROPINDEX /
  FT.CONFIG SET through it (single-shard; multi-shard FT.* replication
  rides the R2 broadcast redesign; the tokio-sharded master already
  rejects PSYNC).
- The replica applies FT.* through the same ft_create/ft_dropindex/
  ft_config handlers (live FT.CREATE = no backfill, master parity), and
  runs the master's index-parity hooks after every applied KV write:
  HSET auto-index, DEL/UNLINK tombstone, HDEL vector-field tombstone,
  FLUSHDB/FLUSHALL content clear.

Tests (red/green TDD):
- New black-box e2e replica_syncs_vector_index_defs_and_contents
  (confirmed RED at step 1 before the fix): snapshot defs + KNN backfill,
  live HSET indexing, live DEL tombstone, live FT.CREATE streaming,
  FLUSHALL clears contents keeps defs.
- Unit tests for the RDB aux carrier: round-trip, loader-skips-aux,
  foreign/truncated-buffer rejection (no panic on any prefix cut).
- No regression: 41 replication unit tests, 2/2 streaming e2e,
  5/5 hardening suite, clippy clean on monoio + tokio feature sets.

Refs: task #24 (v0.7 R0.5), stacked on R0 (faed291, e346d0e)
author: Tin Dang
Fixes for the C1/M1 findings from the R0/R0.5 adversarial review, plus
two of its hygiene items.

C1 — registration-bounded catch-up (silent replica gap): the PSYNC task
read backlog catch-up bytes and only then registered the replica with
the event loop. Any write drained in between (KV Execute or the new
ReplicateVerbatim FT.* fanout) reached neither the RDB, nor the
catch-up read, nor live fan-out — the master answered +OK while the
new replica silently never learned about it. The master now registers
FIRST; RegisterReplica carries a reply channel and the event loop
answers with the shard offset at which live fan-out begins (read
synchronously between drains, so it is exact). Catch-up then sends
precisely [snapshot_offset, registration_offset): no gap, and bytes at
or above the registration offset arrive only via the replica channel
(the read is truncated, so nothing is delivered twice).

C1b — atomic snapshot capture: snapshot_offset was read at fn entry,
with the +FULLRESYNC write awaited before the RDB capture. A write
interleaving there landed inside the RDB AND above the advertised
offset, so catch-up re-delivered it — double-applying non-idempotent
commands (INCR) on the replica. The offset read and RDB capture now
share one synchronous stretch with no .await between them.

Also: backlog eviction during catch-up now aborts the sync loudly
(send_backlog_range bails; the replica retries a fresh full resync)
instead of the old silent `if let Some` skip.

M1 — a full resync whose RDB carries no moon index-def aux (pre-R0.5
master, def-serialization failure) silently wiped the replica's local
FT indexes. The authoritative replace now logs a warning with the
dropped counts when there is no replacement.

Hygiene (review m2/m3): read_moon_aux validates the RDB version bytes
like load_rdb (a foreign version would misalign the fixed 9-byte header
walk); the FT.* fanout hook checks replication_fanout_active (replica
registered or backlog allocated) before paying the serialize + SPSC
round trip — the backlog is allocated during the PSYNC handshake before
any snapshot capture, so a mutation racing a first-ever attach still
fans out.

The multi-shard RegisterReplica paths pass registered: None
(fire-and-forget, superseded by the R2 PrepareReplicaSync redesign).

Tests: new race-guard e2e replica_attach_races_live_ft_create — 30
FT.CREATEs from a side thread racing a mid-stream replica attach,
exact FT._LIST parity required (3/3 green). No regression: 3/3
streaming e2e, 5/5 hardening, 40 replication + 27 redis_rdb unit
tests, clippy clean on monoio + tokio feature sets.

Refs: task #24 follow-up; review findings C1/M1/m2/m3
author: Tin Dang
…zers

PR #278 CI (Check + Check macOS) failed with E0063: the new
`repl_backlog_size` ServerConfig field (--repl-backlog-size, default
1 MiB) was missing from four struct literals that only compile under
--features runtime-tokio,jemalloc — invisible to default-feature
builds:

- tests/mq_integration.rs
- tests/txn_kv_wiring.rs
- tests/workspace_integration.rs (x2)

All four now set `repl_backlog_size: 1024 * 1024` (the CLI default).
Verified: `cargo check --tests --no-default-features --features
runtime-tokio,jemalloc` and default-feature `cargo check --tests`
both clean.

author: Tin Dang
@TinDang97 TinDang97 force-pushed the feat/v0.7-r0-replica-apply branch from b06b438 to 1ea05f9 Compare July 11, 2026 00:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/replication/state.rs`:
- Around line 10-16: Fresh ReplicationBacklog instances are initialized at
offset zero instead of the current master offset. Update
ReplicationState::ensure_backlogs_allocated() and the lazy backlog creation in
spsc_handler to initialize both start_offset and end_offset from the current
master replication offset, including offsets set by seed_master_offset(), so
newly created backlogs align with PSYNC/FULLRESYNC state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c94d3362-4e27-409a-97b4-393a4e6d715c

📥 Commits

Reviewing files that changed from the base of the PR and between 3c51694 and b06b438.

📒 Files selected for processing (22)
  • CHANGELOG.md
  • src/config.rs
  • src/main.rs
  • src/persistence/redis_rdb.rs
  • src/replication/apply.rs
  • src/replication/handshake.rs
  • src/replication/master.rs
  • src/replication/mod.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/embedded.rs
  • src/server/listener.rs
  • src/shard/dispatch.rs
  • src/shard/spsc_handler.rs
  • tests/mq_integration.rs
  • tests/observability_resident_bytes.rs
  • tests/replication_hardening.rs
  • tests/replication_streaming.rs
  • tests/txn_kv_wiring.rs
  • tests/workspace_integration.rs

Comment thread src/replication/state.rs
Comment on lines +10 to +16
/// Default per-shard replication backlog capacity (1 MiB), Redis parity.
pub const DEFAULT_REPL_BACKLOG_SIZE: usize = 1024 * 1024;
/// Smallest accepted backlog capacity. Redis clamps `repl-backlog-size` to
/// 16 KiB; the floor also protects `ServerConfig`'s derived `Default` (0)
/// from allocating an evict-everything backlog.
pub const MIN_REPL_BACKLOG_SIZE: usize = 16 * 1024;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether backlog allocation seeds start/end offset to the current
# global replication offset, and locate all allocation/append call sites.
rg -n 'ensure_backlogs_allocated' -C4
echo "---append call sites---"
rg -n '\.append\(' src/shard/spsc_handler.rs -C6
echo "---issue_lsn call sites near append---"
rg -n 'issue_lsn' src/shard/spsc_handler.rs -C6
echo "---any seed-like method on ReplicationBacklog---"
rg -n 'fn (new|seed|set_start|set_end)' src/replication/backlog.rs -A10
echo "---REPLCONF handling on master side---"
rg -n 'REPLCONF' -i src/replication -C4

Repository: pilotspace/moon

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- files ---\n'
git ls-files 'src/replication/*' 'src/shard/*' | sed -n '1,200p'

printf '\n--- backlog outline ---\n'
ast-grep outline src/replication/backlog.rs --view expanded || true

printf '\n--- master outline ---\n'
ast-grep outline src/replication/master.rs --view expanded || true

printf '\n--- state outline ---\n'
ast-grep outline src/replication/state.rs --view expanded || true

printf '\n--- search allocation and seeding ---\n'
rg -n 'ensure_backlogs_allocated|ReplicationBacklog::new|backlog.*seed|seed_master_offset|master_repl_offset|start_offset|end_offset|backlog_off|repl_backlog' src/replication src/shard -C 3 || true

Repository: pilotspace/moon

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf -- '--- files ---\n'
git ls-files 'src/replication/*' 'src/shard/*' | sed -n '1,200p'

printf -- '\n--- backlog outline ---\n'
ast-grep outline src/replication/backlog.rs --view expanded || true

printf -- '\n--- master outline ---\n'
ast-grep outline src/replication/master.rs --view expanded || true

printf -- '\n--- state outline ---\n'
ast-grep outline src/replication/state.rs --view expanded || true

printf -- '\n--- search allocation and seeding ---\n'
rg -n 'ensure_backlogs_allocated|ReplicationBacklog::new|backlog.*seed|seed_master_offset|master_repl_offset|start_offset|end_offset|backlog_off|repl_backlog' src/replication src/shard -C 3 || true

Repository: pilotspace/moon

Length of output: 50371


Seed ReplicationBacklog from the current master offset when allocating it
ReplicationState::ensure_backlogs_allocated() and the lazy fallback in src/shard/spsc_handler.rs both create a fresh backlog with start_offset = end_offset = 0. If the master has already advanced (for example after prior writes or seed_master_offset()), the first replica can get a backlog window that no longer matches master_repl_offset, breaking PSYNC/FULLRESYNC catch-up. Seed the backlog at creation time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/replication/state.rs` around lines 10 - 16, Fresh ReplicationBacklog
instances are initialized at offset zero instead of the current master offset.
Update ReplicationState::ensure_backlogs_allocated() and the lazy backlog
creation in spsc_handler to initialize both start_offset and end_offset from the
current master replication offset, including offsets set by
seed_master_offset(), so newly created backlogs align with PSYNC/FULLRESYNC
state.

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