Skip to content

Commit ea0e770

Browse files
committed
Feature: add a kv example based on fjall kv store
1 parent 548a449 commit ea0e770

18 files changed

Lines changed: 1328 additions & 0 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ jobs:
373373
- 'raft-kv-memstore-network-v2'
374374
- 'raft-kv-memstore-opendal-snapshot-data'
375375
- 'raft-kv-memstore-single-threaded'
376+
- 'raft-kv-fjall'
376377
- 'raft-kv-rocksdb'
377378
- 'multi-raft-kv'
378379

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ exclude = [
9090
"examples/raft-kv-memstore-opendal-snapshot-data",
9191
"examples/raft-kv-rocksdb",
9292
"examples/multi-raft-kv",
93+
"examples/raft-kv-fjall",
9394

9495
"rt-monoio",
9596
"rt-compio",

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ test-examples:
4545
cargo test --manifest-path examples/raft-kv-memstore-network-v2/Cargo.toml
4646
cargo test --manifest-path examples/raft-kv-memstore-opendal-snapshot-data/Cargo.toml
4747
cargo test --manifest-path examples/raft-kv-memstore-single-threaded/Cargo.toml
48+
cargo test --manifest-path examples/raft-kv-fjall/Cargo.toml
4849
cargo test --manifest-path examples/raft-kv-rocksdb/Cargo.toml
4950
cargo test --manifest-path examples/rocksstore/Cargo.toml
5051
cargo test --manifest-path examples/multi-raft-kv/Cargo.toml
@@ -103,6 +104,7 @@ lint:
103104
cargo fmt --manifest-path examples/raft-kv-memstore-opendal-snapshot-data/Cargo.toml
104105
cargo fmt --manifest-path examples/raft-kv-memstore-single-threaded/Cargo.toml
105106
cargo fmt --manifest-path examples/raft-kv-memstore/Cargo.toml
107+
cargo fmt --manifest-path examples/raft-kv-fjall/Cargo.toml
106108
cargo fmt --manifest-path examples/raft-kv-rocksdb/Cargo.toml
107109
cargo fmt --manifest-path examples/multi-raft-kv/Cargo.toml
108110
cargo clippy --no-deps --all-targets -- -D warnings
@@ -118,6 +120,7 @@ lint:
118120
cargo clippy --no-deps --manifest-path examples/raft-kv-memstore-opendal-snapshot-data/Cargo.toml --all-targets -- -D warnings
119121
cargo clippy --no-deps --manifest-path examples/raft-kv-memstore-single-threaded/Cargo.toml --all-targets -- -D warnings
120122
cargo clippy --no-deps --manifest-path examples/raft-kv-memstore/Cargo.toml --all-targets -- -D warnings
123+
cargo clippy --no-deps --manifest-path examples/raft-kv-fjall/Cargo.toml --all-targets -- -D warnings
121124
cargo clippy --no-deps --manifest-path examples/raft-kv-rocksdb/Cargo.toml --all-targets -- -D warnings
122125
cargo clippy --no-deps --manifest-path examples/multi-raft-kv/Cargo.toml --all-targets -- -D warnings
123126
# Bug: clippy --all-targets reports false warning about unused dep in
@@ -166,6 +169,7 @@ check:
166169
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/raft-kv-memstore-opendal-snapshot-data/Cargo.toml
167170
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/raft-kv-memstore-single-threaded/Cargo.toml
168171
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/raft-kv-memstore/Cargo.toml
172+
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/raft-kv-fjall/Cargo.toml
169173
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/raft-kv-rocksdb/Cargo.toml
170174
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/rocksstore/Cargo.toml
171175
RUSTFLAGS="-D warnings" cargo check --manifest-path examples/multi-raft-kv/Cargo.toml
@@ -188,6 +192,7 @@ clean:
188192
cargo clean --manifest-path examples/raft-kv-memstore-opendal-snapshot-data/Cargo.toml
189193
cargo clean --manifest-path examples/raft-kv-memstore-single-threaded/Cargo.toml
190194
cargo clean --manifest-path examples/raft-kv-memstore/Cargo.toml
195+
cargo clean --manifest-path examples/raft-kv-fjall/Cargo.toml
191196
cargo clean --manifest-path examples/raft-kv-rocksdb/Cargo.toml
192197
cargo clean --manifest-path examples/rocksstore/Cargo.toml
193198
cargo clean --manifest-path examples/multi-raft-kv/Cargo.toml

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This directory contains example applications demonstrating different implementat
1717
| Example | Log | State Machine | RaftNetwork Impl | RaftNetwork | Client | Server | Special Features |
1818
|---------|-----|---------------|------------------|-------------|--------|--------|------------------|
1919
| [raft-kv-memstore] | [log-mem] | [sm-mem] | HTTP/reqwest | RaftNetwork | reqwest | actix-web | Basic example |
20+
| [raft-kv-fjall] | [fjall] | [fjall] | HTTP/reqwest([network-v1]) | RaftNetwork | reqwest | actix-web | Persistent storage |
2021
| [raft-kv-rocksdb] | [rocksstore] | [rocksstore] | HTTP/reqwest([network-v1]) | RaftNetwork | reqwest | actix-web | Persistent storage |
2122
| [raft-kv-memstore-network-v2] | [log-mem] | [sm-mem] | HTTP/reqwest | RaftNetworkV2 | reqwest | actix-web | Network V2 interface |
2223
| [multi-raft-kv] | [log-mem] | [sm-mem] | HTTP/channel | GroupRouter | channel | in-memory | Multi-Raft groups |
@@ -48,6 +49,7 @@ The following symbolic links are provided for backward compatibility:
4849

4950
<!-- Reference Links -->
5051
[raft-kv-memstore]: raft-kv-memstore/
52+
[raft-kv-fjall]: raft-kv-fjall/
5153
[raft-kv-rocksdb]: raft-kv-rocksdb/
5254
[raft-kv-memstore-network-v2]: raft-kv-memstore-network-v2/
5355
[raft-kv-memstore-grpc]: raft-kv-memstore-grpc/

examples/raft-kv-fjall/Cargo.toml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[package]
2+
name = "raft-kv-fjall"
3+
version = "0.1.0"
4+
readme = "README.md"
5+
6+
edition = "2024"
7+
authors = [
8+
"ariesdevil <ariesdevil77@gmail.com>",
9+
]
10+
categories = ["algorithms", "asynchronous", "data-structures"]
11+
description = "An example distributed key-value store built upon `openraft`."
12+
homepage = "https://github.com/databendlabs/openraft"
13+
keywords = ["raft", "consensus"]
14+
license = "MIT OR Apache-2.0"
15+
repository = "https://github.com/databendlabs/openraft"
16+
17+
[[bin]]
18+
name = "raft-key-value-fjall"
19+
path = "src/bin/main.rs"
20+
21+
[dependencies]
22+
client-http = { path = "../client-http" }
23+
network-v1-http = { path = "../network-v1-http" }
24+
openraft = { path = "../../openraft", features = ["serde", "type-alias"] }
25+
openraft-legacy = { path = "../../legacy" }
26+
types-kv = { path = "../types-kv" }
27+
28+
actix-web = { version = "4.0.0-rc.2" }
29+
clap = { version = "4.1.11", features = ["derive", "env"] }
30+
fjall = {version = "3.0.1"}
31+
futures = { version = "0.3" }
32+
serde = { version = "1.0.114", features = ["derive"] }
33+
serde_json = { version = "1.0.57" }
34+
tracing = { version = "0.1.40" }
35+
tracing-subscriber = { version = "0.3.0", features = ["env-filter"] }
36+
byteorder = "1.5.0"
37+
38+
[dev-dependencies]
39+
maplit = { version = "1.0.2" }
40+
tempfile = { version = "3.4.0" }
41+
42+
43+
[features]
44+
45+
[package.metadata.docs.rs]
46+
all-features = true

examples/raft-kv-fjall/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# openraft-fjall-kv-example
2+
3+
A fjall-backed persistent storage implementation for Openraft, demonstrating production-ready log storage and state machine patterns.
4+
5+
## Key Features Demonstrated
6+
7+
- **Persistent storage**: [`RaftLogStorage`] and [`RaftStateMachine`] with fjall
8+
- **Column families**: Separate storage for logs, state machine, and metadata
9+
- **Durability**: On-disk persistence for cluster recovery
10+
- **Performance**: Efficient batch operations and compaction
11+
12+
## Overview
13+
14+
This example implements:
15+
- **[`RaftLogStorage`](https://docs.rs/openraft/latest/openraft/storage/trait.RaftLogStorage.html)** - Persistent Raft log storage
16+
- **[`RaftStateMachine`](https://docs.rs/openraft/latest/openraft/storage/trait.RaftStateMachine.html)** - Persistent application state machine
17+
18+
Built with [fjall v3](https://fjall-rs.github.io/) for production-grade durability and performance.
19+
20+
## Architecture
21+
22+
**Storage structure**:
23+
- Logs stored in dedicated fjall key space
24+
- State machine data in separate key space
25+
- Vote and metadata persisted independently
26+
27+
**Asynchronous I/O operations**:
28+
- WAL flush operations run in spawned tasks to avoid blocking the async runtime
29+
- `save_vote()` and `append_to_log()` spawn async tasks for disk persistence
30+
- Callbacks receive actual flush results for proper error propagation
31+
- Log truncation (`purge()`) doesn't require immediate persistence
32+
33+
**Key Code Locations**:
34+
- Storage implementation: `src/store.rs`
35+
- Log storage with async WAL flush: `src/log_store.rs`
36+
- Type definitions: See parent example for network and client implementations
37+
38+
## Comparison
39+
40+
| Feature | kv-fjall | memstore |
41+
|---------|--------------|----------|
42+
| Storage | fjall (disk) | Memory |
43+
| Persistence | Yes | No |
44+
| Recovery | Full | None |
45+
| Complexity | Higher | Lower |
46+
47+
Built for testing and demonstration purposes.

examples/raft-kv-fjall/src/app.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::collections::BTreeMap;
2+
use std::sync::Arc;
3+
4+
use futures::lock::Mutex;
5+
use openraft::Config;
6+
7+
use crate::NodeId;
8+
use crate::Raft;
9+
10+
// Representation of an application state. This struct can be shared around to share
11+
// instances of raft, store and more.
12+
pub struct App {
13+
pub id: NodeId,
14+
pub addr: String,
15+
pub raft: Raft,
16+
pub key_values: Arc<Mutex<BTreeMap<String, String>>>,
17+
pub config: Arc<Config>,
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use clap::Parser;
2+
use raft_kv_fjall::start_example_raft_node;
3+
use tracing_subscriber::EnvFilter;
4+
5+
#[derive(Parser, Clone, Debug)]
6+
#[clap(author, version, about, long_about = None)]
7+
pub struct Opt {
8+
#[clap(long)]
9+
pub id: u64,
10+
11+
#[clap(long)]
12+
pub addr: String,
13+
}
14+
15+
#[actix_web::main]
16+
async fn main() -> std::io::Result<()> {
17+
// Setup the logger
18+
tracing_subscriber::fmt()
19+
.with_target(true)
20+
.with_thread_ids(true)
21+
.with_level(true)
22+
.with_ansi(false)
23+
.with_env_filter(EnvFilter::from_default_env())
24+
.init();
25+
26+
// Parse the parameters passed by arguments.
27+
let options = Opt::parse();
28+
29+
start_example_raft_node(options.id, format!("{}.db", options.addr), options.addr).await
30+
}

examples/raft-kv-fjall/src/lib.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::path::Path;
2+
use std::sync::Arc;
3+
4+
use actix_web::HttpServer;
5+
use actix_web::middleware;
6+
use actix_web::middleware::Logger;
7+
use actix_web::web::Data;
8+
use openraft::Config;
9+
10+
use crate::app::App;
11+
use crate::network::api;
12+
use crate::network::management;
13+
use crate::network::raft;
14+
use crate::store::new_storage;
15+
16+
pub mod app;
17+
pub mod log_store;
18+
pub mod network;
19+
pub mod store;
20+
21+
pub type NodeId = u64;
22+
23+
openraft::declare_raft_types!(
24+
pub TypeConfig:
25+
D = types_kv::Request,
26+
R = types_kv::Response,
27+
);
28+
29+
pub type LogStore = log_store::FjallLogStore<TypeConfig>;
30+
pub type StateMachineStore = store::StateMachineStore;
31+
pub type Raft = openraft::Raft<TypeConfig>;
32+
33+
#[path = "../../utils/declare_types.rs"]
34+
pub mod typ;
35+
36+
pub async fn start_example_raft_node<P>(node_id: NodeId, dir: P, addr: String) -> std::io::Result<()>
37+
where P: AsRef<Path> {
38+
// Create a configuration for the raft instance.
39+
let config = Config {
40+
heartbeat_interval: 250,
41+
election_timeout_min: 299,
42+
..Default::default()
43+
};
44+
45+
let config = Arc::new(config.validate().unwrap());
46+
47+
let (log_store, state_machine_store) = new_storage(&dir).await;
48+
49+
let kvs = state_machine_store.data.kvs.clone();
50+
51+
// Create the network layer using network-v1 crate
52+
let network = network_v1_http::NetworkFactory {};
53+
54+
// Create a local raft instance.
55+
let raft = openraft::Raft::new(node_id, config.clone(), network, log_store, state_machine_store).await.unwrap();
56+
57+
// Create an application that will store all the instances created above, this will
58+
// later be used on the actix-web services.
59+
let app_data = Data::new(App {
60+
id: node_id,
61+
addr: addr.clone(),
62+
raft,
63+
key_values: kvs,
64+
config,
65+
});
66+
67+
// Start the actix-web server.
68+
let server = HttpServer::new(move || {
69+
actix_web::App::new()
70+
.wrap(Logger::default())
71+
.wrap(Logger::new("%a %{User-Agent}i"))
72+
.wrap(middleware::Compress::default())
73+
.app_data(app_data.clone())
74+
// raft internal RPC
75+
.service(raft::append)
76+
.service(raft::snapshot)
77+
.service(raft::vote)
78+
// admin API
79+
.service(management::init)
80+
.service(management::add_learner)
81+
.service(management::change_membership)
82+
.service(management::metrics)
83+
// application API
84+
.service(api::write)
85+
.service(api::read)
86+
.service(api::linearizable_read)
87+
});
88+
89+
let x = server.bind(addr)?;
90+
91+
x.run().await
92+
}

0 commit comments

Comments
 (0)