Repository navigation
Conversation
…y log entry. feat #66: Refactor the Errors to distinguish between protocol logic errors and system-level errors. * feat #43: When a Raft leader is elected, it should first send an empty log entry. 1. implement verify_leadership_in_new_term 2. refactor verify_leadership_in_new_term and enforce_quorum_consensus * feat #66: Refactor the Errors to distinguish between protocol logic errors and system-level errors.
…r-friendly StorageEngine and StateMachine * feat #59: Refactor state_machine_commit_listener into a separate function to improve visibility across all newly spawned threads. * feat #79: Add snapshot feature * fix #90: Refactor: Decouple Client Command Protocol from Raft Internal Log Payload Type * feat #89: Add auto-discovery support for new learner nodes * feat #101: Revised learner join process with promotion/fail semantics * feat #45: Implemented first/last index for term to resolve replication conflicts * fix #106: Retry leadership noop confirmation until timeout * feat #102: Automatic node removal * feat #109: Enable RPC connection cache * fix #107: 1. Add back rpc connection params 2. Change parking lot sync lock to Tokio async lock in RaftMembership 3. optimize MembershipGuard for performance. Remove read locker. * feat #110: 1. add new events SnapshotCreated and LogPurgeCompleted 2. decouple Leader snapshot create to several events 3. refactor raft log index generation from pre_allocate_raft_logs_next_index to pre_allocate_id_range 4. optimize LocalLogBatch structure - based on performance analysis * feat #119: Make the StorageEngine trait more developer-friendly * feat #120: Make the StateMachine trait more developer-friendly * fix #121: Election: higher term vote request should be updated immediately and reset voted_for * feat #122: Refactor compress and decompress snapshot logic from StateMachine to StateMachineHandler * fix #123: Replace global tracing log with trace_test crate * feat #125: Make rocksdb adaptor as feature. Move sled implementation as example
…tart when path is already locked by another node
* refs #138: optimize to use long lived peer tasks for append entries request only * refs #138: try to fix race issue - Multiple threads can pass the initial check and all create new appenders * feat #138: 1. introduce crossbeam 2. optimize buffered_raft_log by creating a flush worker pool with configurable number of workers to handle persistence operations * feat #138: optimize flush_workers as configure * feat #138: optimize channel_capacity into configure * feat #139: work on a reliable monitoring metric for resource utilization * feat #140: Optimize proto bytes fields to use bytes::Bytes * feat #140: switch Into<Bytes> to AsRef<[u8]> * fix #141: Optimize RocksDB write path and Raft log loop for lower latency * feat 142: Add Read Consistency Policy Support and Lease-Based Read Optimization * feat #142: update v0.1.4 bench report * feat #142: add client doc guide * feat #143: Refactor gRPC compression configuration for Raft transport (performance optimization) - step1: disable server side client rpc compress response * feat #143: Refactor gRPC compression configuration for Raft transport (performance optimization) * feat #143: add performance diagram * feat #138: ready to publish v0.1.4 * feat #138: add ut * fix #146: buffered_raft_log.rs Code Reviews * fix #146: update bench report * fix #145: Bug: Undefined Behavior from Incorrect Lifetime Conversion in Mmap Zero-Copy Path * fix #148: fix rocksdb storage engine flush bug * fix #147: fix file state machine bug
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Changes: - Remove commented legacy package config from workspace Cargo.toml - Update README benchmark reference to v0.2.0 (dengine_comparison_v0.2.0.png) - Upgrade rustfmt edition from 2021 to 2024 - Clean up outdated v0.2.0 benchmark reports (report_20251205/09/10.md) - Add final v0.2.0 benchmark report and comparison chart - Sync version references across all example Cargo.toml files
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
Makefile (1)
154-155: Fix duplicate comment for clippy target.Lines 154-155 contain duplicate comment lines describing the
clippytarget, which may confuse the help parser. Keep a single, descriptive comment.Apply this diff:
-## clippy Run Clippy linter (fail on warnings with -D flag) -## clippy Run Clippy linter on all crates +## clippy Run Clippy linter on all crates (fail on warnings)
🧹 Nitpick comments (3)
benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md (1)
174-174: Minor grammar refinement: "a small number of keys" → "10 keys".At line 174, the phrase "a small number of keys (10)" can be tightened to simply "10 keys" or "few keys" for clearer, more direct prose.
README.md (1)
80-80: Clarify benchmark methodology and source attribution.The footnote "*Benchmarks show 51% higher write throughput vs etcd..." references specific numbers but doesn't clearly link to the detailed benchmark report. Consider explicitly directing readers to
benches/d-engine-bench/reports/v0.2.0/for the comprehensive methodology and environment details.Consider updating to: "Benchmarks show 51% higher write throughput vs etcd in test environments (see
benches/d-engine-bench/reports/v0.2.0/for methodology and environment details). Production results vary by hardware."Makefile (1)
1-576: Approve comprehensive Makefile structure.The Makefile provides excellent developer experience with:
- Clear organization across logical sections (quality, build, test, docs, security, cleanup, troubleshooting)
- Detailed help output with color-coded messages and workflow examples
- Environment validation and workspace structure checks
- Comprehensive test targets (unit, integration, doc, examples, detailed output, verbose, all)
- Documentation generation with browser automation
- Security audit targets
- Composite workflows for common tasks (pre-release, check-all-projects)
- Color-coded output for improved readability
The overall structure and functionality are well-designed and should significantly improve the development experience.
Consider addressing the static analysis warnings for consistency: while body length warnings are acceptable and don't affect functionality, ensuring all targets are declared
.PHONYis a Makefile best practice that improves portability and clarity.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
benches/d-engine-bench/Cargo.lockis excluded by!**/*.lockbenches/d-engine-bench/reports/v0.2.0/dengine_comparison_v0.2.0.pngis excluded by!**/*.png
📒 Files selected for processing (5)
Cargo.toml(2 hunks)Makefile(1 hunks)README.md(4 hunks)benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md(1 hunks)benches/d-engine-bench/rustfmt.toml(1 hunks)
🧰 Additional context used
🪛 checkmake (0.2.2)
Makefile
[warning] 54-54: Target body for "help" exceeds allowed length of 5 (35).
(maxbodylength)
[warning] 407-407: Target body for "audit" exceeds allowed length of 5 (6).
(maxbodylength)
[warning] 416-416: Target body for "deny" exceeds allowed length of 5 (6).
(maxbodylength)
[warning] 448-448: Target body for "troubleshoot" exceeds allowed length of 5 (34).
(maxbodylength)
[warning] 18-18: Missing required phony target "clean"
(minphony)
[warning] 115-115: Target "install" should be declared PHONY.
(phonydeclared)
[warning] 397-397: Target "doc" should be declared PHONY.
(phonydeclared)
🪛 LanguageTool
benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md
[style] ~174-~174: Specify a number, remove phrase, use “a few”, or use “some”
Context: ...st simulates contention scenarios where a small number of keys (10) receive high request volume. ...
(SMALL_NUMBER_OF)
🪛 markdownlint-cli2 (0.18.1)
benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md
178-178: Multiple headings with the same content
(MD024, no-duplicate-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: audit
- GitHub Check: Analyze (rust)
- GitHub Check: audit
🔇 Additional comments (4)
Cargo.toml (2)
50-76: Approve profile configuration tuning.The profile configurations are well-tuned for different build scenarios:
- dev: Low optimization (opt-level=1) for fast iteration, incremental compilation enabled
- release: Aggressive optimization (opt-level='z' for size, LTO, single codegen unit) for production
- test: Balanced approach with incremental compilation and multi-core codegen
This is a good setup for a Rust workspace.
2-10: All workspace members are correctly declared and paths are valid.Verification confirms all 6 members exist with proper Cargo.toml files: d-engine, d-engine-proto, d-engine-core, d-engine-client, d-engine-docs, and d-engine-server. Each crate name matches its path reference.
README.md (1)
10-11: Verify new public APIs and features are properly exported.The README introduces several new public APIs and features that must exist and be properly exported:
- EmbeddedEngine (lines 22, 107, 116): Public struct in
d-engine-serveratsrc/embedded/mod.rs, re-exported viad-engine::prelude- LocalKvClient (line 23): Public struct in
d-engine-server, re-exported viad-engine::prelude- prelude module (line 101): Public module at
d-engine/src/lib.rs:203that exports EmbeddedEngine, LocalKvClient, Node, NodeBuilder, and related types (conditional on features)- Feature flags (lines 48, 51):
clientandserverfeatures properly defined ind-engine/Cargo.tomlas optional dependencies- TTL/Lease (lines 20-21): Lease trait re-exported from
d-engine-core, withDefaultLeaseimplementation ind-engine-server/src/storage/lease.rs- Watch API (lines 20-21): Implemented via
watch()method inEmbeddedEngineatsrc/embedded/mod.rs:322andleader_notifier()for leader change notificationsAll mentioned example directories exist:
examples/quick-start/,examples/single-node-expansion/,examples/sled-cluster/,examples/three-nodes-cluster/.Makefile (1)
18-22: Add missing.PHONYdeclarations.Several targets are missing from the
.PHONYdeclaration at lines 18-22, includinginstall(line 115) anddoc/doc-check(lines 397-400). Update the.PHONYlist to include all non-file targets.Apply this diff to add missing phony targets:
.PHONY: help all check fmt fmt-check clippy clippy-fix test test-unit \ test-integration test-doc bench clean doc build build-release \ pre-release install check-env audit deny troubleshoot \ test-crate test-examples test-detailed \ - docs docs-all docs-check docs-check-all + docs docs-all docs-check docs-check-all \ + install-tools doc doc-check check-workspace check-examples \ + check-benches check-all-projects build-examples fmt-fix \ + clippy-excluded test-verbose fix clean-depsLikely an incorrect or invalid review comment.
- Fix typo: rename backgroup → background in snapshot transfer - Remove obsolete storage_test.rs - Update client error handling and KV operations - Fix network module exports - Update candidate state and state machine handler - Fix single-node cluster examples documentation - Update benchmark workflow configuration
Cache cluster metadata in LeaderState to eliminate 3 async calls per write.
Performance: +2-3% throughput, -13-21% p99 latency vs v0.2.0
Changes:
- Add ClusterMetadata { is_single_node, total_voters } cache
- Initialize on leader election, update on membership changes
- Pass to replication handler to avoid repeated queries
- Fix 6 test failures due to missing metadata initialization
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
d-engine-client/src/grpc_kv_client.rs (1)
376-395: Potential panic if channel pool is empty.
rng.gen_range(0..channels.len())will panic with an empty range ifget_all_channels()returns an empty vector (e.g., during initialization or when all nodes are disconnected).Add a guard for empty channels:
pub(super) async fn make_client( &self ) -> std::result::Result<RaftClientServiceClient<Channel>, ClientApiError> { let client_inner = self.client_inner.load(); // Balance from read clients let mut rng = StdRng::from_entropy(); let channels = client_inner.pool.get_all_channels(); + if channels.is_empty() { + return Err(ClientApiError::Network("No available channels".into())); + } let i = rng.gen_range(0..channels.len()); let mut client = RaftClientServiceClient::new(channels[i].clone());d-engine-core/src/replication/mod.rs (1)
189-220: Docs referenceAppendEntriesResponse::{success,higher_term,conflict}—likely stale after helper removal.
check_append_entries_request_is_legal’s docs still point at constructor-style APIs that (per this PR) were removed fromAppendEntriesResponse. Update doc return bullets to match the new construction path (e.g., “returns anAppendEntriesResponsewithsuccess_result/conflict_result/term semantics…”), so downstream implementors don’t chase non-existent helpers.d-engine-core/src/state_machine_handler/default_state_machine_handler.rs (1)
1199-1224: Critical: Hardcoded offset breaks configurable prefix.Line 1211 uses a hardcoded offset of 9 bytes, assuming the prefix is always "snapshot-". However, the function now accepts a configurable
snapshot_dir_prefixparameter. If a different prefix is used (e.g., "snap-", "backup-", or any non-9-character prefix), the parsing will:
- Slice at the wrong position, producing incorrect index/term values
- Potentially panic if the string is shorter than 9 characters
- Cause snapshot cleanup and management to fail
Apply this diff to use the dynamic prefix length:
- // Remove fixed parts - let core = &name[9..name.len()]; // "snapshot-".len() = 9, + // Remove prefix (now configurable) + let core = &name[snapshot_dir_prefix.len()..];d-engine-client/src/error.rs (1)
150-205: Preserve server message in fallback case for better diagnostics.The fallback handler (
_ => Self::Business) dropsstatus.message()when mapping unhandled status codes, replacing it only with a code debug representation. This makes diagnosing server-side failures harder. Include the original message:_ => Self::Business { code: ErrorCode::Uncategorized, - message: format!("Unhandled status code: {code:?}"), + message: format!("Unhandled status code: {code:?}: {message}"), required_action: None, },d-engine-core/src/raft_role/candidate_state.rs (1)
243-284: Avoidself.voted_for().unwrap()in vote-request handling (panic risk).
Ifvoted_for()can ever error (storage, state init), Candidate will crash instead of rejecting / stepping down cleanly.- if ctx.election_handler().check_vote_request_is_legal( + if ctx.election_handler().check_vote_request_is_legal( &vote_request, my_term, last_log_index, last_log_term, - self.voted_for().unwrap(), + self.voted_for()?, ) {d-engine-core/src/replication/replication_handler.rs (2)
564-614: New public API: avoidassert!panics ingenerate_new_entries.
assert!(!id_range.is_empty())(Line 579) will abort the process if allocation ever fails. For apub async fnreturningResult, prefer non-panicking handling (at minimumdebug_assert!+ a structured error path).
686-706: New public helper must not panic: remove.unwrap()on protobuf encoding.
cmd.encode(&mut buf).unwrap()(Line 699) can panic; for a public API, use the infallibleencode_to_vec()method instead.pub fn client_command_to_entry_payloads(commands: Vec<WriteCommand>) -> Vec<EntryPayload> { commands .into_iter() .map(|cmd| { - let mut buf = BytesMut::with_capacity(cmd.encoded_len()); - cmd.encode(&mut buf).unwrap(); - EntryPayload { - payload: Some(Payload::Command(buf.freeze())), + payload: Some(Payload::Command(cmd.encode_to_vec().into())), } }) .collect() }
♻️ Duplicate comments (2)
d-engine-core/src/network/mod.rs (1)
9-13: Typo fix from past review has been addressed.The module has been correctly renamed from
backgroup_snapshot_transfertobackground_snapshot_transfer.d-engine-client/src/error.rs (1)
104-147: Default transport failure: make invalid-URI detection robust + align retry hint.
err.to_string().contains("invalid uri")is case-sensitive; tonic errors often say “invalid URI”, so this can silently misclassify.- The fallback now correctly uses
Uncategorized(good), butretry_after_ms: Some(5000)implies retryability despite being “unclassified”; past guidance suggestedNone.@@ - // Check for invalid address errors - if err.to_string().contains("invalid uri") { + // Check for invalid address errors (be case-insensitive) + let err_s = err.to_string(); + if err_s.to_ascii_lowercase().contains("invalid uri") { return Self::Network { code: ErrorCode::InvalidAddress, - message: format!("Invalid address: {err}"), + message: format!("Invalid address: {err}"), retry_after_ms: None, // Not retryable - needs address correction leader_hint: None, }; } @@ Self::Network { code: ErrorCode::Uncategorized, message: format!("Transport error: {err}"), - retry_after_ms: Some(5000), + retry_after_ms: None, leader_hint: None, }
🧹 Nitpick comments (14)
d-engine-core/src/storage/lease.rs (3)
114-141: Clarify caller responsibilities for expired keys.Both
get_expired_keysandon_applyreturnVec<Bytes>of expired keys, but the documentation doesn't explicitly state what the caller should do with them. While it's implied that these keys should be deleted from the main storage, making this contract explicit would improve API clarity.Consider adding to the documentation:
/// # Returns -/// List of expired keys (keys are removed from lease tracking) +/// List of expired keys (keys are removed from lease tracking). +/// The caller is responsible for deleting these keys from the main storage.Similar clarification for
on_applywould be helpful.
182-188: Consider returning Result fromto_snapshotfor error transparency.The method returns
Vec<u8>rather thanResult<Vec<u8>>, preventing serialization errors from being propagated. The implementation in d-engine-server uses.unwrap_or_default(), which silently returns an empty snapshot on failure. This could mask snapshot creation issues.While
reloadproperly returnsResult<()>for deserialization errors, the asymmetry means snapshot creation failures go unnoticed until restoration fails.Consider changing the signature to:
-fn to_snapshot(&self) -> Vec<u8>; +fn to_snapshot(&self) -> Result<Vec<u8>>;This allows callers to handle serialization failures explicitly.
156-169: Documentation is slightly imprecise about performance.The documentation states "Should be O(1) or O(log N) at most" (line 165), but the implementation in d-engine-server samples the first 10 entries, which is O(1). The "or O(log N)" part might be unnecessary given the heuristic nature of this check.
Consider tightening the performance documentation:
/// # Performance -/// Should be O(1) or O(log N) at most. +/// Should be O(1) - typically a fast heuristic check (e.g., sampling a few entries).d-engine-client/src/grpc_kv_client.rs (1)
99-136: Consider extracting shared write logic to reduce duplication.The
put_with_ttlimplementation mirrorsputexcept for theWriteCommandconstruction. The request building, leader client acquisition, and response handling are duplicated.Consider extracting a helper method:
+ async fn execute_write(&self, commands: Vec<WriteCommand>, op_name: &str) -> std::result::Result<(), ClientApiError> { + let client_inner = self.client_inner.load(); + let request = ClientWriteRequest { + client_id: client_inner.client_id, + commands, + }; + let mut client = self.make_leader_client().await?; + match client.handle_client_write(request).await { + Ok(response) => { + debug!("[:KvClient:{}] response: {:?}", op_name, response); + response.get_ref().validate_error() + } + Err(status) => { + error!("[:KvClient:{}] status: {:?}", op_name, status); + Err(status.into()) + } + } + }This would simplify
put,put_with_ttl, anddeleteto single command construction + helper call.d-engine-client/src/error.rs (3)
55-98: Delete commented-out public enums instead of keeping them in-tree.
Large commented blocks (NetworkErrorType/ProtocolErrorType/etc.) add noise and tend to rot; rely on git history or reintroduce with a proper deprecation cycle if needed.
239-275: Avoid manual “JSON-like” parsing forx-raft-leadermetadata.
The current split-by-,/ split-by-:parser will break if values contain commas/colons, or if encoding changes slightly. If the header is JSON, parse it as JSON; if it’s not JSON, standardize it (e.g., base64 JSON) or use a structured delimiter format.@@ fn parse_leader_from_metadata(status: &Status) -> Option<LeaderInfo> { status .metadata() .get("x-raft-leader") .and_then(|v| v.to_str().ok()) - .and_then(|s| { - // Manually parse JSON-like string - let mut id = None; - let mut address = None; - let mut last_contact = None; - - // Remove whitespace and outer braces - let s = s.trim().trim_start_matches('{').trim_end_matches('}'); - - // Split into key-value pairs - for pair in s.split(',') { - let pair = pair.trim(); - if let Some((key, value)) = pair.split_once(':') { - let key = key.trim().trim_matches('"'); - let value = value.trim().trim_matches('"'); - - match key { - "id" => id = Some(value.to_string()), - "address" => address = Some(value.to_string()), - "last_contact" => last_contact = value.parse().ok(), - _ => continue, - } - } - } - - Some(LeaderInfo { - id: id?, - address: address?, - last_contact: last_contact?, - }) - }) + .and_then(|s| serde_json::from_str::<LeaderInfo>(s).ok()) }
277-397: Consider mappingErrorCode::UncategorizedtoGeneral(notBusiness).
Right nowErrorCode::UncategorizedbecomesClientApiError::Business, which can drive callers toward “client must change behavior” semantics for unknown failures.Generalseems more appropriate unless you have a specific reason.d-engine-client/src/pool.rs (1)
153-187: Improve final failure signal fromload_cluster_metadataby including last error.
Currently you log each failure but return a genericClusterUnavailable. Consider capturing the last error string and embedding it inClientApiError(or attach as context) to help operators.d-engine-core/src/raft_role/role_state.rs (1)
322-353:create_not_leader_response()likely does an avoidable O(n) scan; prefer a direct leader lookup if Membership supports it.
Right now it awaitsmembers()theniter().find(...). If there’s aretrieve_node_meta(id)/map-like accessor, it’ll be cheaper and clearer.d-engine-core/src/raft_role/candidate_state.rs (1)
571-575:StepDownSelfRemovedasunreachable!()is OK, but consider a defensive fallback in release builds.
If routing ever regresses and this becomes reachable, this will crash the node. Awarn!+BecomeFollower(None)fallback can make the system more resilient.d-engine-core/src/raft_role/leader_state.rs (2)
1235-1253: Step-down on self-removal is good; consider also clearingcurrent_leader_idto avoid stale redirection.
After stepping down, clients hitting this node could still be redirected to the old leader id until the next heartbeat updates state elsewhere.RaftEvent::StepDownSelfRemoved => { + self.shared_state().clear_current_leader(); warn!( "[Leader-{}] Removed from cluster membership, stepping down to Follower", self.node_id() );
1008-1032:snapshot_in_progressuses mixed orderings (Acquire/Release then SeqCst); consider standardizing.
Not wrong, but it’s harder to reason about than sticking to Acquire loads + Release stores (or SeqCst consistently).d-engine-core/src/replication/replication_handler.rs (2)
543-549: New public struct: add#[non_exhaustive](or keep fields private) to reduce semver breakage.
MakingReplicationDataand all fieldspublocks you into this shape;#[non_exhaustive]is a low-cost guardrail.#[derive(Debug)] +#[non_exhaustive] pub struct ReplicationData { pub leader_last_index_before: u64, pub current_term: u64, pub commit_index: u64, pub peer_next_indices: HashMap<u32, u64>, }
617-631: Publicprepare_peer_entriesreturnsDashMap+ clones input—document ownership/perf expectations.
Now that this ispub, callers may assume it’s cheap; but it clonesnew_entriesand later clones per peer. A short doc note (or a future API taking shared buffers) will prevent misuse.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.github/workflows/benchmark.yml(1 hunks)d-engine-client/src/error.rs(8 hunks)d-engine-client/src/grpc_kv_client.rs(6 hunks)d-engine-client/src/kv_client.rs(1 hunks)d-engine-client/src/pool.rs(10 hunks)d-engine-client/src/pool_test.rs(1 hunks)d-engine-core/src/network/background_snapshot_transfer.rs(1 hunks)d-engine-core/src/network/background_snapshot_transfer_test.rs(2 hunks)d-engine-core/src/network/mod.rs(7 hunks)d-engine-core/src/raft.rs(10 hunks)d-engine-core/src/raft_role/candidate_state.rs(16 hunks)d-engine-core/src/raft_role/leader_state.rs(40 hunks)d-engine-core/src/raft_role/mod.rs(9 hunks)d-engine-core/src/raft_role/role_state.rs(7 hunks)d-engine-core/src/replication/mod.rs(3 hunks)d-engine-core/src/replication/replication_handler.rs(10 hunks)d-engine-core/src/state_machine_handler/default_state_machine_handler.rs(12 hunks)d-engine-core/src/storage/lease.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- d-engine-client/src/pool_test.rs
- d-engine-core/src/raft.rs
🧰 Additional context used
🧬 Code graph analysis (4)
d-engine-core/src/network/mod.rs (1)
d-engine-core/src/network/background_snapshot_transfer.rs (1)
mpsc(83-83)
d-engine-core/src/replication/replication_handler.rs (1)
d-engine-core/src/utils/cluster.rs (1)
is_majority(8-13)
d-engine-core/src/storage/lease.rs (2)
d-engine-server/src/storage/lease.rs (10)
register(200-214)unregister(221-227)is_expired(234-243)get_expired_keys(252-269)on_apply(280-293)has_lease_keys(300-302)may_have_expired_keys(312-329)len(336-338)to_snapshot(345-354)reload(363-395)d-engine-core/src/storage/state_machine.rs (2)
len(62-62)is_empty(66-68)
d-engine-core/src/raft_role/mod.rs (2)
d-engine-core/src/raft_role/leader_state.rs (6)
new(105-113)new(2008-2049)update_voted_for(290-295)voted_for(284-286)match_index(327-332)next_index(297-306)d-engine-core/src/raft_role/role_state.rs (7)
update_voted_for(295-300)current_term(230-232)voted_for(289-291)commit_index(242-244)match_index(65-71)node_id(42-44)next_index(49-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: audit
- GitHub Check: Lint and Format Check
- GitHub Check: Analyze (rust)
- GitHub Check: Lint and Format Check
- GitHub Check: audit
🔇 Additional comments (36)
.github/workflows/benchmark.yml (2)
3-4: Permissions block properly implemented, addressing previous CodeQL feedback.The explicit
permissions: contents: readblock correctly restricts the GITHUB_TOKEN to read-only access, which aligns with the principle of least privilege. This addresses the earlier security concern flagged by CodeQL.
1-83: Well-structured benchmark workflow with sound caching and artifact strategies.The workflow is well-designed for performance testing:
- Scheduling & triggers (lines 7–13): Weekly runs with manual override provide flexibility for on-demand benchmarking.
- Rust setup (lines 30–36): Stable toolchain + protobuf-compiler installation is correct for the workspace.
- Caching (lines 38–42): Custom cache key
benchmark-*appropriately isolates benchmark cache from test runs.- Full history checkout (line 28):
fetch-depth: 0enables meaningful performance comparisons across commits.- Output & artifacts (lines 62–83): Criterion results + log tee + 30-day retention and clear summary messaging support post-hoc analysis and iteration.
d-engine-core/src/network/background_snapshot_transfer.rs (1)
30-33: LGTM! Import migration to external proto crate is consistent.The migration from
crate::proto::server::storage::*tod_engine_proto::server::storage::*aligns with the PR's architectural goal of using the external proto crate.d-engine-core/src/network/background_snapshot_transfer_test.rs (2)
24-46: LGTM! Well-structured test helper for creating snapshot streams.The helper function properly:
- Creates test chunks with metadata on seq 0
- Maps tonic Status errors to NetworkError appropriately
- Returns a properly boxed stream
19-22: Import migration to d_engine_proto is consistent with main module.The test file properly imports
SnapshotChunkandSnapshotResponsefromd_engine_proto::server::storage, matching the production code's import paths.d-engine-core/src/network/mod.rs (4)
36-44: Verify public API exposure is intentional forAppendResults.The visibility has been widened from
pub(crate)topub. Ensure this struct is properly documented and re-exported from the crate's public API surface if intended for external use.
93-98: LGTM! Extended automock availability for integration testing.The
#[cfg_attr(any(test, feature = "test-utils"), automock)]pattern correctly enablesMockTransportboth for internal tests and for downstream crates using thetest-utilsfeature.
24-32: Import migration to d_engine_proto is consistent and complete.All protobuf types used in the Transport trait have been properly migrated from internal paths to
d_engine_proto::server::*.
305-309: Confirmgrpc_task_with_timeout_and_exponential_backoffshould be public API.This utility function is now
pubinstead ofpub(crate). While useful for consumers implementing custom gRPC operations, verify this is intentionally part of the v0.2.0 public API and document its usage accordingly.d-engine-core/src/storage/lease.rs (2)
1-24: LGTM! Excellent module documentation.The module documentation provides clear design philosophy, architecture overview, and concurrency guidelines. This sets appropriate expectations for trait implementers.
73-87: Parameter type choice is appropriate.The
registermethod takes ownership ofkey: Byteswhile other methods (likeunregister,is_expired) use&[u8]. This asymmetry is intentional and efficient:registerneeds to store the key, while lookups only need a reference.d-engine-client/src/kv_client.rs (2)
1-51: Well-documented trait interface with clear consistency guarantees.The module documentation clearly explains the design principles, implementations, and performance characteristics. The trait's thread safety requirements (
Send + Sync) and consistency guarantees are well-specified.
74-197: LGTM!Method signatures are consistent with idiomatic Rust async trait patterns. Using
impl AsRef<[u8]> + Sendprovides flexibility for callers while maintaining type safety. The idempotent delete semantics are correctly documented.d-engine-client/src/grpc_kv_client.rs (3)
14-36: LGTM!Imports are well-organized with clear aliasing (
KvClient as CoreKvClient). The struct definition withArc<ArcSwap<ClientInner>>enables efficient concurrent access with runtime-updatable configuration.
310-358: LGTM!The watch implementation correctly uses load-balanced client selection since followers can serve watch streams. Documentation clearly notes the v1 limitation (no prefix/range watch support).
398-447: LGTM!The
CoreKvClienttrait implementation correctly delegates to the inherent methods and properly mapsClientResulttoBytes(extracting the.valuefield). Error propagation viaInto::intois clean and type-safe.d-engine-core/src/replication/mod.rs (2)
105-113: No action required; parameter change is complete and properly integrated.All implementors and call sites have been updated correctly. The single
ReplicationCoreimplementation inreplication_handler.rsincludes the newcluster_metadataparameter (line 65) and actively uses it on line 76 (let is_single_node = cluster_metadata.is_single_node;). The production call site inleader_state.rs(lines 1383-1389) properly passes&self.cluster_metadata, and all 30+ test cases pass the parameter correctly.
21-26: No issues found. Thetest-utilsfeature properly gatesmockallas an optional dependency inCargo.toml, and all#[cfg(any(test, feature = "test-utils"))]guards correctly restrict mockall imports to test/test-utils builds only. Theasync-traitcrate is a non-optional dependency (needed for all async trait definitions), sotonic::async_traitis unconditionally available—this is correct. Tonic v0.12.3 is declared with proper features. Test-only dependencies do not leak into production.d-engine-core/src/state_machine_handler/default_state_machine_handler.rs (5)
111-113: Good design for optional feature integration.The use of
Option<Arc<WatchManager>>provides zero-overhead when watch functionality is disabled, with clear documentation explaining the behavior.
186-198: LGTM: Watcher notification correctly guarded by success check.The previous concern about notifying watchers even on error has been properly addressed. The notification now only occurs when
apply_result.is_ok()is true, ensuring watchers are not notified about failed operations.
709-739: LGTM: Clean watcher notification implementation.The method correctly:
- Gracefully handles decode failures with
if let Ok- Uses pattern matching for operation variants
- Maintains performance with inline attribute and early returns
The silent skipping of decode errors is appropriate here, as not all entries may contain decodable WriteCommands.
746-796: LGTM: Constructor properly updated with test helper.The changes correctly:
- Add the
watch_managerparameter to the main constructor- Initialize all fields including the new watch_manager
- Provide a test-only
new_without_watchhelper for backward compatibility in testsThe
#[cfg(test)]attribute appropriately limits the helper to test builds.
400-476: LGTM: Snapshot cleanup properly parameterized.The cleanup method now correctly accepts a configurable
snapshot_dir_prefixparameter and threads it through toparse_snapshot_dirnamecalls, both for direct filenames and.tar.gzsuffixed variants.d-engine-client/src/pool.rs (2)
189-214: Good fix: “no leader” now maps to retryableClusterUnavailable.
This matches typical “election in progress / temporarily unavailable” semantics and should improve client retry behavior.
39-54: Returning/storingcurrent_leader_idlooks correct; ensure callers treat it as advisory.
current_leader_idis snapshotted at pool build/refresh time and used only during initialization to map node ID to leader address. It's exposed viaget_leader_id()for metadata queries, not for per-request routing. Staleness is handled naturally by subsequent RPC redirects when the leader changes.d-engine-core/src/raft_role/role_state.rs (3)
394-424: Leader discovery notification gating looks good; double-check correctness of “leader committed” semantics.
You now (1) atomically store leader_id, (2) persist/recordVotedFor{committed:true}, and (3) emitLeaderDiscoveredonly whenupdate_voted_for()reports a new committed transition. That’s a solid de-dupe strategy—just ensureSharedState::update_voted_for()is the single source of truth for “committed leader” and is persisted where required.
35-37: No action required — all callers ofupdate_voted_foralready properly handle theResult<bool>return type. The API migration is complete with no compilation breaks or missing updates in implementations, tests, or call sites.
89-96: This concern is already properly addressed in the code.
init_cluster_metadata()is correctly called in theRoleEvent::BecomeLeaderhandler (raft.rs:343) immediately after the Leader role is created and before any hot paths execute. The call sequence ensures cluster topology is cached beforeprocess_batchand other leader operations accesscluster_metadatafor quorum calculations. Error handling with the?operator propagates any failures, and the Leader implementation correctly populatesis_single_nodeandtotal_votersbased on the membership configuration.d-engine-core/src/raft_role/candidate_state.rs (1)
390-398: Returning NOT_LEADER with leader metadata on Candidate proposals is the right UX.
This aligns Candidate behavior with redirection and avoids clients waiting on election completion.d-engine-core/src/raft_role/mod.rs (3)
79-88: Atomic leader-id ordering upgraded to Acquire/Release: good.
This matches the intended “publish leader info / observe leader info” pattern for cross-thread visibility.Also applies to: 146-167
190-214:SharedState::update_voted_for()semantics look consistent with event-driven leader discovery.
The “only true on committed transition/leader-term change” contract is clear and matches how roles consume it.
48-61:HardState: Copyis valid;VotedForimplementsCopyand serde traits.
The proto-generatedVotedForstruct explicitly derivesClone, Copy, PartialEq, ::prost::Message, serde::Serialize, serde::Deserialize. All its fields (u32, u64, bool) are primitives, makingCopysafe.Option<VotedFor>is therefore alsoCopy. The current derives onHardStateare correct and the code compiles without issues.d-engine-core/src/raft_role/leader_state.rs (1)
500-520: Verify comment: ensure voters() excludes self and remove the off-by-one concern.The
voters.len() + 1calculation is correct—membership.voters()explicitly excludes the leader node (filtered bynode.id != self.node_id), so adding 1 accounts for self. This is not an off-by-one error.However, a maintainability concern remains:
update_cluster_metadata()only updatestotal_votersand does not recomputeis_single_node, whileinit_cluster_metadata()computes both. Sinceis_single_node_cluster()derives from the immutableinitial_cluster_size, desynchronization is impossible at runtime. Consider adding a comment inupdate_cluster_metadata()explaining thatis_single_nodeis immutable configuration and does not need recomputation, to prevent future confusion or refactoring mistakes.d-engine-core/src/replication/replication_handler.rs (3)
634-671:build_append_requestlogic looks consistent with follower “virtual log” handling.
Defaulting to(prev_log_index, prev_log_term) = (0, 0)aligns withcheck_append_entries_request_is_legal’s special-case accept path.
60-67: Verifyis_single_nodeconsistency with actual cluster membership (lines 74-96).
cluster_metadata.is_single_nodeis used to determine quorum behavior; if this field diverges from the authoritative membership state, replication can be incorrectly skipped. Ensure either: (1)is_single_nodeis derived fromMembershipat call time, or (2) there is an assertion verifying consistency withMembershipwhencluster_metadatais instantiated.
149-164: No action needed. Single-node commit index advancement is correctly implemented with a dedicated code path that does not depend on peer match indices.
| #[cfg_attr(any(test, feature = "test-utils"), automock)] | ||
| #[async_trait] |
There was a problem hiding this comment.
test-utils automock is enabled, but tests modules are still #[cfg(test)] only.
If the goal is to compile/use mocks under --features test-utils outside cfg(test) (e.g., integration tests in other crates), consider also widening local test module gating (e.g., batch_buffer_test) or documenting that test-utils only affects mock generation, not unit tests in this crate.
Also applies to: 13-15
🤖 Prompt for AI Agents
In d-engine-core/src/replication/mod.rs around lines 59-60 (and also apply same
change for lines 13-15), the automock attribute is enabled for the "test-utils"
feature but the test modules themselves remain gated with #[cfg(test)],
preventing mocks from being available when building with --features test-utils
outside of unit-test compilation; either change the test module gating to
#[cfg(any(test, feature = "test-utils"))] (or equivalent per-module cfg) so
mocks are compiled when the feature is enabled, or update crate documentation to
state that "test-utils" only controls mock generation and does not enable the
internal test modules — implement the cfg change for relevant test modules if
you want mocks usable by integration tests in other crates.
…ale cache Bug: update_cluster_metadata() only updated total_voters, missing is_single_node. Caused test_embedded_node_rejoin to hang intermittently. Fix: - Update BOTH is_single_node and total_voters in update_cluster_metadata() - Call update_cluster_metadata() after learner promotion - Add unit test to catch this bug
Race condition: gRPC servers start async, election RPCs timeout before ready. Solution: Increase test election timeout 100ms→2000ms, retries 3→5. Only affects tests/common/mod.rs test configuration.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
d-engine-core/src/raft_role/leader_state.rs (1)
1599-1608: Consider replacingprintln!with structured logging.While the "Plan B" print statements serve immediate visibility needs, consider using the existing tracing infrastructure (info!/warn!) for consistency and production deployability.
Also applies to: 1985-1992
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
d-engine-core/src/raft_role/leader_state.rs(40 hunks)d-engine-core/src/raft_role/leader_state_test.rs(1 hunks)d-engine-core/src/raft_role/mod.rs(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
d-engine-core/src/raft_role/mod.rs (2)
d-engine-core/src/raft_role/leader_state.rs (6)
new(105-113)new(2014-2055)update_voted_for(290-295)voted_for(284-286)match_index(327-332)next_index(297-306)d-engine-core/src/raft_role/role_state.rs (5)
update_voted_for(295-300)voted_for(289-291)match_index(65-71)node_id(42-44)next_index(49-55)
🪛 GitHub Actions: CI
d-engine-core/src/raft_role/leader_state_test.rs
[error] 11-11: Rustfmt would modify this file: formatting changes detected (diffs shown at lines 11, 18, 27, 34, 41). Run 'cargo fmt' to apply formatting.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: audit
- GitHub Check: Analyze (rust)
- GitHub Check: audit
🔇 Additional comments (9)
d-engine-core/src/raft_role/mod.rs (4)
77-170: LGTM: Hot-path leadership tracking with correct memory ordering.The atomic
current_leader_idfield with Acquire/Release semantics properly synchronizes with persistent state updates, addressing the previous review concern about memory ordering.
193-217: LGTM: Leader commitment detection logic is sound.The boolean return value correctly identifies transitions to a committed leader vote, enabling event-driven leader discovery without hot-path overhead.
310-368: LGTM: Test-only accessor methods properly gated.The
cfg(any(test, feature = "test-utils"))guards correctly restrict these internal accessors to test contexts.
15-15: LGTM: Public API expansion aligns with v0.2.0 objectives.The newly public exports (
ClusterMetadata,as_i32,current_term,QuorumVerificationResult) support the broader API surface documented in the release.Also applies to: 267-267, 305-305, 421-421
d-engine-core/src/raft_role/leader_state.rs (5)
116-127: LGTM: ClusterMetadata struct design is sound.The cached topology metadata approach avoids repeated async membership queries in hot paths, with clear documentation of immutability constraints.
1416-1427: LGTM: Single-node commit optimization is correct.Single-node clusters can commit immediately (quorum of 1), while multi-node clusters use the existing majority-based calculation. The cached metadata avoids async calls in this hot path.
1930-1933: LGTM: Cluster metadata updated after successful join.Updating the cached metadata after membership changes ensures the hot-path commit logic uses current topology information.
2440-2442: LGTM: Leader ID set immediately in role transition.Setting
current_leader_idatomically during theCandidateState→LeaderStateconversion ensures the hot-pathcurrent_leader()reads are correct from the moment the new leader is created.
500-520: > Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
cliff.toml (1)
66-67: Consider removing the redundant commit_parser rule.Line 67 with the
docs?regex pattern matches both "doc" and "docs" followed by an issue number, making line 66's^doc #\\d+:rule redundant. Both rules assign to the same "Documentation" group.If line 66 is intended only as a comment or historical reference, consider removing it to adhere to the DRY principle and simplify the configuration:
{ message = "^feat #\\d+:", group = "Features" }, { message = "^fix #\\d+:", group = "Bug Fixes" }, { message = "^refs #\\d+:", group = "Work in progress" }, - { message = "^doc #\\d+:", group = "Documentation" }, { message = "^docs? #\\d+:", group = "Documentation" }, { message = "^perf #\\d+:", group = "Performance" },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/commit-message-check.yml(1 hunks)cliff.toml(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/commit-message-check.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: audit
- GitHub Check: Analyze (rust)
- GitHub Check: audit
…tations - Add Project Status section to README (pre-1.0 + compatibility promise) - Simplify MIGRATION_GUIDE (remove NodeBuilder API, keep WAL format only) - Streamline CHANGELOG v0.2.0 highlights (code examples + concise features) - Update benchmark reports with actual test data (Dec 13, 2025) - Restructure performance report (TL;DR first, detailed results collapsed) Focus: Lower cognitive load for new users while maintaining transparency
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
259-259: Fix typo in license section.Line 259 contains "d-eninge" which should be "d-engine".
Apply this diff:
-d-eninge is licensed under the terms of the [MIT License](https://en.wikipedia.org/wiki/MIT_License#License_terms) +d-engine is licensed under the terms of the [MIT License](https://en.wikipedia.org/wiki/MIT_License#License_terms)
🧹 Nitpick comments (5)
benches/d-engine-bench/reports/v0.1.4/report_20251205_remeasured.md (1)
3-3: Optional: Apply consistent date formatting per style guide.LanguageTool suggests that some style guides recommend commas to set off the year in month-day-year date format. If your project follows a formal style guide, consider updating both date mentions.
Apply this diff to add commas after the year:
-**Date:** December 13, 2025 +**Date:** December 13, 2025,-**Test Date:** December 13, 2025 +**Test Date:** December 13, 2025,Also applies to: 185-185
d-engine-client/src/kv_client.rs (3)
20-25: Example return type inconsistent with actual API.The example uses
Result<()>but trait methods returnKvResult<()>. Consider updating for consistency:-//! async fn store_config<C: KvClient>(client: &C) -> Result<()> { +//! async fn store_config<C: KvClient>(client: &C) -> KvResult<()> {
80-105: Consider documenting TTL edge case behavior.The behavior of
ttl_secs = 0is not specified. Does it mean "no expiration" or "immediate expiration"? Clarifying this edge case in the documentation would help implementers maintain consistent semantics.
168-171: API signature inconsistency with other methods.
get_multirequires&[Bytes]while other methods acceptimpl AsRef<[u8]> + Send. This forces callers to pre-convert keys toBytesfor batch operations, whereas single-key operations accept byte slices directly.If this is intentional for performance (avoiding per-key allocations), consider documenting the rationale. Otherwise, a more flexible signature could improve ergonomics:
async fn get_multi<K: AsRef<[u8]> + Send>( &self, keys: &[K], ) -> KvResult<Vec<Option<Bytes>>>;benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md (1)
260-260: Minor style suggestion: Add comma after year in date.Some style guides suggest commas after the year in month-day-year format. Currently "December 13, 2025" could be "December 13, 2025," (comma before or after depending on guide).
This is purely stylistic and can be deferred.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
benches/d-engine-bench/reports/v0.2.0/dengine_comparison_v0.2.0.pngis excluded by!**/*.png
📒 Files selected for processing (6)
CHANGELOG.md(1 hunks)MIGRATION_GUIDE.md(1 hunks)README.md(4 hunks)benches/d-engine-bench/reports/v0.1.4/report_20251205_remeasured.md(1 hunks)benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md(1 hunks)d-engine-client/src/kv_client.rs(1 hunks)
🧰 Additional context used
🪛 LanguageTool
benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md
[style] ~260-~260: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ... v0.2.0 Report Date: December 13, 2025 Test Environment: Apple M2 Mac mi...
(MISSING_COMMA_AFTER_YEAR)
benches/d-engine-bench/reports/v0.1.4/report_20251205_remeasured.md
[style] ~3-~3: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...enchmark Report Date: December 13, 2025 Hardware: Apple Mac mini M2 (8-co...
(MISSING_COMMA_AFTER_YEAR)
[style] ~185-~185: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...ne v0.1.4 Test Date: December 13, 2025 Benchmark Runs: Single run per co...
(MISSING_COMMA_AFTER_YEAR)
🪛 markdownlint-cli2 (0.18.1)
MIGRATION_GUIDE.md
71-71: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: audit
- GitHub Check: Analyze (rust)
- GitHub Check: audit
🔇 Additional comments (12)
benches/d-engine-bench/reports/v0.1.4/report_20251205_remeasured.md (1)
1-187: Excellent benchmark documentation structure.This remeasured baseline report is well-organized and provides clear, reproducible performance data for v0.1.4. The sections are logically arranged, test commands are fully specified, and the comparison with the original report includes reasonable context for differences. This provides a solid foundation for comparing v0.2.0 performance.
d-engine-client/src/kv_client.rs (4)
32-52: Well-designed trait definition.Good use of
async_traitwith explicitSend + Syncbounds. The documentation clearly explains consistency guarantees and performance characteristics for each implementation.
74-78: LGTM!The generic bounds
impl AsRef<[u8]> + Sendprovide good flexibility for callers while maintaining type safety.
136-139: LGTM!Consistent API design with clear semantics for missing/expired keys returning
None.
173-197: LGTM!Idempotent delete semantics are appropriate for a distributed KV store, avoiding race conditions and simplifying retry logic.
MIGRATION_GUIDE.md (1)
42-90: Migration strategies are clear and well-documented.The three migration options (Clean Start, Rolling Upgrade, Snapshot-based) are practical, actionable, and appropriately scoped for different deployment scenarios. The verification section and timeline are helpful for operators. No concerns.
CHANGELOG.md (1)
7-86: v0.2.0 release entry is comprehensive and well-structured.The new release notes clearly articulate feature value, provide actionable examples, explicitly call out breaking changes with migration guidance, and back performance claims with benchmarks. Structure and tone are professional. No concerns.
benches/d-engine-bench/reports/v0.2.0/report_v0.2.0_final.md (2)
3-20: TL;DR is clear and provides honest hardware context.The executive summary effectively communicates v0.2.0 advantages while explicitly noting hardware differences between test setups. This transparency is important for readers evaluating the results. Good practice.
86-134: Reproduction steps are detailed and executable.The benchmark commands include all necessary flags (endpoints, connections, clients, key/value sizes, consistency levels) for reproducibility. Commands span single-client, high-concurrency, and hot-key scenarios. Clear structure. No concerns.
README.md (3)
37-53: v0.2.0 features and core capabilities are well-articulated.The distinction between new features (Modular Workspace, TTL/Lease, Watch API, etc.) and core capabilities (Single-Node Start, Strong Consistency, Pluggable Storage) is clear and helpful. Feature descriptions are concise with concrete examples (e.g., "<0.1ms latency" for LocalKvClient).
15-31: Project Status section sets proper expectations for pre-1.0 users.Clearly states that breaking changes may occur between minor versions, explicitly references MIGRATION_GUIDE.md for compatibility promise, and provides timeline context (v1.0 expected Q2 2026). This is valuable transparency for production users. Approach is professional and encourages adoption while managing expectations.
57-116: Quick Start reorganization is significantly improved.Splitting installation guidance into three distinct modes (Embedded/Standalone/Custom Storage) with use cases, TOML examples, and "when to use" guidance resolves the ambiguity that likely existed in v0.1.x. Each option includes:
- Clear TOML syntax for each mode
- Use case examples (e.g., "Go/Python/Java apps for Standalone")
- Performance note for context
- Link to relevant documentation
This structure is intuitive for both new users and experienced operators choosing deployment architecture.
…ments This PR consolidates architecture review improvements and critical bug fixes for v0.2.0 release. ## 🎯 Critical Fixes - **#212**: Fix learner promotion stuck (voter count + role transition bugs) - **#218**: Fix leader next_index initialization for new learners - **#222**: Return NOT_LEADER with leader metadata for client redirection - **#209**: Fix node restart wait_ready() timeout (leader notification race) ## ✨ Feature Additions - **#213**: Implement READ_ONLY Learner nodes for permanent analytics - **#219**: Pre-generate Go protobuf code for zero-config experience ## 🔧 Refactoring & Performance - **#223**: Optimize check_learner_progress() lock contention - Extract 5 helper methods (SRP), add 11 unit tests - **#211**: Remove Arc::get_mut anti-pattern from lease injection - **#210**: Simplify watch architecture (tokio::broadcast, 90% code sharing) - **#217**: Refactor Node::run() with strategy pattern - **#209**: Consolidate Raft unit tests (migrate 27 tests from server to core) ## 📚 Documentation - Restructure quick-start docs (embedded + standalone examples) - Add integration-modes.md and use-cases.md - Delete 911 lines of internal architecture docs (20/80 principle) - Fix Go client example with pre-generated protobufs ## 🧪 Testing & Validation - 14+ new unit tests across components - Fix flaky tests and timing issues (#204, #209) - Optimize test suite with nextest - All 430 core + 305 server + 292 integration tests passing ## 📊 Performance - Benchmarks stable (±3% variance) - CI optimized: removed benchmark compile check (saves 2-3 min/run) --- **Closes:** #209, #210, #211, #212, #213, #217, #218, #219, #222, #223 **Migration Notes:** - Config: `raft.watch.enabled` removed (breaking) - API: `StateMachine::start()` changed to async (breaking) - NodeStatus enum refactored: PROMOTABLE/READ_ONLY/ACTIVE (non-breaking) **Files Changed:** 100+ files, ~5000 insertions, ~1500 deletions
Type
Description
Release v0.2.0
Related Issues
This release includes work from the following issues:
rolefield from cluster config - use voters/learners lists only #202, Fix ClusterMembership API: Separate static role from dynamic leader info #201make test-all#197, feat(watch): Implement client Watch gRPC streaming service #196, docs(bench): Update v0.2.0 benchmark report with accurate v0.1.4 baseline #195Checklist
Summary by CodeRabbit
New Features
Documentation
Chores
Tests
✏️ Tip: You can customize this high-level summary in your review settings.