Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dd4dff0
test #245: migrate follower_state tests to core (38 tests, 100% passing)
JoshuaChi Jan 12, 2026
115252e
test #245: migrate selection_handler_test tests to core (14 tests, 10…
JoshuaChi Jan 12, 2026
6d8cbe7
test #245: migrate candidate_state tests to core (27 tests, 100% pass…
JoshuaChi Jan 12, 2026
2c7cfe2
test #245: migrate learner_state tests to core (29 tests, 100% passing)
JoshuaChi Jan 12, 2026
311acae
refactor #245: migrate leader state tests to modular structure
JoshuaChi Jan 13, 2026
fd1e269
fix #245: quick fix for make test
JoshuaChi Jan 13, 2026
f5c9b4d
refactor #245: Migrate replication handler tests from server to core …
JoshuaChi Jan 13, 2026
34f8cd7
refactor #245: Move BufferedRaftLog from server to core as generic ab…
JoshuaChi Jan 13, 2026
f212b8a
refactor #245: Move leader_state_bench from d-engine-server to d-engi…
JoshuaChi Jan 13, 2026
d84e920
test #245: Migrate BufferedRaftLog tests from server to core (Phase 0-2)
JoshuaChi Jan 13, 2026
2520edb
test #245: Migrate BufferedRaftLog tests Phase 3 (ID allocation)
JoshuaChi Jan 13, 2026
152f0ab
test #245: Migrate BufferedRaftLog tests Phase 4 (term index)
JoshuaChi Jan 13, 2026
410e096
test #245: Migrate BufferedRaftLog tests Phase 5 (concurrent ops)
JoshuaChi Jan 13, 2026
80a4ac0
test #245: Migrate BufferedRaftLog tests Phase 6 (remove_range ops)
JoshuaChi Jan 13, 2026
e25016e
test #245: Migrate BufferedRaftLog tests Phase 7 (edge cases)
JoshuaChi Jan 13, 2026
7b31e0e
test #245: Migrate BufferedRaftLog tests Phase 8 (Raft properties)
JoshuaChi Jan 13, 2026
978ea30
test #245: Migrate BufferedRaftLog tests Phase 9 (majority index)
JoshuaChi Jan 13, 2026
583f555
test #245: Migrate BufferedRaftLog tests Phase 10 (shutdown)
JoshuaChi Jan 13, 2026
c8d56ee
test #245: Migrate BufferedRaftLog tests Phase 11 (durable index & wo…
JoshuaChi Jan 13, 2026
e7ed445
refactor #245: Reorganize integration tests structure (preserve git h…
JoshuaChi Jan 13, 2026
63e5b98
test #245: Fix storage_buffered_raft_log integration tests compilatio…
JoshuaChi Jan 13, 2026
c012012
test #245: Extract remaining 3 performance tests and fix compilation
JoshuaChi Jan 13, 2026
26c9c24
refactor #245: Move test utilities from core to server and cleanup
JoshuaChi Jan 13, 2026
bba5ae7
refactor #245: hide test utilities from public API and improve docume…
JoshuaChi Jan 14, 2026
c672590
chore #245: make benchmarks more reliable on slow machines and improv…
JoshuaChi Jan 14, 2026
39d8280
chore #245: use workspace to new release version
JoshuaChi Jan 14, 2026
f301a1c
fix #245: fix for code review feedbacks
JoshuaChi Jan 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ members = [

exclude = [
"docs",
"examples/client_usage",
"examples/client-usage-standalone",
"examples/sled-cluster",
"examples/three-nodes-standalone",
"examples/three-nodes-embedded",
Expand All @@ -33,10 +33,10 @@ repository = "https://github.com/deventlab/d-engine"
license = "MIT OR Apache-2.0"

[workspace.dependencies]
d-engine-proto = { path = "./d-engine-proto", version = "0.2.1" }
d-engine-core = { path = "./d-engine-core", version = "0.2.1" }
d-engine-client = { path = "./d-engine-client", version = "0.2.1" }
d-engine-server = { path = "./d-engine-server", version = "0.2.1" }
d-engine-proto = { path = "./d-engine-proto", version = "0.2.2" }
d-engine-core = { path = "./d-engine-core", version = "0.2.2" }
d-engine-client = { path = "./d-engine-client", version = "0.2.2" }
d-engine-server = { path = "./d-engine-server", version = "0.2.2" }

tokio = { version = "1", features = [
"macros",
Expand Down
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ help:
@echo ""
@echo " $(YELLOW)Documentation:$(NC)"
@echo " make docs # Build and open documentation in browser"
@echo " make docs-check # Verify docs compile without warnings"
@echo " make docs-check # Simulate docs.rs build (all features, strict warnings)"
@echo " make docs-private # Build docs with private items visible"
@echo " make docs-crate CRATE=name # Build docs for specific crate"
@echo ""
Expand Down Expand Up @@ -251,10 +251,13 @@ test: install-tools check-workspace check-all-projects
# ============================================================================

## bench Run performance benchmarks with regression detection
## bench Run performance benchmarks
bench: check-workspace
@echo "$(BLUE)Running performance benchmarks...$(NC)"
@$(CARGO) bench --workspace --all-features || \
{ echo "$(RED)✗ Benchmark execution failed$(NC)"; exit 1; }
@BENCH_OP_TIMEOUT_MS=200 $(CARGO) bench --workspace --all-features || \
{ echo "$(RED)✗ Benchmark execution failed$(NC)"; \
echo "$(YELLOW)Tip: On slow machines, try: BENCH_OP_TIMEOUT_MS=500 make bench$(NC)"; \
exit 1; }
@echo "$(GREEN)✓ Benchmark run completed$(NC)"
@echo "$(CYAN)→ View detailed results: target/criterion/report/index.html$(NC)"

Expand Down Expand Up @@ -347,7 +350,7 @@ clean-deps: clean
# ============================================================================

## pre-release Full pre-release validation (comprehensive checks)
pre-release: install-tools check-workspace check test audit build-release
pre-release: install-tools check-workspace check docs-check test audit build-release
@echo "$(BLUE)Checking version consistency across workspace...$(NC)"
@version=$$(grep '^version' d-engine/Cargo.toml | head -n1 | cut -d'"' -f2); \
for crate in d-engine-proto d-engine-core d-engine-client d-engine-server d-engine; do \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ and pluggable storage backends. Start with one node, scale to a cluster when nee

## Features

### New in v0.2.0 🎉
### New in v0.2 🎉

- **EmbeddedEngine**: Single-node start, scale to 3-node cluster when needed
- **LocalKvClient**: Zero-overhead in-process access (<0.1ms latency)
Expand Down
21 changes: 16 additions & 5 deletions d-engine-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ categories = ["algorithms", "concurrency"]
publish = true

[package.metadata.docs.rs]
# Only enable user-facing features, exclude test-utils
# Only enable user-facing features
features = ["watch"]
rustdoc-args = ["--cfg", "docsrs"]
default-features = false
Expand All @@ -23,7 +23,10 @@ tag = false


[features]
test-utils = ["mockall","uuid", "tonic-health"]
# Internal: Test support for downstream crates
# This is not a public API and may change without notice
__test_support = ["mockall", "uuid", "tonic-health"]

# Experimental: Watch API for key change notifications
# WARNING: This feature is under active development and not production-ready
watch = []
Expand Down Expand Up @@ -62,12 +65,14 @@ http-body-util = "0.1.3"
crossbeam-channel = "0.5"
crossbeam-skiplist = "0.1"
crossbeam = "0.8"
mockall = { version = "0.12.1", optional = true }
tonic-health = { version = "0.12.3", optional = true }
# Compress/decompress data stream
async-compression = { version = "0.4", features = ["tokio", "gzip"] }
async-stream = "0.3.6"
uuid = { version = "1", features = ["v4"], optional = true }

# Test utilities - optional, enabled via __test_support feature
mockall = { version = "0.12.1", optional = true }
uuid = { version = "1", features = ["v4"], optional = true }
tonic-health = { version = "0.12.3", optional = true }

[dev-dependencies]
tracing-test = "0.2"
Expand All @@ -79,3 +84,9 @@ nix = { version = "0.30.1", features = ["fs"] }
mockall = "0.12.1"
tonic-health = "0.12.3"
uuid = { version = "1", features = ["v4"] }
criterion = { version = "0.5", features = ["html_reports", "async_tokio"] }

# Benchmark configuration
[[bench]]
name = "leader_state_bench"
harness = false
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,20 @@
//! - **v0.2.0** (#236): Added after ReadIndex batching refactor to ensure no regression
//! in promotion path

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use d_engine_core::leader_state::{LeaderState, PendingPromotion};
use d_engine_core::test_utils::MockBuilder;
use d_engine_core::{
AppendResults, MockMembership, MockRaftLog, MockTypeConfig, PeerUpdate, RaftContext,
RaftNodeConfig,
AppendResults, MockElectionCore, MockMembership, MockPurgeExecutor, MockRaftLog,
MockReplicationCore, MockStateMachine, MockStateMachineHandler, MockTransport, MockTypeConfig,
PeerUpdate, RaftContext, RaftCoreHandlers, RaftNodeConfig, RaftStorageHandles,
};
use d_engine_proto::common::NodeStatus;
use d_engine_proto::server::cluster::NodeMeta;
use d_engine_proto::common::{LogId, NodeStatus};
use d_engine_proto::server::cluster::{ClusterMembership, NodeMeta};
use tempfile::TempDir;
use tokio::sync::{mpsc, watch};
use tokio::sync::mpsc;
use tokio::time::Instant;

/// Benchmark 1: LeaderState Creation Performance
Expand Down Expand Up @@ -158,34 +157,85 @@ impl BenchFixture {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let mut node_config = RaftNodeConfig::default();
node_config.cluster.db_root_dir = temp_dir.path().join(test_name);
node_config.raft.membership.verify_leadership_persistent_timeout =
Duration::from_millis(100);

// Build mock storage
let mut raft_log = MockRaftLog::new();
raft_log.expect_last_entry_id().returning(|| 0);
raft_log.expect_last_log_id().returning(|| None);
raft_log.expect_flush().returning(|| Ok(()));
raft_log.expect_load_hard_state().returning(|| Ok(None));
raft_log.expect_save_hard_state().returning(|_| Ok(()));
raft_log.expect_calculate_majority_matched_index().returning(|_, _, _| Some(5));

let (_graceful_tx, graceful_rx) = watch::channel(());
let mut raft_context =
MockBuilder::new(graceful_rx).with_node_config(node_config).build_context();
let mut state_machine = MockStateMachine::new();
state_machine.expect_start().returning(|| Ok(()));
state_machine.expect_stop().returning(|| Ok(()));
state_machine.expect_is_running().returning(|| true);
state_machine.expect_get().returning(|_| Ok(None));
state_machine.expect_entry_term().returning(|_| None);
state_machine.expect_apply_chunk().returning(|_| Ok(()));
state_machine.expect_len().returning(|| 0);
state_machine.expect_update_last_applied().returning(|_| ());
state_machine.expect_last_applied().return_const(LogId::default());
state_machine.expect_persist_last_applied().returning(|_| Ok(()));
state_machine.expect_update_last_snapshot_metadata().returning(|_| Ok(()));
state_machine.expect_snapshot_metadata().returning(|| None);
state_machine.expect_persist_last_snapshot_metadata().returning(|_| Ok(()));
state_machine.expect_apply_snapshot_from_file().returning(|_, _| Ok(()));
state_machine
.expect_generate_snapshot_data()
.returning(|_, _| Ok(bytes::Bytes::copy_from_slice(&[0u8; 32])));
state_machine.expect_save_hard_state().returning(|| Ok(()));
state_machine.expect_flush().returning(|| Ok(()));

// Mock successful quorum verification
raft_context
.handlers
.replication_handler
let storage = RaftStorageHandles {
raft_log: Arc::new(raft_log),
state_machine: Arc::new(state_machine),
};

// Build mock transport
let transport = Arc::new(MockTransport::new());

// Build mock handlers
let mut election_handler = MockElectionCore::new();
election_handler
.expect_broadcast_vote_requests()
.returning(|_, _, _, _, _| Ok(()));

let mut replication_handler = MockReplicationCore::new();
replication_handler
.expect_handle_raft_request_in_batch()
.returning(|_, _, _, _, _| {
Ok(AppendResults {
commit_quorum_achieved: true,
learner_progress: HashMap::new(),
peer_updates: HashMap::from([
learner_progress: std::collections::HashMap::new(),
peer_updates: std::collections::HashMap::from([
(2, PeerUpdate::success(5, 6)),
(3, PeerUpdate::success(5, 6)),
]),
})
});

let mut raft_log = MockRaftLog::new();
raft_log.expect_calculate_majority_matched_index().returning(|_, _, _| Some(5));
raft_context.storage.raft_log = Arc::new(raft_log);
let mut state_machine_handler = MockStateMachineHandler::new();
state_machine_handler.expect_update_pending().returning(|_| {});
state_machine_handler.expect_read_from_state_machine().returning(|_| None);

let mut purge_executor = MockPurgeExecutor::new();
purge_executor.expect_execute_purge().returning(|_| Ok(()));

let handlers = RaftCoreHandlers {
election_handler,
replication_handler,
state_machine_handler: Arc::new(state_machine_handler),
purge_executor: Arc::new(purge_executor),
};

// Mock membership
// Build mock membership
let mut membership = MockMembership::new();
membership.expect_can_rejoin().returning(|_, _| Ok(()));
membership.expect_pre_warm_connections().returning(|| Ok(()));
membership.expect_get_node_status().returning(|_| Some(NodeStatus::Active));
membership.expect_voters().returning(|| {
vec![NodeMeta {
Expand All @@ -195,16 +245,31 @@ impl BenchFixture {
role: d_engine_proto::common::NodeRole::Follower.into(),
}]
});
raft_context.membership = Arc::new(membership);

let (role_tx, _role_rx) = mpsc::unbounded_channel();
membership.expect_replication_peers().returning(Vec::new);
membership.expect_members().returning(Vec::new);
membership.expect_check_cluster_is_ready().returning(|| Ok(()));
membership
.expect_retrieve_cluster_membership_config()
.returning(|_| ClusterMembership {
version: 1,
nodes: vec![],
current_leader_id: None,
});
membership.expect_get_zombie_candidates().returning(Vec::new);
membership.expect_get_peers_id_with_condition().returning(|_| vec![]);
membership.expect_is_single_node_cluster().returning(|| false);
membership.expect_initial_cluster_size().returning(|| 3);

// Set short timeout for benchmarks
let mut node_config = (*raft_context.node_config).clone();
node_config.raft.membership.verify_leadership_persistent_timeout =
Duration::from_millis(100);
raft_context.node_config = Arc::new(node_config.clone());
let raft_context = RaftContext {
node_id: 1,
storage,
transport,
membership: Arc::new(membership),
handlers,
node_config: Arc::new(node_config.clone()),
};

let (role_tx, _role_rx) = mpsc::unbounded_channel();
let leader_state = LeaderState::new(1, Arc::new(node_config));

BenchFixture {
Expand Down
5 changes: 3 additions & 2 deletions d-engine-core/src/commit_handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ pub use default_commit_handler::*;
#[cfg(test)]
mod default_commit_handler_test;

#[cfg(any(test, feature = "test-utils"))]
#[cfg(any(test, feature = "__test_support"))]
use mockall::automock;
use tonic::async_trait;

use crate::Result;

#[cfg_attr(any(test, feature = "test-utils"), automock)]
#[cfg_attr(any(test, feature = "__test_support"), automock)]
#[doc(hidden)]
#[async_trait]
pub trait CommitHandler: Send + Sync + 'static {
async fn run(&mut self) -> Result<()>;
Expand Down
Loading