Skip to content

Feature/172 ttl support - #176

Merged
JoshuaChi merged 17 commits into
developfrom
feature/172_ttl_support
Nov 12, 2025
Merged

JoshuaChi merged 17 commits into
developfrom
feature/172_ttl_support

Conversation

@JoshuaChi

@JoshuaChi JoshuaChi commented Nov 12, 2025 •

Copy link
Copy Markdown
Contributor

Type

  • New feature
  • Bug Fix

Description

Add TTL Support

Related Issues

Checklist

  • The code has been tested locally (unit test or integration test)
  • Squash down commits to one or two logical commits which clearly describe the work you've done.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added TTL (Time-To-Live) support for keys with automatic expiration capabilities.
    • Introduced unified start_server() API for simplified server initialization.
    • Added performance benchmarking suite with comprehensive test coverage.
  • Documentation

    • Added migration guide for v0.1.x to v0.2.0 API changes.
    • Updated server and storage customization guides with new patterns.
    • New benchmarking documentation and best practices.
  • Improvements

    • Enhanced CI/CD pipeline with automated benchmark runs and dependency audits.
    • Updated dependency dashmap to version 6.1.

…shutdown

- Remove TTL persist from apply_chunk hot path (zero overhead)
- Add TTL persist to graceful shutdown and snapshot
- Isolate benchmark measurements for accurate profiling
- Simplify benchmark workflow (remove third-party action)
- Improve Arc::get_mut error handling in builder

Performance improvements:
- Apply_chunk: eliminated TTL persist I/O overhead
- Benchmark accuracy: 99.997% improvement (isolated cleanup logic)
- Add explicit stable Rust toolchain installation
- Use --locked flag for cargo-deny to avoid version conflicts
- Fixes: smol_str@0.3.4 requires rustc 1.89 error
Copilot AI review requested due to automatic review settings November 12, 2025 08:18
@coderabbitai

coderabbitai Bot commented Nov 12, 2025 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR introduces TTL/lease-based expiration support across the storage layer, refactors the node startup sequence into a unified start_server() API, enhances CI/testing infrastructure with benchmark workflows, and adds comprehensive performance benchmarking for the d-engine server.

Changes

Cohort / File(s) Summary
CI/Workflows & Build Infrastructure
.github/workflows/benchmark.yml, .github/workflows/ci.yml, .github/workflows/dependency-audit.yml, Makefile, .gitignore
Added new benchmark workflow (weekly + on-demand), refined cargo targets to separate lib/tests from benches/examples, updated dependency audit toolchain setup, added benchmark results artifact retention.
Documentation & Release Notes
CHANGELOG.md, MIGRATION_GUIDE.md, README.md, d-engine-docs/src/docs/*, d-engine-server/README.md, d-engine-server/benches/README.md
Documented v0.2.0 release with TTL/Lease support, NodeBuilder API migration, added comprehensive migration guide, updated code examples to use start_server(), introduced benchmark documentation with setup and interpretation guidance.
Core Configuration & Error Layer
d-engine-core/src/config/raft.rs, d-engine-core/src/errors.rs
Introduced StateMachineConfig and LeaseConfig structs with validation, default strategies, and piggyback cleanup settings; added LeaseNotSupported error variant.
Core Storage Traits & Interfaces
d-engine-core/src/storage/lease.rs, d-engine-core/src/storage/mod.rs, d-engine-core/src/storage/state_machine.rs
Defined public Lease trait for TTL management with registration, expiration checks, snapshot/reload methods; extended StateMachine trait with try_inject_lease() and post_start_init() hooks.
Protocol Buffers & Proto Extensions
d-engine-proto/proto/client/client_api.proto, d-engine-proto/src/exts/client_ext.rs
Added optional ttl_secs field to Insert message; introduced insert_with_ttl() constructor for WriteCommand.
Client API
d-engine-client/src/kv.rs, d-engine-client/src/kv_test.rs
Added put_with_ttl() method to KvClient with success/failure/zero-TTL test coverage.
Server — Node Builder & Lifecycle
d-engine-server/src/node/builder.rs, d-engine-server/src/node/builder_test.rs
Converted build() to async with lease injection and post-start initialization; introduced unified start_server() combining build, RPC startup, and readiness into single awaitable call.
Server — Lease Implementation
d-engine-server/src/storage/lease.rs
Implemented DefaultLease with dual-index (DashMap for hot-path expiration checks, BTreeMap for range-based cleanup), snapshot serialization/restore, and piggyback cleanup orchestration.
Server — State Machine Implementations
d-engine-server/src/storage/adaptors/file/file_state_machine.rs, d-engine-server/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs
Extended both state machines with TTL tracking: WAL entries now carry optional TTL, INSERT/DELETE propagate TTL to lease, passive expiration on get, snapshot persistence of TTL state, piggyback cleanup integration.
Server — Storage Module & Integration
d-engine-server/src/storage/mod.rs, d-engine-server/src/storage/lease_integration_test.rs, d-engine-server/src/storage/adaptors/file/file_state_machine_test.rs, d-engine-server/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs, d-engine-server/src/test_utils/mock/mock_raft_builder.rs, d-engine-server/src/test_utils/mock/mock_node_builder.rs
Re-exported Lease and DefaultLease; added comprehensive lease/TTL integration tests for file and RocksDB backends; updated mocks to expect try_inject_lease() and post_start_init() calls.
Benchmarking Infrastructure
d-engine-server/Cargo.toml, d-engine-server/benches/state_machine.rs, d-engine-server/benches/ttl.rs
Added Criterion benchmarks for state machine apply/get performance, TTL registration/cleanup latencies, batch operations, and expiration scenarios; enabled HTML reporting.
Core & Server Tests
d-engine-core/src/storage/state_machine_test.rs, d-engine-core/src/storage/storage_engine_test.rs, d-engine-server/tests/components/replication/replication_handler_test.rs
Updated test payloads with ttl_secs: None field in Insert constructions; added performance test scaffolding (run_performance_tests) to state machine test suite.
Dependency Updates
d-engine-core/Cargo.toml, d-engine-server/Cargo.toml
Bumped dashmap from 5.5.3 to 6.1 for concurrency improvements.
Examples & Main Library
examples/*/src/main.rs, examples/sled-cluster/Cargo.toml, examples/sled-cluster/src/sled_engine_test.rs, examples/sled-cluster/src/sled_state_machine.rs, examples/client_usage/src/main.rs, d-engine/src/lib.rs
Updated all examples to use start_server() instead of build().start_rpc_server().ready(), added d-engine-proto dependency to sled example, updated Insert pattern-matching to handle new ttl_secs field.

