Skip to main content

Config

Struct Config 

Source
pub struct Config {
Show 14 fields pub maxmemory: u64, pub eviction_policy: EvictionPolicy, pub data_dir: Option<PathBuf>, pub aof: bool, pub appendfsync: AppendFsync, pub snapshot_filename: String, pub aof_filename: String, pub ttl_reaper: TtlReaperMode, pub reaper_interval: Duration, pub reaper_samples: usize, pub reaper_max_rounds: u32, pub auto_aof_rewrite_pct: u32, pub auto_aof_rewrite_min_size: u64, pub shards: usize, /* private fields */
}
Expand description

Embedded-store config. Build by chaining with_* methods on Config::default.

Fields§

§maxmemory: u64

Soft memory ceiling in bytes. 0 (default) = unlimited.

§eviction_policy: EvictionPolicy

Eviction policy when over maxmemory. Default NoEviction.

§data_dir: Option<PathBuf>

Persistence directory. None = pure in-memory (no AOF, no snapshot).

§aof: bool

AOF on/off when data_dir is set. Defaults to true (on) when with_persist was called; ignored if data_dir is None.

§appendfsync: AppendFsync

AOF fsync policy. Default EverySec (matches Redis: ≤ 1 s loss).

§snapshot_filename: String

Snapshot file name inside data_dir (single-shard only; n > 1 always uses dump-{i}.rdb). Default "dump-0.rdb". A custom name opts the dir out of server interop: no shards.meta is recorded, and a kevy server opening the same dir won’t find the files.

§aof_filename: String

AOF file name inside data_dir (single-shard only; n > 1 always uses aof-{i}.aof). Default "aof-0.aof". Same interop opt-out as Self::snapshot_filename.

§ttl_reaper: TtlReaperMode

TTL reaper mode. Default Background.

§reaper_interval: Duration

Reaper tick interval. Default 100 ms (10 Hz).

§reaper_samples: usize

tick_expire samples per round. Default 20 (matches Redis).

§reaper_max_rounds: u32

Max sample rounds per tick. Default 16.

§auto_aof_rewrite_pct: u32

Auto-BGREWRITEAOF trigger: rewrite when the live AOF has grown by at least this percent over its size at the previous rewrite. 0 disables (call crate::Store::rewrite_aof manually). Default 100 (Redis).

§auto_aof_rewrite_min_size: u64

Floor below which auto-rewrite is skipped. Default 64 MiB (Redis).

§shards: usize

Keyspace shard count (hash(key) % shards), each a fully independent lock + keyspace + AOF (shared-nothing) — concurrent access scales across cores. Default 1 (single shard = the original single-lock / single-aof-0.aof layout, zero migration). Set > 1 via Self::with_shards; the first open with > 1 re-shards an existing single AOF into per-shard files.

Implementations§

Source§

impl Config

Source

pub fn with_persist(self, dir: impl Into<PathBuf>) -> Self

Enable persistence under dir — snapshot file + AOF land inside. AOF defaults on; turn it off with Self::without_aof for pure snapshot-only durability.

