You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(raft): implement snapshot creation and restore with logindex sync
This PR implements raft snapshot creation and installation with full
logindex synchronization support:
Snapshot creation:
- Checkpoint-based snapshot creation (tar packaging of checkpoint + metadata)
- Includes LogIndex collector state and cf_tracker state in snapshot metadata
- Version-aware snapshot metadata with forward compatibility (< instead of !=)
Snapshot installation:
- ArcSwap-based Storage hot-swapping with pause/resume coordination
- State updates (cf_tracker init, collector restore) happen BEFORE resume
to prevent applied_state() from returning stale values
- Refresh collector/cf_tracker from new storage after swap (not orphaned refs)
- Proper error cleanup via pause_controller resume
Logindex tracking:
- Event listener (DB-level, not CF-level) for tracking (log_index, seqno)
- LogIndexOfColumnFamilies tracks applied/flushed state per CF
- Mappings persisted to SST table properties via custom collector
- State sync during snapshot build/install for follower bootstrap
- watchdog uses std::thread::spawn (no tokio runtime in RocksDB callback threads)
CI fixes:
- License header truncation in snapshot_logindex_test.rs
- collapsible_match clippy warnings in hscan/sscan/zscan/message.rs
- is_retryable for LogIndex errors: distinguish transient vs structural
- CF metadata compile-time assertions to prevent drift
- Import consolidation, pub use re-exports, doc additions, test improvements
Co-Authored-By: github-actions
* chore: ensure .claude path is ignored
Cover both file and directory cases in .gitignore so personal Claude Code
configuration cannot be committed.
* docs: restore CLAUDE.md project documentation
Replace local-path content with the canonical project documentation
imported from main.
* refactor(raft): drop unused KiwiSnapshotBuilder logindex fields
The builder's collector and cf_tracker Arc fields were never read — the
build_snapshot path always re-fetches the collector from the live
storage. Carrying them is misleading: after install_snapshot swaps
storage, the builder fields would point at orphaned instances anyway.
Remove them so the builder only carries what it actually uses.
* fix(raft): export logindex collector state per Storage instance
Snapshot meta previously only captured instance 0's collector via
`get_logindex_collector(0)`, so when `db_instance_num > 1` the
(log_index, seqno) mappings written to non-zero instances were lost on
snapshot install and the follower's collector started empty for those
shards.
`RaftSnapshotMeta` now stores a `Vec<Vec<String>>` (outer index = Storage
instance id), and `KiwiSnapshotBuilder`/`install_snapshot` iterate every
instance's collector. The state machine no longer caches stale collector
references either — it always looks them up through `storage_swap` so
they remain valid after a snapshot-install hot swap.
* test(storage): keep TempDir alive in storage_basic_test
`tempfile::tempdir().unwrap().path().to_path_buf()` drops the TempDir
guard before `Storage::open()` runs, so the directory was deleted from
underneath the test. Bind the guard so it lives until the end of the test.
* refactor(storage): extract commit-and-track-logindex helper
Move the (commit, latest_sequence_number, collector.update) sequence out
of `Storage::on_binlog_write` and into a new
`Redis::commit_batch_and_track_logindex` method. This gives us one place
to reason about the seqno-after-commit pattern and to harden later
(e.g. a per-instance write mutex or a future rust-rocksdb API exposing
the batch's own sequence number) without touching every caller. No
behavior change.
* fix(storage): wire flush_trigger to RocksDB and stop logging snapshot stub as info
`flush_trigger` was a `log::info!(...) + TODO`, so when the LogIndex
collector exceeded its bound the listener "asked" for a flush that
nothing performed — entries piled up. The trigger now resolves the CF
handle through a shared `OnceCell<Arc<DB>>` (populated post-open from
the same DB used by compaction filters) and calls `db.flush_cf`.
`snapshot_callback` still cannot be wired without a storage→raft
back-channel that does not exist today. Demote it to `log::debug!` and
spell out in the comment that it is intentionally a no-op while the
manual snapshot path remains the only trigger, instead of the
misleading "will be connected" comment.
* style: cargo fmt
* docs(storage): document single-writer invariant on on_binlog_write
Reviewer asked the call site to make the single-writer assumption explicit so
future changes don't accidentally introduce a second writer that would inflate
the captured seqno via latest_sequence_number().
* ci: skip snapshot_logindex_test under leak sanitizer
RocksDB integration tests are excluded from LSan/TSan upstream because
RocksDB internals trigger spurious leak reports. The new snapshot logindex
tests exercise the same Storage::open path and need the same exclusion.
* fix(raft): drop old Storage before restoring checkpoint
The previous block-scoped placeholder pattern relied on the comment 'old
Arc dropped at the end of this block', but `current_storage` is owned by
the outer scope, so it lived through restore_checkpoint_layout(). With
the old RocksDB handle still alive, restore could race with an open lock
on db_path. Explicitly drop current_storage right after the swap so the
restore runs with no live handle.
Reported by AlexStocks in PR #265 review (P0).
---------
Co-authored-by: lupengfan1 <lupengfan1@xiaomi.com>
0 commit comments