Sequence Diagram

sequenceDiagram
    actor User
    participant NodeBuilder
    participant StateMachine
    participant Lease
    participant RaftNode

    User->>NodeBuilder: build()
    
    rect rgba(0, 100, 150, 0.1)
        Note over NodeBuilder: Lease Injection & Init
        NodeBuilder->>StateMachine: try_inject_lease(config)
        StateMachine->>Lease: new(config)
        NodeBuilder->>StateMachine: start()
        NodeBuilder->>StateMachine: post_start_init()
        StateMachine->>Lease: load_lease_data()
    end
    
    NodeBuilder->>RaftNode: Create
    NodeBuilder-->>User: Ok(Self)
    
    rect rgba(0, 150, 100, 0.1)
        Note over User: Application calls start_server()
        User->>NodeBuilder: start_server()
        NodeBuilder->>NodeBuilder: build().await
        NodeBuilder->>RaftNode: start_rpc_server()
        NodeBuilder->>RaftNode: ready()
        RaftNode-->>NodeBuilder: Ok(())
    end
    
    NodeBuilder-->>User: Ok(Arc<Node>)
    
    rect rgba(150, 100, 0, 0.1)
        Note over RaftNode: Lease-aware Operations
        User->>RaftNode: put_with_ttl(key, value, ttl)
        RaftNode->>StateMachine: apply_chunk(Insert{ttl_secs})
        StateMachine->>Lease: register(key, ttl_secs)
        User->>RaftNode: get(key)
        RaftNode->>StateMachine: get(key)
        StateMachine->>Lease: is_expired(key)
        alt Expired
            StateMachine->>Lease: unregister(key)
            StateMachine-->>RaftNode: None
        else Valid
            StateMachine-->>RaftNode: Some(value)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • State machine TTL integration (d-engine-server/src/storage/adaptors/file/file_state_machine.rs, rocksdb_state_machine.rs): Comprehensive TTL tracking across WAL replay, snapshot persistence, and passive expiration paths; verify correctness of lease state restoration and atomicity of operations.
  • Node builder lifecycle refactoring (d-engine-server/src/node/builder.rs): Async build with lease injection and try_inject_lease Arc mutation logic; confirm error handling and state consistency across initialization phases.
  • Lease implementation concurrency (d-engine-server/src/storage/lease.rs): Dual-index design (DashMap + BTreeMap) with Mutex; review lock contention, expiration check performance, and snapshot serialization correctness.
  • Proto field addition (d-engine-proto/proto/client/client_api.proto, Insert struct): Verify backward compatibility of optional ttl_secs field across all downstream code that constructs or pattern-matches Insert.
  • Benchmark infrastructure (d-engine-server/benches/*): Validate performance baselines and ensure benchmarks capture relevant TTL/lease scenarios without introducing test artifacts.

Possibly related PRs

  • Feature/79 snapshot #127: Modifies overlapping CI artifacts (.github/workflows/ci.yml, Makefile, d-engine-proto), infrastructure coordination recommended.
  • Feature/138 long lived peer task #144: Adds read-consistency and related RaftConfig fields; coordinate lease config and state machine initialization sequencing.
  • feat #167: workspace structure #175: Modifies proto/client/client_api.proto and proto-generated types affecting Insert struct; review for compatibility with TTL field addition.

Poem

🐰 Leases bloom in storage bright,
TTL timers tick through the night,
Builders start with one clean call,
Snapshots save the lease for all,
Expiration's dance, both swift and right! 🕐

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Feature/172 ttl support' directly describes the main feature being added (TTL support) and references the related issue #172, which aligns with the core objective and changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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 and usage tips.

@JoshuaChi

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 12, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI 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.

Pull Request Overview

This PR implements TTL (time-to-live) support for d-engine, adding automatic key expiration through a lease-based lifecycle management system. The implementation includes a simplified NodeBuilder API, comprehensive testing infrastructure, and performance benchmarking.

  • Adds TTL/Lease functionality with multiple cleanup strategies (disabled, passive, piggyback, background)
  • Simplifies NodeBuilder API: .build().start_rpc_server().await.ready() → .start_server().await
  • Implements lease injection framework with try_inject_lease() and post_start_init() hooks
  • Adds comprehensive integration tests and performance benchmarks

Reviewed Changes

Copilot reviewed 47 out of 51 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
d-engine-proto/proto/client/client_api.proto Adds optional ttl_secs field to Insert message
d-engine-core/src/storage/lease.rs Defines Lease trait for TTL management
d-engine-server/src/storage/lease.rs Implements DefaultLease with dual-index architecture
d-engine-server/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs Adds TTL support to RocksDB state machine
d-engine-server/src/storage/adaptors/file/file_state_machine.rs Adds TTL support to File state machine
d-engine-server/src/node/builder.rs Implements unified start_server() API
d-engine-core/src/config/raft.rs Adds LeaseConfig with cleanup strategies
d-engine-server/benches/ttl.rs TTL-specific performance benchmarks
d-engine-server/benches/state_machine.rs State machine performance benchmarks
d-engine-client/src/kv.rs Adds put_with_ttl() client method

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread d-engine-server/src/storage/adaptors/file/file_state_machine.rs Outdated
Comment on lines +143 to +181
fn open_db<P: AsRef<Path>>(path: P) -> Result<DB, Error> {
// Same options as new()
let mut opts = Options::default();
opts.create_if_missing(true);
opts.create_missing_column_families(true);

// Memory and write optimization
opts.set_max_write_buffer_number(4);
opts.set_min_write_buffer_number_to_merge(2);
opts.set_write_buffer_size(128 * 1024 * 1024);

// Compression optimization
opts.set_compression_type(rocksdb::DBCompressionType::Lz4);
opts.set_bottommost_compression_type(rocksdb::DBCompressionType::Zstd);
opts.set_compression_options(-14, 0, 0, 0);

// WAL-related optimizations
opts.set_wal_bytes_per_sync(1024 * 1024);
opts.set_manual_wal_flush(true);
opts.set_use_fsync(false);

// Performance Tuning
opts.set_max_background_jobs(4);
opts.set_max_open_files(5000);
opts.set_use_direct_io_for_flush_and_compaction(true);
opts.set_use_direct_reads(true);

// Leveled Compaction Configuration
opts.set_level_compaction_dynamic_level_bytes(true);
opts.set_target_file_size_base(64 * 1024 * 1024);
opts.set_max_bytes_for_level_base(256 * 1024 * 1024);

// Block cache configuration
let cache = Cache::new_lru_cache(128 * 1024 * 1024);
opts.set_row_cache(&cache);

let cfs = vec![STATE_MACHINE_CF, STATE_MACHINE_META_CF];
DB::open_cf(&opts, path, cfs).map_err(|e| StorageError::DbError(e.to_string()).into())
}

Copilot AI Nov 12, 2025

Copy link

Choose a reason for hiding this comment

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

The open_db() function duplicates all RocksDB configuration logic from new(). Consider extracting the options configuration into a shared helper function (e.g., configure_db_options()) to maintain DRY principle and ensure consistency between both initialization paths.

Copilot uses AI. Check for mistakes.
Comment on lines +481 to +492
let ttl_secs = if pos + 4 <= buffer.len() {
let ttl = u32::from_be_bytes(buffer[pos..pos + 4].try_into().unwrap());
pos += 4;
if ttl > 0 { Some(ttl) } else { None }
} else {
// Backward compatibility: if no TTL field, assume no TTL
debug!(
"No TTL field at position {}, assuming no TTL (backward compatibility)",
pos
);
None
};

Copilot AI Nov 12, 2025

Copy link

Choose a reason for hiding this comment

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

The backward compatibility logic for WAL entries without TTL fields is critical for upgrades. Consider adding a comment explaining the migration path: how existing WAL entries (written before this PR) are handled during replay after upgrading to this version.

Copilot uses AI. Check for mistakes.
Comment on lines +557 to +562
if !(10..=10000).contains(&self.piggyback_frequency) {
return Err(Error::Config(ConfigError::Message(format!(
"piggyback_frequency must be between 10 and 10000, got {}",
self.piggyback_frequency
))));
}

Copilot AI Nov 12, 2025

Copy link

Choose a reason for hiding this comment

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

The piggyback_frequency validation range (10-10000) lacks justification in the validation logic. Consider documenting why these specific bounds were chosen (e.g., reference to benchmark results or design decisions) either in code comments or configuration documentation to help users understand the constraints.

Copilot uses AI. Check for mistakes.

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
d-engine-server/Cargo.toml (1)

4-4: Invalid Rust edition specified.

Rust edition "2024" does not exist. The latest stable edition is 2021. This will cause cargo build and all other cargo commands to fail immediately.

Apply this diff to fix the edition:

-edition = "2024"
+edition = "2021"
d-engine-core/Cargo.toml (1)

4-4: Invalid Rust edition "2024".

Rust supports editions 2015, 2018, and 2021. Edition 2024 does not exist and will cause the build to fail.

Apply this diff to fix the edition:

-edition = "2024"
+edition = "2021"
examples/sled-cluster/src/sled_state_machine.rs (1)

229-244: TTL inserts are silently treated as non-expiring

ttl_secs is now part of Insert, but this branch just discards it and writes the value permanently. Any client using the Sled-backed example with TTL will never see keys expire, undermining the new TTL/lease feature and leaking data. Please either integrate this path with the lease/TTL machinery (so the expiry is enforced) or reject TTL writes explicitly until support is landed.

d-engine-server/src/node/builder.rs (1)

13-28: Update the builder docs to reflect the new async API.

build() is now async fn build(...) -> Result<Self>, yet the snippet still chains .build().start_rpc_server().await.ready() without awaiting or handling the Result. Anyone copying this will get a compiler error. Please update the example to either await build() (handling the error) or switch the sample entirely to the new start_server().await? flow advertised in the bullet above so the docs stay consistent.

d-engine-server/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs (1)

523-544: Ensure TTL removals clear existing leases

Re-inserting a key without a TTL (or with ttl_secs = 0) should cancel any previous lease. The current logic only registers TTL when ttl_secs is Some(ttl > 0) and otherwise leaves the old lease in place, causing keys that were explicitly converted to non-TTL entries to still expire later. Please unregister the lease whenever no positive TTL is provided.

                         Some(Operation::Insert(Insert {
                             key,
                             value,
                             ttl_secs,
                         })) => {
                             batch.put_cf(&cf, &key, &value);

-                            // Register TTL if specified
-                            if let Some(ttl) = ttl_secs {
-                                if ttl > 0 {
-                                    if let Some(ref lease) = self.lease {
-                                        lease.register(key.clone(), ttl);
-                                    }
-                                }
-                            }
+                            if let Some(ref lease) = self.lease {
+                                match ttl_secs {
+                                    Some(ttl) if ttl > 0 => lease.register(key.clone(), ttl),
+                                    _ => lease.unregister(&key),
+                                }
+                            }
d-engine-server/src/storage/adaptors/file/file_state_machine.rs (1)

503-533: TTL metadata is dropped during WAL replay.

FileStateMachine::new calls load_from_disk() → replay_wal() before set_lease()/try_inject_lease() ever runs, so self.lease is guaranteed to be None in this block. After a crash, any INSERT that was only present in the WAL (because we hadn’t persisted ttl_state.bin yet) replays here without re-registering its TTL and the key becomes permanent. Please inject the lease prior to replaying the WAL (e.g., construct the machine with an Arc<DefaultLease> already set) or defer WAL replay until after the lease is installed so those register/unregister calls actually run.

🧹 Nitpick comments (4)
d-engine-core/Cargo.toml (1)

36-37: Clarify the stale comment reference.

Line 36 references "Dashset" but the dependency is "dashmap". Clarify or update the comment for clarity.

-# using Dashset
+# Concurrent hash map for lease tracking
d-engine-server/benches/README.md (1)

1-172: LGTM!

Excellent benchmark documentation that covers all aspects from overview to implementation details. The performance targets table (lines 81-86) is particularly helpful, and the example benchmark code provides a clear template for contributors.

You could optionally add language specifiers to the fenced code blocks at lines 67 and 106 to satisfy the markdown linter:

-```
+```text
 target/criterion/report/index.html

and

```diff
-```
+```text
 https://[your-org].github.io/d-engine/dev/bench/

This is purely cosmetic and doesn't affect functionality.

</blockquote></details>
<details>
<summary>d-engine-server/benches/state_machine.rs (1)</summary><blockquote>

`99-129`: **Consider state machine reuse for more accurate performance measurements.**

The `bench_apply_without_ttl` and `bench_apply_with_ttl` benchmarks create a new `FileStateMachine` on every iteration, which includes filesystem operations and initialization overhead that may not reflect typical runtime performance where a state machine is long-lived.



Consider moving the state machine creation outside the iteration loop, similar to the read benchmarks (lines 137-142):

```diff
 fn bench_apply_without_ttl(c: &mut Criterion) {
     let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
+    
+    // Setup state machine once before benchmark
+    let (sm, _temp_dir) = runtime.block_on(async {
+        create_test_state_machine().await
+    });
 
     c.bench_function("apply_without_ttl", |b| {
         b.to_async(&runtime).iter(|| async {
-            let (sm, _temp_dir) = create_test_state_machine().await;
-            let entries = create_entries_without_ttl(1, 1);
+            // Use incrementing indices to avoid key conflicts
+            let entries = create_entries_without_ttl(1, black_box(1));
 
             // Measure pure apply performance
             sm.apply_chunk(entries).await.unwrap();
             black_box(());
         });
     });
 }

However, if the intent is to measure cold-start performance including initialization, the current approach is correct. Consider adding a comment clarifying the intent.

d-engine-server/benches/ttl.rs (1)

26-46: Async function doesn't require async operations before await.

The create_test_state_machine function is marked async, but all operations before the FileStateMachine::new call are synchronous. While functionally correct, this is a minor style inconsistency.

This is acceptable as-is since the function is only called from async contexts, but you could simplify slightly:

-async fn create_test_state_machine() -> (FileStateMachine, TempDir) {
+fn create_test_state_machine() -> impl std::future::Future<Output = (FileStateMachine, TempDir)> {
+    async move {
         use d_engine_server::storage::DefaultLease;
         
         let temp_dir = TempDir::new().expect("Failed to create temp dir");
         // ...
+    }
 }

Or keep as-is for simplicity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be93d34 and 7136903.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • d-engine-proto/src/generated/d_engine.client.rs is excluded by !**/generated/**
  • examples/rocksdb-cluster/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (48)
  • .github/workflows/benchmark.yml (1 hunks)
  • .github/workflows/ci.yml (1 hunks)
  • .github/workflows/dependency-audit.yml (2 hunks)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • MIGRATION_GUIDE.md (1 hunks)
  • Makefile (8 hunks)
  • README.md (1 hunks)
  • d-engine-client/src/kv.rs (1 hunks)
  • d-engine-client/src/kv_test.rs (1 hunks)
  • d-engine-core/Cargo.toml (1 hunks)
  • d-engine-core/src/config/raft.rs (4 hunks)
  • d-engine-core/src/errors.rs (1 hunks)
  • d-engine-core/src/storage/lease.rs (1 hunks)
  • d-engine-core/src/storage/mod.rs (1 hunks)
  • d-engine-core/src/storage/state_machine.rs (2 hunks)
  • d-engine-core/src/storage/state_machine_test.rs (4 hunks)
  • d-engine-core/src/storage/storage_engine_test.rs (1 hunks)
  • d-engine-core/src/test_utils/mock/mock_raft_builder.rs (1 hunks)
  • d-engine-docs/src/docs/overview.md (1 hunks)
  • d-engine-docs/src/docs/server_guide/customize-state-machine.md (3 hunks)
  • d-engine-docs/src/docs/server_guide/customize-storage-engine.md (1 hunks)
  • d-engine-proto/proto/client/client_api.proto (1 hunks)
  • d-engine-proto/src/exts/client_ext.rs (1 hunks)
  • d-engine-server/Cargo.toml (2 hunks)
  • d-engine-server/README.md (1 hunks)
  • d-engine-server/benches/README.md (1 hunks)
  • d-engine-server/benches/state_machine.rs (1 hunks)
  • d-engine-server/benches/ttl.rs (1 hunks)
  • d-engine-server/src/node/builder.rs (4 hunks)
  • d-engine-server/src/node/builder_test.rs (1 hunks)
  • d-engine-server/src/storage/adaptors/file/file_state_machine.rs (25 hunks)
  • d-engine-server/src/storage/adaptors/file/file_state_machine_test.rs (4 hunks)
  • d-engine-server/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs (1 hunks)
  • d-engine-server/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs (15 hunks)
  • d-engine-server/src/storage/lease.rs (1 hunks)
  • d-engine-server/src/storage/lease_integration_test.rs (1 hunks)
  • d-engine-server/src/storage/mod.rs (1 hunks)
  • d-engine-server/src/test_utils/mock/mock_node_builder.rs (1 hunks)
  • d-engine-server/tests/components/replication/replication_handler_test.rs (3 hunks)
  • d-engine/src/lib.rs (1 hunks)
  • examples/client_usage/src/main.rs (1 hunks)
  • examples/rocksdb-cluster/src/main.rs (2 hunks)
  • examples/sled-cluster/Cargo.toml (1 hunks)
  • examples/sled-cluster/src/main.rs (1 hunks)
  • examples/sled-cluster/src/sled_engine_test.rs (2 hunks)
  • examples/sled-cluster/src/sled_state_machine.rs (1 hunks)
  • examples/three-nodes-cluster/src/main.rs (1 hunks)
🧰 Additional context used
🪛 LanguageTool
d-engine-server/benches/README.md

[grammar] ~32-~32: Ensure spelling is correct
Context: ...h varying expired key counts (target: < 1ms for 100 keys) - ttl_registration - ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~37-~37: Ensure spelling is correct
Context: ... Performance with different TTL values (1min, 1hr, 1day) - *worst_case_all_expired...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~37-~37: Ensure spelling is correct
Context: ...rmance with different TTL values (1min, 1hr, 1day) - worst_case_all_expired - A...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~146-~146: Try using a synonym here to strengthen your wording.
Context: ...bench_<what_you_test>4. Include clear comments about what you're measuring 5. Runcar...

(COMMENT_REMARK)

🪛 markdownlint-cli2 (0.18.1)
d-engine-server/benches/README.md

67-67: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

d-engine-server/README.md

18-18: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (2)
  • GitHub Check: Lint and Format Check
  • GitHub Check: Lint and Format Check
🔇 Additional comments (38)
.github/workflows/dependency-audit.yml (2)

15-16: Good: Explicit Rust toolchain setup improves reproducibility.

Adding the dtolnay/rust-toolchain@stable step ensures consistent Rust versions across CI runs and is a best practice for GitHub Actions workflows.


32-32: Good: --locked flag ensures reproducible dependency installation.

Using the --locked flag when installing cargo-deny ensures the exact versions specified in Cargo.lock are used, which improves reproducibility and consistency across CI runs.

d-engine-server/Cargo.toml (3)

69-69: Benchmark tooling configuration looks good.

Criterion is the standard Rust benchmarking framework and the feature selection (html_reports, async_tokio) is appropriate for async workloads.


71-78: Benchmark configurations verified and properly set up.

Both benchmark files are present and correctly located:

  • ✓ state_machine.rs (8445 bytes)
  • ✓ ttl.rs (11440 bytes)

The Cargo.toml configurations with harness = false are correctly set up to use criterion. No issues detected.


38-38: Dashmap 6.1 upgrade is API-compatible; TTL assumption in original comment is unfounded.

All existing dashmap usage in the codebase (DashMap, DashSet, and entry API) is compatible with version 6.1. However, dashmap 6.1 does not include built-in TTL or automatic-expiration support, so the bump is not motivated by TTL requirements. Breaking changes between 5.5.3 and 6.1 are internal only and do not affect the public API surface used in this codebase. The upgrade is safe to proceed.

d-engine-core/Cargo.toml (1)

37-37: No issues found—DashMap 6.1 upgrade is safe.

DashMap v6.0.0 contains no breaking API changes, only performance optimizations and QoL improvements. v6.1.0 introduces no explicit breaking changes, and MSRV is 1.65. The codebase's Entry API usage (Occupied/Vacant patterns at lines 604-609 in grpc_transport.rs and elsewhere) remains stable and compatible across all 10 DashMap usage locations. While v6.0.0 was yanked due to a critical bug (#304), v6.1 is the stable release with that issue resolved.

.gitignore (1)

49-49: LGTM!

Adding *.bak* to ignore backup files is a sensible addition for keeping the repository clean.

.github/workflows/ci.yml (1)

69-73: LGTM!

The benchmark compilation check is well-placed and ensures that benchmark code stays in sync with the main codebase without the overhead of running full benchmarks on every CI run.

examples/client_usage/src/main.rs (1)

114-114: LGTM!

Removing the .into() conversion simplifies the code. The policy variable is already of type ReadConsistencyPolicy, so the explicit conversion was unnecessary.

CHANGELOG.md (1)

7-40: LGTM!

The v0.2.0 release notes are comprehensive and well-structured. Breaking changes are clearly documented with references to the migration guide, and the feature descriptions align with the PR objectives.

d-engine-server/README.md (1)

1-244: LGTM!

This is comprehensive and well-structured documentation that clearly explains the server architecture, provides practical examples, and demonstrates the new start_server() API consistently throughout. The quick-start examples and storage backend comparisons are particularly helpful.

Note: The markdownlint warning about the fenced code block on line 18 is a false positive—it's an ASCII art architecture diagram, not a code block requiring a language specifier.

.github/workflows/benchmark.yml (1)

1-89: LGTM!

The benchmark workflow is well-configured with appropriate triggers (weekly schedule, manual dispatch, and pushes to performance-critical paths). The separate cache key for benchmarks prevents interference with test caches, and the 30-day artifact retention provides a reasonable window for performance analysis.

d-engine-server/benches/state_machine.rs (3)

203-240: LGTM!

The batch benchmarks properly test scaling characteristics across different sizes (10, 100, 1000) and use BenchmarkId::from_parameter for clear result labeling. Creating a fresh state machine per iteration is appropriate here to ensure consistent starting conditions for each batch size.


133-199: LGTM!

The read benchmarks demonstrate good benchmark design by setting up the state machine once before iterations (lines 137-142, 159-164, 181-190), which isolates the actual read performance being measured. The expired TTL benchmark correctly waits for expiration before measuring passive deletion costs.


26-95: LGTM!

The helper functions are well-factored and reusable. The entry creation logic correctly constructs protobuf messages with and without TTL values, and the use of formatting with indices ensures unique keys across benchmark iterations.

d-engine-server/benches/ttl.rs (2)

158-175: Single-threaded runtime may not reflect production workload.

The batch TTL registration benchmark uses new_current_thread() (Line 159), which doesn't reflect production environments where d-engine typically runs with a multi-threaded tokio runtime. This could mask concurrency-related performance characteristics.

Consider whether multi-threaded runtime benchmarks are needed for realistic performance profiles, or document that these benchmarks intentionally isolate single-threaded performance.


94-130: Benchmark timing affected by explicit sleep.

The 2-second sleep on Line 114 is included in the benchmark measurement, which distorts the actual cleanup performance metrics. Criterion measures the entire closure execution, including the sleep time.

Move the sleep and setup outside the benchmark closure:

 fn bench_piggyback_cleanup(c: &mut Criterion) {
     use d_engine_server::storage::DefaultLease;
 
     let mut group = c.benchmark_group("piggyback_cleanup");
 
     // Test with different numbers of expired keys
     for expired_count in [10, 50, 100, 500].iter() {
         let lease_config = d_engine_core::config::LeaseConfig {
             cleanup_strategy: "piggyback".to_string(),
             ..Default::default()
         };
-        let lease = DefaultLease::new(lease_config);
-
-        // Register keys with very short TTL
-        for i in 0..*expired_count {
-            let key = format!("key_ttl_{}", i);
-            lease.register(bytes::Bytes::from(key), 1); // 1 second TTL
-        }
-
-        // Wait for expiration
-        std::thread::sleep(Duration::from_secs(2));
 
         group.bench_with_input(
             BenchmarkId::from_parameter(expired_count),
             expired_count,
             |b, _| {
+                // Setup happens per iteration to ensure consistent state
+                let lease = DefaultLease::new(lease_config.clone());
+                for i in 0..*expired_count {
+                    let key = format!("key_ttl_{}", i);
+                    lease.register(bytes::Bytes::from(key), 1);
+                }
+                std::thread::sleep(Duration::from_secs(2));
+                
                 b.iter(|| {
                     // Directly measure cleanup logic without apply_chunk overhead
                     let expired_keys = lease.on_apply();
                     black_box(expired_keys);
                 });
             },
         );
     }
 
     group.finish();
 }

Likely an incorrect or invalid review comment.

Makefile (3)

163-176: LGTM! Well-structured separation of excluded projects.

The new clippy-excluded target properly isolates linting for examples and benchmarks, preventing them from slowing down the main workspace clippy checks. The loop structure is clear and provides good error reporting.


315-321: Excellent addition of bench-compile target.

This target allows CI to verify benchmark compilation without the time cost of running them. The error handling and messaging are clear and appropriate.


155-160: No action required—workspace-level Clippy scope is correctly configured.

Verification confirms that the workspace contains no binary targets in any member crate, and example targets are intentionally located outside the workspace (in examples/ subdirectories) and are already covered by the clippy-excluded target with --all-targets. The --lib --tests scope at the workspace level is appropriate and sufficient.

d-engine-docs/src/docs/server_guide/customize-storage-engine.md (1)

167-168: LGTM! Documentation properly reflects the new startup API.

The update from build() to start_server().await? correctly documents the unified asynchronous startup flow introduced in this PR.

d-engine-proto/src/exts/client_ext.rs (1)

46-85: LGTM! Clean TTL support integration.

The changes properly extend the WriteCommand API with TTL support:

  • insert() explicitly sets ttl_secs: None for backward compatibility
  • insert_with_ttl() provides an ergonomic constructor for TTL-enabled inserts
  • Documentation is clear and follows existing patterns
d-engine-proto/proto/client/client_api.proto (1)

11-13: LGTM! Backward-compatible TTL field addition.

The optional uint64 ttl_secs field is properly added with clear documentation. Using field number 3 maintains sequential ordering, and the optional nature ensures backward compatibility with existing clients.

examples/sled-cluster/Cargo.toml (1)

8-8: LGTM! Required dependency for proto updates.

Adding the d-engine-proto dependency allows the sled-cluster example to access the updated Insert type with TTL support.

d-engine-server/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs (1)

105-109: LGTM! Test updated for proto changes.

The Insert construction correctly includes ttl_secs: None, maintaining existing test behavior while adapting to the updated proto definition.

README.md (1)

63-64: LGTM! Example reflects the new startup API.

The quick start example correctly demonstrates the updated start_server() flow, replacing the previous build() pattern. This aligns with the unified asynchronous startup introduced in the PR.

d-engine-core/src/test_utils/mock/mock_raft_builder.rs (1)

360-362: LGTM! Lease injection mock support added correctly.

The mock expectation for try_inject_lease is properly configured and aligns with the broader lease/TTL framework being introduced.

d-engine-server/src/test_utils/mock/mock_node_builder.rs (1)

525-525: LGTM! Post-start initialization mock expectation added correctly.

The mock properly supports the new post_start_init lifecycle hook.

d-engine-core/src/storage/storage_engine_test.rs (1)

288-292: LGTM! Test payload updated for TTL field.

The ttl_secs: None addition correctly aligns with the proto changes introducing optional TTL support.

d-engine-docs/src/docs/overview.md (1)

50-52: LGTM! Documentation updated for unified startup API.

The example correctly demonstrates the new start_server() method, simplifying the startup sequence.

d-engine-core/src/storage/mod.rs (2)

1-1: LGTM! Lease module added to storage layer.

The new lease module declaration follows the existing module organization pattern.


7-7: LGTM! Lease types exposed through public API.

The re-export makes lease-related functionality available to consumers of the storage module.

d-engine-server/src/storage/adaptors/file/file_state_machine_test.rs (4)

29-29: LGTM! Test data updated with TTL field.

The Insert payload correctly includes ttl_secs: None to align with proto changes.


46-46: LGTM! Consistent TTL field usage.


77-77: LGTM! Clear documentation of TTL intent.


102-130: LGTM! Performance test infrastructure added.

The test properly:

  • Uses #[ignore] to prevent automatic execution
  • Implements StateMachineBuilder for test harness integration
  • Leverages tempfile for isolation
  • Calls the shared performance test suite
d-engine/src/lib.rs (1)

69-70: LGTM! Documentation reflects simplified startup API.

The example correctly demonstrates the new start_server() method, making the embedded server setup more straightforward.

d-engine-core/src/errors.rs (1)

341-343: LGTM! New error variant for lease support.

The LeaseNotSupported error is well-documented and provides a clear message for lease-related failures.

Comment thread d-engine-server/benches/ttl.rs
Comment thread d-engine-server/benches/ttl.rs
Comment thread d-engine-server/benches/ttl.rs
Comment thread d-engine-server/benches/ttl.rs
Comment thread d-engine-server/src/storage/adaptors/file/file_state_machine.rs
- Use inline format arguments in ttl benchmark
…drop

- Call stop() to persist TTL metadata before state machine drop
- Consistent with FileStateMachine test pattern
… fixes

BREAKING CHANGES:
- WAL format changed from relative ttl_secs (u32) to absolute expire_at_secs (u64)
- Enables crash-safe TTL semantics compatible with etcd lease behavior
- TTL now persists correctly across restarts (no TTL reset)
- See MIGRATION_GUIDE.md for upgrade instructions

Features:
- etcd-compatible TTL semantics with absolute expiration time
- Crash-safe WAL replay skips expired entries
- Snapshot restore now persists TTL metadata to RocksDB CF
- WAL stores absolute expiration time for proper recovery

Fixes:
- Fix clippy warning: empty line after doc comments in rocksdb_state_machine.rs
- Fix duplicate trace logging in BufferedRaftLog initialization
- Fix log level filtering: RUST_LOG now limits to DEBUG (no TRACE spam)
- Fix crash-safety bug in snapshot restore (TTL metadata persistence)
- Remove high-frequency trace logs from hot paths

Performance:
- Optimize TTL benchmarks: 10x faster execution (213s → 20s)
  - Use iter_batched to separate setup from measurement
  - Reduce sample size to 10 for tests with sleep operations
  - worst_case_all_expired: 10.6x faster
  - mixed_ttl_workload: 10x faster
  - piggyback_high_frequency: 10x faster

Internal:
- Refactor RocksDB options into configure_db_options() helper (DRY)
- Update RUST_LOG_LEVEL format for proper module-level filtering
- Add comprehensive WAL migration guide

Docs:
- Update CHANGELOG.md with breaking changes and migration notes
- Add WAL format migration section to MIGRATION_GUIDE.md
- Document TTL behavior changes and upgrade paths
- Remove trace! macro calls from BufferedRaftLog (command processor, batch processor, drop)
- Remove trace! macro calls from FileStateMachine (apply_chunk, lease cleanup)
- Remove unused 'use tracing::trace' imports
- Tests now show only DEBUG and higher log levels
- Significantly improves test output readability

This completes the log level filtering fix started in previous commit.
@JoshuaChi
JoshuaChi force-pushed the feature/172_ttl_support branch from 6df3853 to 0b67527 Compare November 12, 2025 12:09
Add three integration tests to verify WAL replay behavior:
- test_wal_replay_handles_incomplete_entries: Verifies incomplete WAL entries
  (missing expire_at field) are treated as permanent keys, not crashed
- test_wal_replay_all_expired_empty_state: Verifies all-expired WAL entries
  result in empty state after replay (expired keys are skipped)
- test_wal_replay_mixed_expired_and_valid: Verifies mixed expired/valid/permanent
  entries are correctly filtered during replay

These tests ensure crash-safe TTL semantics per MIGRATION_GUIDE.md requirements.
Add 16 unit tests covering core Raft leader election protocol:
- Vote request handling with valid conditions (granted/denied)
- Term advancement and vote reset behavior
- Log recency checks (term precedence, index comparison)
- Already-voted scenarios (same/different candidates)
- Empty log edge cases
- Protocol compliance (term 0, large term numbers)
- Legal vote request validation

Tests use MockRaftLog for RaftLog dependency to keep tests focused and fast.
No #[traced_test] used - following production best practices.

All 16 tests passing.
Add 15 unit tests for ElectionTimer and ReplicationTimer:
- ElectionTimer initialization and randomness verification
- Timeout bounds checking and distribution analysis
- Reset behavior and expiration detection
- ReplicationTimer dual-timeout coordination
- Independent reset verification (batch vs replication)
- Deadline calculation and minimum selection

Tests verify:
- Random timeouts fall within [min, max] range
- Distribution is not clustered at range edges
- Reset updates deadlines appropriately
- Expiration detection works correctly
- Timer independence when resetting separately

All 15 tests passing.
…te file

- Moved tests to dedicated lease_unit_test.rs file (best practice)
- Tests cover 22 code paths including:
  - Basic registration, unregister, expiration checks
  - Key updates and multi-key scenarios
  - Snapshot serialization/deserialization
  - Error handling (invalid data)
  - Piggyback cleanup with different configurations
  - Edge cases (empty leases, non-existent keys, etc)
  - may_have_expired_keys() optimization paths
  - on_apply() frequency-based cleanup
- All 22 tests passing
- Improves coverage of DefaultLease methods
Coverage Improvement Results:
- Before: 77.96% line coverage (Codecov diff hit)
- After: 85.84% line coverage (24,432/28,461 lines)
- Target: 85.61% ✅ EXCEEDED

Tests Added:
- 22 unit tests for DefaultLease in lease_unit_test.rs
- Fixed 6 Clippy warnings in timer_test.rs
- 1,006 total tests passing (0 failed)

Test Coverage by Module:
- storage::lease_unit_test: 22 new tests
- storage::lease_integration_test: 16 tests
- d-engine-core timer tests: 15 tests
- d-engine-core election tests: 16 tests
- d-engine-server integration: 100+ tests
- buffered raft log: 600+ tests

Key Improvements:
- DefaultLease::register() - key update scenarios ✓
- DefaultLease::unregister() - cleanup logic ✓
- DefaultLease::is_expired() - O(1) checks ✓
- DefaultLease::get_expired_keys() - range cleanup ✓
- DefaultLease::on_apply() - piggyback frequency ✓
- DefaultLease::may_have_expired_keys() - optimization ✓
- DefaultLease::reload() - error handling ✓

Verification:
- All tests pass with cargo test
- Coverage verified with cargo llvm-cov nextest
- lcov.info generated and ready for Codecov upload
…ypes

Update ALLOWED_TYPES in commit-message-check.yml to match cliff.toml:
- Add: style, test, ci, revert
- These types are defined in cliff.toml but were missing from workflow validation
- Ensures consistency between changelog generation and CI validation
@JoshuaChi
JoshuaChi merged commit d9636d1 into develop Nov 12, 2025
7 of 12 checks passed
@JoshuaChi
JoshuaChi deleted the feature/172_ttl_support branch November 12, 2025 14:32
@coderabbitai coderabbitai Bot mentioned this pull request Nov 28, 2025
3 of 4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Dec 11, 2025
4 tasks done
@JoshuaChi JoshuaChi mentioned this pull request Dec 13, 2025
4 tasks done
JoshuaChi added a commit that referenced this pull request Dec 31, 2025
… engine

## 🎯 Overview

v0.2.0 is a major release that transforms d-engine from a Raft implementation into a **production-ready distributed coordination engine** with workspace structure, developer-friendly APIs, and comprehensive features.

---

## 🚀 Key Features

### 🏗️ **Workspace Structure (#167)**
- Modular design: `d-engine-core`, `d-engine-server`, `d-engine-client`, `d-engine-proto`
- Clean separation of concerns for library users
- Profile-optimized build configuration

### ⏱️ **TTL & Lease Support (#172)**
- Crash-safe TTL with absolute expiration timestamps
- WAL format change: relative `ttl_secs` → absolute `expire_at_secs`
- Piggyback cleanup mechanism (minimal overhead)
- **Breaking Change**: See MIGRATION_GUIDE.md for WAL upgrade

### 👁️ **Watch API (#174, #196)**
- Lock-free event notification with crossbeam-channel
- gRPC streaming support for real-time key monitoring
- <10ns apply-path overhead, <100μs end-to-end latency
- Service discovery examples included

### 🚀 **EmbeddedEngine API (#182)**
- Zero-config single-node quick start
- `wait_leader()` and `leader_notifier()` for event-driven apps
- Dynamic cluster expansion (1→3 nodes without downtime)
- In-process `LocalKvClient` with explicit consistency levels

### 🎯 **Single-Node Support (#179)**
- Configuration-based single-node detection
- Automatic election/replication optimization
- Production-ready for low-traffic scenarios

### 📖 **Read Consistency Policies (#142)**
- LinearizableRead (strong consistency)
- LeaseRead (optimized with leader lease)
- EventualConsistency (fast local reads)

### 🔧 **Go Client Support (#170, #219)**
- Pre-generated Go protobuf code (zero-config)
- Comprehensive error handling guide
- Service discovery pattern examples

---

## 🐛 Critical Fixes

- **#212**: Fix learner promotion stuck (voter count + role transition)
- **#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)
- **#211**: Remove Arc::get_mut anti-pattern from lease injection
- **#145**: Fix undefined behavior in mmap zero-copy path
- **#197**: Fix integration test failures (Arc ownership, timing issues)

---

## ⚡ Performance Improvements

- **#138**: Long-lived peer tasks for append entries (100K+ throughput target)
- **#140**: Optimize proto bytes fields (use `bytes::Bytes`)
- **#141**: Optimize RocksDB write path for lower latency
- **#143**: Refactor gRPC compression configuration
- **#194**: Skip protobuf decoding when no active watchers
- **#208**: Eliminate redundant async calls in leader write hot path (+2-3% throughput)
- **#223**: Optimize check_learner_progress() lock contention

### Benchmark Results
- **LeaseRead**: 99,418 ops/s (+7.8% vs v0.1.4)
- **EventualConsistency**: 126,095 ops/s (+9.2% vs v0.1.4)
- **Linearizable p99**: 23.01ms (-8% vs v0.1.4)

---

## 🔧 Refactoring & Architecture

- **#210**: Simplify watch architecture (tokio::broadcast, 90% code sharing)
- **#217**: Refactor Node::run() with strategy pattern
- **#209**: Consolidate Raft unit tests (27 tests migrated from server to core)
- **#201**: Separate static membership from dynamic leader state
- **#223**: Extract 5 helper methods in learner promotion (SRP + 11 unit tests)

---

## 📚 Documentation

- Restructure quick-start docs (embedded + standalone examples)
- Add integration-modes.md and use-cases.md
- Comprehensive error handling guide
- Service discovery pattern documentation
- Delete 911 lines of internal architecture docs (20/80 principle)

---

## 🧪 Testing

- **430** core tests + **305** server tests + **292** integration tests passing
- Fix flaky tests and timing issues
- Add 14+ new unit tests across components
- Optimize test suite with nextest

---

## ⚠️ Breaking Changes

1. **WAL Format**: Relative `ttl_secs` → absolute `expire_at_secs` (requires data migration)
2. **Config**: `raft.watch.enabled` removed (Watch always available)
3. **API**: `StateMachine::start()` changed to async
4. **NodeStatus**: Refactored enum (PROMOTABLE/READ_ONLY/ACTIVE)

See **MIGRATION_GUIDE.md** for detailed upgrade instructions.

---

## 📦 Dependency Updates

- tokio-stream: Fix net feature requirement
- astral-tokio-tar: Replace tokio-tar (CVE-2025-62518 fix)
- simd-adler32: 0.3.7 → 0.3.8
- rustls-pemfile: unmaintained warning suppressed (tonic 0.12 dep)

---

## 🎯 Closes

#43, #45, #59, #66, #70, #71, #79, #89, #90, #101, #102, #106, #107, #109, #110, #119, #120, #121, #122, #123, #125, #133, #135, #138, #139, #140, #141, #142, #143, #145, #146, #147, #148, #150, #151, #152, #153, #154, #155, #156, #157, #158, #159, #161, #164, #167, #170, #172, #174, #175, #176, #178, #185, #186, #187, #192, #193, #194, #195, #196, #197, #200, #201, #203, #204, #205, #208, #209, #210, #211, #212, #213, #217, #218, #219, #222, #223, #224

---

**Files Changed:** 100+ files, ~5,000 insertions, ~1,500 deletions
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