Examples found in repository?
examples/replay_real_aof.rs (line 35)
14fn main() {
15    let path = std::env::args()
16        .nth(1)
17        .expect("usage: replay_real_aof <aof-path>");
18    let src = PathBuf::from(&path);
19    let bytes = std::fs::metadata(&src)
20        .map(|m| m.len())
21        .unwrap_or(0);
22    println!("reproducer: staging {} bytes from {}", bytes, src.display());
23
24    let dir = std::env::temp_dir().join(format!(
25        "kevy-aof-reproducer-{}",
26        std::process::id()
27    ));
28    std::fs::create_dir_all(&dir).expect("create temp dir");
29    let staged = dir.join("aof-0.aof");
30    std::fs::copy(&src, &staged).expect("copy AOF into staging dir");
31
32    println!("reproducer: opening Store from {}", dir.display());
33    let store = Store::open(
34        Config::default()
35            .with_persist(&dir)
36            .with_aof_filename("aof-0.aof"),
37    )
38    .expect("Store::open");
39
40    println!("reproducer: opened OK; dbsize = {}", store.dbsize());
41}
More examples
Hide additional examples
examples/bench_embed.rs (line 63)
45fn main() {
46    let n: usize = std::env::var("KEVY_BENCH_N")
47        .ok()
48        .and_then(|s| s.parse().ok())
49        .unwrap_or(2_000_000);
50    // Keys precomputed outside the timed loop so `format!`/alloc cost isn't
51    // attributed to kevy.
52    let keys: Vec<Vec<u8>> = (0..KEYS).map(|i| format!("k{i}").into_bytes()).collect();
53
54    println!("kevy-embedded in-process throughput — single thread, n={n}, {KEYS} keys, {}B val", VAL.len());
55
56    let s1 = Store::open(Config::default().with_ttl_reaper_manual()).unwrap();
57    bench("in-memory", &s1, n, &keys);
58
59    let dir2 = std::env::temp_dir().join("kevy_embed_bench_everysec");
60    let _ = std::fs::remove_dir_all(&dir2);
61    let s2 = Store::open(
62        Config::default()
63            .with_persist(&dir2)
64            .with_ttl_reaper_manual()
65            .with_appendfsync(AppendFsync::EverySec),
66    )
67    .unwrap();
68    bench("aof-everysec", &s2, n, &keys);
69
70    let dir3 = std::env::temp_dir().join("kevy_embed_bench_always");
71    let _ = std::fs::remove_dir_all(&dir3);
72    let s3 = Store::open(
73        Config::default()
74            .with_persist(&dir3)
75            .with_ttl_reaper_manual()
76            .with_appendfsync(AppendFsync::Always),
77    )
78    .unwrap();
79    // Always-fsync is one fdatasync per write (no group commit on the embedded
80    // single-op path) — fsync-rate-bound, so run far fewer ops to stay bounded.
81    bench("aof-always", &s3, (n / 20).max(50_000), &keys);
82
83    // TTL'd-key GET: the mailrs path (every cache key has a TTL). With the
84    // background reaper the cached clock is trusted (no per-get Instant::now);
85    // manual mode reads a fresh clock per get — the gap is the cached-clock win.
86    bench_ttl_get("ttl GET (cached clk)", false, n, &keys); // background reaper
87    bench_ttl_get("ttl GET (fresh clk) ", true, n, &keys); // manual reaper
88
89    drop((s1, s2, s3));
90    let _ = std::fs::remove_dir_all(&dir2);
91    let _ = std::fs::remove_dir_all(&dir3);
92}
Source

pub fn without_aof(self) -> Self

Disable the AOF (snapshot-only persistence — explicit save_snapshot calls are the only way data survives restart).

Source

pub fn with_max_memory(self, bytes: u64) -> Self

Soft memory ceiling in bytes. 0 keeps the default (unlimited).

Examples found in repository?
examples/embedded-cache.rs (line 12)
9fn main() -> std::io::Result<()> {
10    let s = Store::open(
11        Config::default()
12            .with_max_memory(200 * 1024)
13            .with_eviction(EvictionPolicy::AllKeysLru),
14    )?;
15
16    for i in 0..10_000 {
17        let key = format!("user:{i:05}");
18        let val = format!("user-payload-{i}");
19        s.set(key.as_bytes(), val.as_bytes())?;
20    }
21
22    println!("dbsize after insert flood: {}", s.dbsize());
23    println!("used_memory: {} bytes (limit 200 KiB)", s.used_memory());
24    println!("evictions_total: {}", s.evictions_total());
25
26    // Touch a recent key — it should still be live.
27    let recent = format!("user:0{}", 9999);
28    println!(
29        "user:09999 → {:?}",
30        s.get(recent.as_bytes())?.as_deref().map(String::from_utf8_lossy)
31    );
32
33    Ok(())
34}
Source

pub fn with_eviction(self, policy: EvictionPolicy) -> Self

Eviction policy when over Self::with_max_memory.

