Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions config/base/README.md
Original file line number Diff line number Diff line change
@@ -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) |
15 changes: 15 additions & 0 deletions config/base/storage.toml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 2 additions & 3 deletions d-engine-client/src/grpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArcSwap<ClientInner>>,
Expand Down
23 changes: 8 additions & 15 deletions d-engine-core/src/client/client_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Empty file.
4 changes: 2 additions & 2 deletions d-engine-server/src/api/embedded_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ fn extract_read_payload(result: Option<ClientResponsePayload>) -> 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<RaftEvent>,
Expand Down
6 changes: 6 additions & 0 deletions d-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
97 changes: 52 additions & 45 deletions d-engine/src/docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error>> {
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(())
}
```
Expand All @@ -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
Expand Down Expand Up @@ -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
19 changes: 6 additions & 13 deletions d-engine/src/docs/quick-start-5min.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
40 changes: 40 additions & 0 deletions d-engine/src/docs/quick-start-standalone.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error>> {
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:**
Expand Down
Loading