diff --git a/README.md b/README.md index ac36906b..9573bfb8 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ async fn main() { **→ Full example:** [examples/quick-start-embedded](https://github.com/deventlab/d-engine/tree/main/examples/quick-start-embedded) +**→ New to d-engine?** [5-minute guide](https://docs.rs/d-engine/latest/d_engine/#-new-to-d-engine-start-here) + --- ## Integration Modes diff --git a/config/base/README.md b/config/base/README.md new file mode 100644 index 00000000..a1657695 --- /dev/null +++ b/config/base/README.md @@ -0,0 +1,54 @@ +# Configuration Reference + +> **Note**: The `config/base/` and `config/overlays/` directories in this repo exist +> for d-engine's own internal demo purpose only. They have no practical +> use for application developers. In your own project, you only need a single +> `d-engine.toml` with the settings you want to override. + +This directory contains annotated reference files — one per configuration section. +They document every available option so you know what can be tuned. + +## In your application + +Write a minimal `d-engine.toml` with only the fields you need — all omitted fields +use built-in defaults. + +**Single node:** + +```toml +[cluster] +db_root_dir = "./data" +``` + +**3-node cluster (one file per node):** + +```toml +[cluster] +node_id = 1 +listen_address = "0.0.0.0:9081" +initial_cluster = [ + { id = 1, address = "0.0.0.0:9081", role = 1, status = 3 }, + { id = 2, address = "0.0.0.0:9082", role = 1, status = 3 }, + { id = 3, address = "0.0.0.0:9083", role = 1, status = 3 }, +] +db_root_dir = "./db/1" +``` + +See [examples/](https://github.com/deventlab/d-engine/tree/main/examples) for complete +working configurations. + +For all available options with descriptions and defaults, see the +[`d_engine_core::config`](https://docs.rs/d-engine-core/latest/d_engine_core/config/) +API documentation. + +## Reference files + +| File | What it covers | +| ----------------- | -------------------------------------------------- | +| `cluster.toml` | Node identity, peer addresses, data directory | +| `raft.toml` | Batching, read consistency, snapshots, replication | +| `network.toml` | gRPC timeouts, connection windows, TCP tuning | +| `retry.toml` | Retry policies for RPC operations | +| `tls.toml` | TLS certificates and mutual auth | +| `monitoring.toml` | Prometheus metrics port | +| `storage.toml` | Storage backend options (unified DB mode) | diff --git a/config/base/storage.toml b/config/base/storage.toml new file mode 100644 index 00000000..c9a58216 --- /dev/null +++ b/config/base/storage.toml @@ -0,0 +1,15 @@ +# Storage engine infrastructure configuration. +# +# Controls low-level storage backend behavior, independent of Raft protocol semantics. + +[storage] +# Use a single shared RocksDB instance (4 column families) instead of two separate DB +# instances (one for Raft log + meta, one for state machine). +# +# true: one 128 MB block cache, one background thread pool, one file-descriptor budget +# — halves resource usage. Good for developer machines and resource-constrained envs. +# false: each DB instance manages its own resources, providing stronger isolation between +# the log and state machine workloads. Recommended for production. +# +# Default: false +unified_db = false diff --git a/d-engine-client/src/grpc_client.rs b/d-engine-client/src/grpc_client.rs index a11b5054..77fb73ac 100644 --- a/d-engine-client/src/grpc_client.rs +++ b/d-engine-client/src/grpc_client.rs @@ -30,10 +30,9 @@ use crate::ClientResponseExt; use crate::scoped_timer::ScopedTimer; use d_engine_core::client::{ClientApi, ClientApiResult}; -/// gRPC-based key-value store client +/// gRPC-based KV client for standalone mode. Obtained via [`crate::ClientBuilder`]. /// -/// Implements remote CRUD operations via gRPC protocol. -/// All write operations use strong consistency. +/// For embedded mode use `EmbeddedClient` instead. Both implement `ClientApi`. #[derive(Clone)] pub struct GrpcClient { pub(super) client_inner: Arc>, diff --git a/d-engine-core/src/client/client_api.rs b/d-engine-core/src/client/client_api.rs index 48d66f0b..4d3b29ff 100644 --- a/d-engine-core/src/client/client_api.rs +++ b/d-engine-core/src/client/client_api.rs @@ -30,25 +30,18 @@ use crate::client::client_api_error::ClientApiResult; use crate::config::ReadConsistencyPolicy; use bytes::Bytes; -/// Unified key-value store interface. +/// Unified key-value store interface implemented by `EmbeddedClient` and `GrpcClient`. /// -/// This trait abstracts over different client implementations, allowing applications -/// to write generic code that works with both remote (gRPC) and embedded (local) access. +/// You do **not** need to import or name this trait directly — use the concrete client type +/// returned by `engine.client()` or `ClientBuilder` and call methods on it. /// -/// # Consistency Guarantees +/// Only import `ClientApi` when writing generic code over both client types. /// -/// - **put()**: Strong consistency, linearizable writes -/// - **get()**: Linearizable reads by default -/// - **delete()**: Strong consistency, linearizable deletes +/// # Consistency /// -/// # Thread Safety -/// -/// All implementations must be `Send + Sync`, safe for concurrent access. -/// -/// # Performance Characteristics -/// -/// - `GrpcClient`: 1-2ms latency (network + serialization) -/// - `EmbeddedClient`: <0.1ms latency (direct function call) +/// - **put / delete**: linearizable writes (replicated to quorum before returning) +/// - **get_linearizable**: linearizable read (goes through Raft leader) +/// - **get_eventual**: fast local read (may be slightly stale) #[async_trait::async_trait] pub trait ClientApi: Send + Sync { /// Stores a key-value pair with strong consistency. diff --git a/d-engine-core/src/config/security.rs b/d-engine-core/src/config/security.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/d-engine-server/src/api/embedded_client.rs b/d-engine-server/src/api/embedded_client.rs index 88efe686..c00ac7e9 100644 --- a/d-engine-server/src/api/embedded_client.rs +++ b/d-engine-server/src/api/embedded_client.rs @@ -114,9 +114,9 @@ fn extract_read_payload(result: Option) -> ClientApiResul } } -/// Zero-overhead KV client for embedded mode. +/// Zero-overhead KV client for embedded mode. Obtained via `EmbeddedEngine::client()`. /// -/// Directly calls Raft core without gRPC overhead. +/// For standalone/gRPC mode use `GrpcClient` instead. Both implement `ClientApi`. #[derive(Clone)] pub struct EmbeddedClient { event_tx: mpsc::Sender, diff --git a/d-engine/Cargo.toml b/d-engine/Cargo.toml index f406a994..a98a3977 100644 --- a/d-engine/Cargo.toml +++ b/d-engine/Cargo.toml @@ -37,6 +37,12 @@ watch = ["server", "d-engine-server/watch"] # Add to `[dev-dependencies]` when implementing and testing custom state machines or storage engines. __test_support = ["server", "d-engine-server/__test_support"] +[lib] +# d-engine is a pure re-export wrapper — doc examples use tokio + feature-gated types +# that are not available in the default doctest context. Real compilation verification +# is provided by examples/ (compiled via `check-examples` in CI). +doctest = false + [dependencies] d-engine-client = { workspace = true, optional = true } d-engine-server = { workspace = true, optional = true } diff --git a/d-engine/src/docs/overview.md b/d-engine/src/docs/overview.md index a8bf8f88..1c4c1b9f 100644 --- a/d-engine/src/docs/overview.md +++ b/d-engine/src/docs/overview.md @@ -4,66 +4,32 @@ designed for embedding into applications that need strong consistency—the consensus layer for building reliable distributed systems. -**Built with a simple vision**: make distributed coordination accessible - cheap -to run, simple to use. **Built on a core philosophy**: choose simple architectures -over complex ones. - -d-engine's Raft core uses a single-threaded event loop to guarantee strong consistency -and strict ordering while keeping the codebase clean and performant. Production-ready -Raft implementation with flexible read consistency (Linearizable/Lease-Based/Eventual) -and pluggable storage backends. Start with one node, scale to a cluster when needed. - -## 🚀 New to d-engine? Start Here - -Follow this learning path to get started quickly: - -```text -1. Is d-engine Right for You? (1 minute) - ↓ -2. Choose Integration Mode (1 minute) - ↓ -3a. Quick Start - Embedded (5 minutes) - OR -3b. Quick Start - Standalone (5 minutes) - ↓ -4. Scale to Cluster (optional) -``` - -**→ Start: [Is d-engine Right for You?](crate::docs::use_cases)** - ## Recommended Entry Point -**This is the recommended crate for most users.** It re-exports all functionality from the internal crates below. - ```toml [dependencies] d-engine = "0.2" # This is all you need ``` -## Crate Organization - -| Crate | Purpose | -| ------------------- | ------------------------------- | -| **d-engine-proto** | Protocol definitions (Prost) | -| **d-engine-core** | Core Raft algorithm & traits | -| **d-engine-client** | Client library for applications | -| **d-engine-server** | Server runtime implementation | - ## Quick Start ### Embedded Mode (Rust) ```rust,ignore use d_engine::prelude::*; +use std::time::Duration; #[tokio::main] async fn main() -> Result<(), Box> { - let engine = EmbeddedEngine::start_with("d-engine.toml").await?; - engine.wait_ready(std::time::Duration::from_secs(5)).await?; + let engine = EmbeddedEngine::start("./data").await?; + engine.wait_ready(Duration::from_secs(5)).await?; let client = engine.client(); - client.put(b"key".to_vec(), b"value".to_vec()).await?; + client.put(b"hello".to_vec(), b"world".to_vec()).await?; + let value = client.get_linearizable(b"hello".to_vec()).await?; + println!("{}", String::from_utf8_lossy(&value.unwrap())); + engine.stop().await?; Ok(()) } ``` @@ -79,6 +45,51 @@ make start-cluster **→ [Standalone Guide](crate::docs::quick_start_standalone)** +## Client API + +- [`EmbeddedClient`] → [`EmbeddedEngine::client()`] +- [`GrpcClient`] → [`ClientBuilder::build()`] + +Both expose identical methods — pick your mode, the API below applies to both. No need to import `ClientApi` directly. + +**Write** + +| Method | Description | +| ------------------------------------ | ---------------------------------------------- | +| [`EmbeddedClient::put`] | Write a key-value pair | +| [`EmbeddedClient::put_with_ttl`] | Write with expiry (key auto-expires after TTL) | +| [`EmbeddedClient::delete`] | Delete a key | +| [`EmbeddedClient::compare_and_swap`] | Atomic CAS | + +**Read** + +| Method | Description | +| ----------------------------------------- | --------------------------------------------------- | +| [`EmbeddedClient::get_eventual`] | Fast local read (may be slightly stale) | +| [`EmbeddedClient::get_linearizable`] | Linearizable read (goes through leader) | +| [`EmbeddedClient::get`] | Read with default consistency policy | +| [`EmbeddedClient::get_multi`] | Read multiple keys | +| [`EmbeddedClient::get_multi_with_policy`] | Read multiple keys with explicit consistency policy | +| [`EmbeddedClient::scan_prefix`] | Prefix scan | +| [`EmbeddedClient::get_lease`] | Lease-based read (optimized linearizable) | + +**Watch** + +| Method | Description | +| --------------------------------------------- | ---------------------------------------- | +| [`EmbeddedClient::watch`] | Watch a key for changes | +| [`EmbeddedClient::watch_with_options`] | Watch a key with full options | +| [`EmbeddedClient::watch_prefix`] | Watch all keys under a prefix | +| [`EmbeddedClient::watch_prefix_with_options`] | Watch prefix with options (e.g. prev_kv) | +| [`EmbeddedEngine::watch_membership`] | Watch cluster membership changes | + +**Cluster** + +| Method | Description | +| --------------------------------- | -------------------------- | +| [`EmbeddedClient::list_members`] | List all cluster members | +| [`EmbeddedClient::get_leader_id`] | Get current leader node ID | + ## Documentation Index ### Getting Started @@ -110,7 +121,3 @@ make start-cluster - [Three-Node Standalone Cluster](crate::docs::examples::three_nodes_cluster) - Standalone mode cluster - [Throughput Optimization](crate::docs::performance::throughput_optimization_guide) - [Benchmarking Guide](crate::docs::performance::benchmarking_guide) - Run embedded and standalone benchmarks - -## License - -MIT or Apache-2.0 diff --git a/d-engine/src/docs/quick-start-5min.md b/d-engine/src/docs/quick-start-5min.md index f2803779..501ef50c 100644 --- a/d-engine/src/docs/quick-start-5min.md +++ b/d-engine/src/docs/quick-start-5min.md @@ -30,19 +30,23 @@ Add d-engine to your `Cargo.toml`: [dependencies] d-engine = "0.2" # Default includes server + rocksdb tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +# rt-multi-thread: d-engine spawns background tasks; macros: #[tokio::main] ``` --- ## Step 2: Create Config File (1 minute) -Create `d-engine.toml`: +Create `d-engine.toml` with only the settings you need — omitted fields use built-in defaults: ```toml [cluster] db_root_dir = "./data/single-node" ``` +For all available options with descriptions and defaults, see +[`d_engine_core::config`](https://docs.rs/d-engine-core/latest/d_engine_core/config/). + ## Step 3: Write Your First d-engine App (2 minutes) Create `src/main.rs`: @@ -239,17 +243,6 @@ See [complete API documentation](https://docs.rs/d-engine/latest/d_engine/prelud --- -## Performance Characteristics - -| Operation | Single-Node | 3-Node Cluster | -| ------------------- | ------------------- | --------------------------- | -| **put()** | <0.1ms (local) | 1-5ms (network replication) | -| **get()** | <0.1ms (local read) | <0.1ms (local read) | -| **Leader election** | <100ms | 1-2s (network RTT) | -| **Failover** | N/A (no peers) | 1-2s (auto re-election) | - ---- - ## Common Patterns ### Pattern 1: Production (explicit data directory) @@ -259,7 +252,7 @@ See [complete API documentation](https://docs.rs/d-engine/latest/d_engine/prelud let engine = EmbeddedEngine::start("./data").await?; ``` -### Pattern 2: Development (explicit config) +### Pattern 2: Explicit config file ```rust,ignore // Use specific config file diff --git a/d-engine/src/docs/quick-start-standalone.md b/d-engine/src/docs/quick-start-standalone.md index 00483706..f86e6abd 100644 --- a/d-engine/src/docs/quick-start-standalone.md +++ b/d-engine/src/docs/quick-start-standalone.md @@ -73,6 +73,46 @@ Value: world --- +## Rust Client + +Add the client feature to your `Cargo.toml`: + +```toml +d-engine = { version = "0.2", features = ["client"], default-features = false } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +``` + +Then connect and run KV operations: + +```rust,ignore +use d_engine::prelude::*; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = ClientBuilder::new(vec!["http://127.0.0.1:9081".into()]) + .build() + .await?; + + client.put(b"hello".to_vec(), b"world".to_vec()).await?; + let value = client.get_linearizable(b"hello".to_vec()).await?; + println!("{}", String::from_utf8_lossy(&value.unwrap())); + + Ok(()) +} +``` + +Pass all cluster nodes for resilience — the client auto-redirects writes to the leader: + +```rust,ignore +ClientBuilder::new(vec![ + "http://127.0.0.1:9081".into(), + "http://127.0.0.1:9082".into(), + "http://127.0.0.1:9083".into(), +]) +``` + +--- + ## What's Next? **Try other languages:**