Examples found in repository?
examples/embedded-cache.rs (line 13)
9fn main() -> std::io::Result<()> {
10    let s = Store::open(
11        Config::default()
12            .with_max_memory(200 * 1024)
13            .with_eviction(EvictionPolicy::AllKeysLru),
14    )?;
15
16    for i in 0..10_000 {
17        let key = format!("user:{i:05}");
18        let val = format!("user-payload-{i}");
19        s.set(key.as_bytes(), val.as_bytes())?;
20    }
21
22    println!("dbsize after insert flood: {}", s.dbsize());
23    println!("used_memory: {} bytes (limit 200 KiB)", s.used_memory());
24    println!("evictions_total: {}", s.evictions_total());
25
26    // Touch a recent key — it should still be live.
27    let recent = format!("user:0{}", 9999);
28    println!(
29        "user:09999 → {:?}",
30        s.get(recent.as_bytes())?.as_deref().map(String::from_utf8_lossy)
31    );
32
33    Ok(())
34}
Source

pub fn with_appendfsync(self, fsync: AppendFsync) -> Self

AOF fsync policy. Default AppendFsync::EverySec.

Examples found in repository?
examples/bench_embed.rs (line 65)
45fn main() {
46    let n: usize = std::env::var("KEVY_BENCH_N")
47        .ok()
48        .and_then(|s| s.parse().ok())
49        .unwrap_or(2_000_000);
50    // Keys precomputed outside the timed loop so `format!`/alloc cost isn't
51    // attributed to kevy.
52    let keys: Vec<Vec<u8>> = (0..KEYS).map(|i| format!("k{i}").into_bytes()).collect();
53
54    println!("kevy-embedded in-process throughput — single thread, n={n}, {KEYS} keys, {}B val", VAL.len());
55
56    let s1 = Store::open(Config::default().with_ttl_reaper_manual()).unwrap();
57    bench("in-memory", &s1, n, &keys);
58
59    let dir2 = std::env::temp_dir().join("kevy_embed_bench_everysec");
60    let _ = std::fs::remove_dir_all(&dir2);
61    let s2 = Store::open(
62        Config::default()
63            .with_persist(&dir2)
64            .with_ttl_reaper_manual()
65            .with_appendfsync(AppendFsync::EverySec),
66    )
67    .unwrap();
68    bench("aof-everysec", &s2, n, &keys);
69
70    let dir3 = std::env::temp_dir().join("kevy_embed_bench_always");
71    let _ = std::fs::remove_dir_all(&dir3);
72    let s3 = Store::open(
73        Config::default()
74            .with_persist(&dir3)
75            .with_ttl_reaper_manual()
76            .with_appendfsync(AppendFsync::Always),
77    )
78    .unwrap();
79    // Always-fsync is one fdatasync per write (no group commit on the embedded
80    // single-op path) — fsync-rate-bound, so run far fewer ops to stay bounded.
81    bench("aof-always", &s3, (n / 20).max(50_000), &keys);
82
83    // TTL'd-key GET: the mailrs path (every cache key has a TTL). With the
84    // background reaper the cached clock is trusted (no per-get Instant::now);
85    // manual mode reads a fresh clock per get — the gap is the cached-clock win.
86    bench_ttl_get("ttl GET (cached clk)", false, n, &keys); // background reaper
87    bench_ttl_get("ttl GET (fresh clk) ", true, n, &keys); // manual reaper
88
89    drop((s1, s2, s3));
90    let _ = std::fs::remove_dir_all(&dir2);
91    let _ = std::fs::remove_dir_all(&dir3);
92}
Source

pub fn with_auto_aof_rewrite(self, pct: u32, min_size: u64) -> Self

Auto-BGREWRITEAOF thresholds: rewrite once the AOF has grown pct percent past its size at the last rewrite AND is at least min_size bytes. In Background reaper mode the check runs on the reaper tick; in Manual mode it runs when you call crate::Store::tick. Pass pct = 0 to disable auto-rewrite (you can still call crate::Store::rewrite_aof yourself). Defaults: 100 % / 64 MiB.

Source

pub fn with_shards(self, n: usize) -> Self

Shard the keyspace into n shared-nothing partitions (hash(key) % n), each with its own lock + keyspace + AOF, so concurrent access scales across cores. n clamps to ≥ 1; 1 (default) is the original single-shard layout. Going from a single-AOF store to n > 1 re-shards the existing aof-0.aof into aof-0..aof-{n-1} on the next open (the old file is backed up to aof-0.aof.premigration.<ts> first). Pub/sub is process-wide (handled on shard 0), not sharded.

