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: u64Soft memory ceiling in bytes. 0 (default) = unlimited.
eviction_policy: EvictionPolicyEviction policy when over maxmemory. Default NoEviction.
data_dir: Option<PathBuf>Persistence directory. None = pure in-memory (no AOF, no snapshot).
aof: boolAOF on/off when data_dir is set. Defaults to true (on) when
with_persist was called; ignored if data_dir is None.
appendfsync: AppendFsyncAOF fsync policy. Default EverySec (matches Redis: ≤ 1 s loss).
snapshot_filename: StringSnapshot 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: StringAOF 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: TtlReaperModeTTL reaper mode. Default Background.
reaper_interval: DurationReaper tick interval. Default 100 ms (10 Hz).
reaper_samples: usizetick_expire samples per round. Default 20 (matches Redis).
reaper_max_rounds: u32Max sample rounds per tick. Default 16.
auto_aof_rewrite_pct: u32Auto-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: u64Floor below which auto-rewrite is skipped. Default 64 MiB (Redis).
shards: usizeKeyspace 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
impl Config
Sourcepub fn with_persist(self, dir: impl Into<PathBuf>) -> Self
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?
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
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}Sourcepub fn without_aof(self) -> Self
pub fn without_aof(self) -> Self
Disable the AOF (snapshot-only persistence — explicit save_snapshot
calls are the only way data survives restart).
Sourcepub fn with_max_memory(self, bytes: u64) -> Self
pub fn with_max_memory(self, bytes: u64) -> Self
Soft memory ceiling in bytes. 0 keeps the default (unlimited).
Examples found in repository?
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}Sourcepub fn with_eviction(self, policy: EvictionPolicy) -> Self
pub fn with_eviction(self, policy: EvictionPolicy) -> Self
Eviction policy when over Self::with_max_memory.
Examples found in repository?
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}Sourcepub fn with_appendfsync(self, fsync: AppendFsync) -> Self
pub fn with_appendfsync(self, fsync: AppendFsync) -> Self
AOF fsync policy. Default AppendFsync::EverySec.
Examples found in repository?
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}Sourcepub fn with_auto_aof_rewrite(self, pct: u32, min_size: u64) -> Self
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.
Sourcepub fn with_shards(self, n: usize) -> Self
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?
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}Sourcepub fn with_metric_sink(
self,
sink: impl Fn(KevyMetric) + Send + Sync + 'static,
) -> Self
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.
Sourcepub fn with_ttl_reaper_manual(self) -> Self
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?
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
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}Sourcepub fn with_reaper_interval(self, iv: Duration) -> Self
pub fn with_reaper_interval(self, iv: Duration) -> Self
Override the background reaper interval. Default 100 ms.
Sourcepub fn with_snapshot_filename(self, name: impl Into<String>) -> Self
pub fn with_snapshot_filename(self, name: impl Into<String>) -> Self
Override the snapshot file name inside data_dir.
Sourcepub fn with_aof_filename(self, name: impl Into<String>) -> Self
pub fn with_aof_filename(self, name: impl Into<String>) -> Self
Override the AOF file name inside data_dir.
Examples found in repository?
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}