Examples found in repository?
examples/bench_embed_mt.rs (line 64)
52fn main() {
53    let n_per: usize = std::env::var("KEVY_BENCH_N")
54        .ok()
55        .and_then(|s| s.parse().ok())
56        .unwrap_or(2_000_000);
57    let keys: Vec<Vec<u8>> = (0..KEYS).map(|i| format!("k{i}").into_bytes()).collect();
58    let shards: usize = std::env::var("KEVY_SHARDS")
59        .ok()
60        .and_then(|s| s.parse().ok())
61        .unwrap_or(1);
62
63    // Shared in-memory store (no AOF) — isolate the lock/keyspace from disk.
64    let store = Store::open(Config::default().with_shards(shards).with_ttl_reaper_manual()).unwrap();
65    for k in &keys {
66        store.set(k, VAL).unwrap();
67    }
68
69    println!(
70        "kevy-embedded MULTI-THREAD throughput — in-memory, shards={shards}, {KEYS} keys, {}B val, n={n_per}/thread",
71        VAL.len()
72    );
73    for &t in &[1usize, 2, 4, 8, 10] {
74        run("GET", &store, t, n_per, &keys, false);
75    }
76    for &t in &[1usize, 2, 4, 8, 10] {
77        run("SET", &store, t, n_per, &keys, true);
78    }
79}
Source

pub fn with_metric_sink( self, sink: impl Fn(KevyMetric) + Send + Sync + 'static, ) -> Self

Register a push-style metric callback. It receives a crate::KevyMetric for each AOF replay (startup) and AOF rewrite (compaction) — wire it to Prometheus / a log line / a counter. The callback runs synchronously on the emitting thread (reaper thread for background rewrites), so keep it fast and non-blocking. Replaces any previously-set sink.

Source

pub fn with_ttl_reaper_manual(self) -> Self

Caller-driven TTL reaping — disables the background thread. Required for WASM (no threads available). Call crate::Store::tick yourself from your event loop.

Examples found in repository?
examples/bench_embed_mt.rs (line 64)
52fn main() {
53    let n_per: usize = std::env::var("KEVY_BENCH_N")
54        .ok()
55        .and_then(|s| s.parse().ok())
56        .unwrap_or(2_000_000);
57    let keys: Vec<Vec<u8>> = (0..KEYS).map(|i| format!("k{i}").into_bytes()).collect();
58    let shards: usize = std::env::var("KEVY_SHARDS")
59        .ok()
60        .and_then(|s| s.parse().ok())
61        .unwrap_or(1);
62
63    // Shared in-memory store (no AOF) — isolate the lock/keyspace from disk.
64    let store = Store::open(Config::default().with_shards(shards).with_ttl_reaper_manual()).unwrap();
65    for k in &keys {
66        store.set(k, VAL).unwrap();
67    }
68
69    println!(
70        "kevy-embedded MULTI-THREAD throughput — in-memory, shards={shards}, {KEYS} keys, {}B val, n={n_per}/thread",
71        VAL.len()
72    );
73    for &t in &[1usize, 2, 4, 8, 10] {
74        run("GET", &store, t, n_per, &keys, false);
75    }
76    for &t in &[1usize, 2, 4, 8, 10] {
77        run("SET", &store, t, n_per, &keys, true);
78    }
79}
More examples
Hide additional examples
examples/bench_embed.rs (line 56)
45fn main() {
46    let n: usize = std::env::var("KEVY_BENCH_N")
47        .ok()
48        .and_then(|s| s.parse().ok())
49        .unwrap_or(2_000_000);
50    // Keys precomputed outside the timed loop so `format!`/alloc cost isn't
51    // attributed to kevy.
52    let keys: Vec<Vec<u8>> = (0..KEYS).map(|i| format!("k{i}").into_bytes()).collect();
53
54    println!("kevy-embedded in-process throughput — single thread, n={n}, {KEYS} keys, {}B val", VAL.len());
55
56    let s1 = Store::open(Config::default().with_ttl_reaper_manual()).unwrap();
57    bench("in-memory", &s1, n, &keys);
58
59    let dir2 = std::env::temp_dir().join("kevy_embed_bench_everysec");
60    let _ = std::fs::remove_dir_all(&dir2);
61    let s2 = Store::open(
62        Config::default()
63            .with_persist(&dir2)
64            .with_ttl_reaper_manual()
65            .with_appendfsync(AppendFsync::EverySec),
66    )
67    .unwrap();
68    bench("aof-everysec", &s2, n, &keys);
69
70    let dir3 = std::env::temp_dir().join("kevy_embed_bench_always");
71    let _ = std::fs::remove_dir_all(&dir3);
72    let s3 = Store::open(
73        Config::default()
74            .with_persist(&dir3)
75            .with_ttl_reaper_manual()
76            .with_appendfsync(AppendFsync::Always),
77    )
78    .unwrap();
79    // Always-fsync is one fdatasync per write (no group commit on the embedded
80    // single-op path) — fsync-rate-bound, so run far fewer ops to stay bounded.
81    bench("aof-always", &s3, (n / 20).max(50_000), &keys);
82
83    // TTL'd-key GET: the mailrs path (every cache key has a TTL). With the
84    // background reaper the cached clock is trusted (no per-get Instant::now);
85    // manual mode reads a fresh clock per get — the gap is the cached-clock win.
86    bench_ttl_get("ttl GET (cached clk)", false, n, &keys); // background reaper
87    bench_ttl_get("ttl GET (fresh clk) ", true, n, &keys); // manual reaper
88
89    drop((s1, s2, s3));
90    let _ = std::fs::remove_dir_all(&dir2);
91    let _ = std::fs::remove_dir_all(&dir3);
92}
93
94/// GET throughput over keys that all carry a (long) TTL — the mailrs cache
95/// shape. `manual_reaper` toggles whether the store trusts the cached clock
96/// (background) or reads a fresh clock per get (manual).
97fn bench_ttl_get(label: &str, manual_reaper: bool, n: usize, keys: &[Vec<u8>]) {
98    let cfg = if manual_reaper {
99        Config::default().with_ttl_reaper_manual()
100    } else {
101        Config::default() // background reaper (default) → cached clock trusted
102    };
103    let store = Store::open(cfg).unwrap();
104    let ttl = std::time::Duration::from_secs(3600); // never expires during the run
105    for k in keys {
106        store.set_with_ttl(k, VAL, ttl).unwrap();
107    }
108    let t = Instant::now();
109    let mut hits = 0usize;
110    for i in 0..n {
111        if store.get(&keys[i % KEYS]).unwrap().is_some() {
112            hits += 1;
113        }
114    }
115    std::hint::black_box(hits);
116    println!("[{label}] GET {:>10.0} ops/s", n as f64 / t.elapsed().as_secs_f64());
117}
Source

pub fn with_reaper_interval(self, iv: Duration) -> Self

Override the background reaper interval. Default 100 ms.

Source

pub fn with_snapshot_filename(self, name: impl Into<String>) -> Self

Override the snapshot file name inside data_dir.

Source

pub fn with_aof_filename(self, name: impl Into<String>) -> Self

Override the AOF file name inside data_dir.

Examples found in repository?
examples/replay_real_aof.rs (line 36)
14fn main() {
15    let path = std::env::args()
16        .nth(1)
17        .expect("usage: replay_real_aof <aof-path>");
18    let src = PathBuf::from(&path);
19    let bytes = std::fs::metadata(&src)
20        .map(|m| m.len())
21        .unwrap_or(0);
22    println!("reproducer: staging {} bytes from {}", bytes, src.display());
23
24    let dir = std::env::temp_dir().join(format!(
25        "kevy-aof-reproducer-{}",
26        std::process::id()
27    ));
28    std::fs::create_dir_all(&dir).expect("create temp dir");
29    let staged = dir.join("aof-0.aof");
30    std::fs::copy(&src, &staged).expect("copy AOF into staging dir");
31
32    println!("reproducer: opening Store from {}", dir.display());
33    let store = Store::open(
34        Config::default()
35            .with_persist(&dir)
36            .with_aof_filename("aof-0.aof"),
37    )
38    .expect("Store::open");
39
40    println!("reproducer: opened OK; dbsize = {}", store.dbsize());
41}

Trait Implementations§

Source§

impl Clone for Config

Source§

fn clone(&self) -> Config

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Config

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Config

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.