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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "sparkv"
authors = ["U-Zyn Chua <chua@uzyn.com>"]
version = "0.1.1"
version = "0.2.0"
edition = "2021"
description = "Expirable in-memory key-value store"
keywords = ["key-value", "database", "embedded-database", "ttl", "in-memory"]
Expand Down
92 changes: 87 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SparKV is an expirable in-memory key-value store for Rust.
1. Flexible expiration duration (a.k.a. time-to-live or TTL) per entry instead of database-wide common TTL.
1. This is similar to that of DNS where each entries of the same domain can have its own unique TTL.
2. Automatically clears expired entries by default.
3. String-based key-value store.
3. Generic over the value type, defaulting to `String`. Store any type you like.
4. Fast data entry enforcements, including ensuring entry size, database size and max TTL.
5. SparKV is intentionally not an LRU cache.
6. Configurable.
Expand All @@ -25,21 +25,103 @@ Quick start
```rust
use sparkv::SparKV;

// The value type defaults to String.
let mut sparkv = SparKV::new();
sparkv.set("your-key", "your-value"); // write
sparkv.set("your-key", "your-value".to_string()).unwrap(); // write
let value = sparkv.get("your-key").unwrap(); // read

// Write with unique TTL
sparkv.set_with_ttl("diff-ttl", "your-value", std::time::Duration::from_secs(60));
sparkv.set_with_ttl("diff-ttl", "your-value".to_string(), std::time::Duration::from_secs(60)).unwrap();
```

See `config.rs` for more configuration options.
`set` takes the value by value, and `get` returns an owned clone (so the value type
must implement `Clone`).

### Generic value types

SparKV is generic over the value type. Built-in types (integers, `Vec<u8>`, etc.)
work out of the box:

```rust
use sparkv::SparKV;

let mut counters: SparKV<i64> = SparKV::new();
counters.set("hits", 42).unwrap();
assert_eq!(counters.get("hits"), Some(42));
```

To store your own type, implement `ValueSize` so SparKV can enforce `max_item_size`
(return `0` if you don't care about size enforcement):

```rust
use sparkv::{SparKV, ValueSize};

#[derive(Clone)]
struct Session {
user_id: u64,
csrf: String,
}

impl ValueSize for Session {
fn value_size(&self) -> usize {
std::mem::size_of::<u64>() + self.csrf.len()
}
}

let mut sessions: SparKV<Session> = SparKV::new();
sessions
.set_with_ttl(
"sid",
Session { user_id: 1, csrf: "abc".to_string() },
std::time::Duration::from_secs(60),
)
.unwrap();
```

See `config.rs` for more configuration options. `max_item_size` is an
`Option<usize>` — set it to `None` to disable per-entry size enforcement.


## Migrating from 0.1 to 0.2

`0.2.0` makes the value type generic (`SparKV<V>`, defaulting to `String`). The store
behaves exactly as before; the breaking changes are limited to how values are passed in
and how `max_item_size` is configured. The `SparKV` and `KvEntry` type names still
resolve to their `String` forms, so type annotations and struct fields are unaffected.

**1. `set` / `set_with_ttl` now take the value by value.** Pass an owned value instead
of a `&str`:

```rust
// 0.1
kv.set("key", "value");
// 0.2
kv.set("key", "value".to_string()); // or "value".into()
```

`get` and `delete` still return `Option<String>` for the default `String` store, so
your read sites do not change.

**2. `Config.max_item_size` is now `Option<usize>`.** Wrap the limit in `Some`, or use
`None` to disable size enforcement:

```rust
// 0.1
config.max_item_size = 500_000;
// 0.2
config.max_item_size = Some(500_000); // or None to disable
```

**3. Custom value types need a `ValueSize` impl** — only relevant if you switch away from
`String`. Built-in types (integers, `Vec<u8>`, `&[u8]`, etc.) already implement it; see
the generic example above.

No other call sites change.


## TODO

1. Documentations
1. Support generic data types

## License

Expand Down
8 changes: 4 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Config {
pub max_items: usize,
pub max_item_size: usize,
pub max_item_size: Option<usize>,
pub max_ttl: std::time::Duration,
pub default_ttl: std::time::Duration,
pub auto_clear_expired: bool,
Expand All @@ -11,7 +11,7 @@ impl Config {
pub fn new() -> Self {
Config {
max_items: 10_000,
max_item_size: 500_000,
max_item_size: Some(500_000),
max_ttl: std::time::Duration::from_secs(60 * 60),
default_ttl: std::time::Duration::from_secs(5 * 60), // 5 minutes
auto_clear_expired: true,
Expand All @@ -33,9 +33,9 @@ mod tests {
fn test_new() {
let config: Config = Config::new();
assert_eq!(config.max_items, 10_000);
assert_eq!(config.max_item_size, 500_000);
assert_eq!(config.max_item_size, Some(500_000));
assert_eq!(config.max_ttl, std::time::Duration::from_secs(60 * 60));
assert_eq!(config.default_ttl, std::time::Duration::from_secs(5 * 60));
assert_eq!(config.auto_clear_expired, true);
assert!(config.auto_clear_expired);
}
}
4 changes: 2 additions & 2 deletions src/expentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl ExpEntry {
}
}

pub fn from_kv_entry(kv_entry: &KvEntry) -> Self {
pub fn from_kv_entry<V>(kv_entry: &KvEntry<V>) -> Self {
Self {
key: kv_entry.key.clone(),
expired_at: kv_entry.expired_at,
Expand Down Expand Up @@ -60,7 +60,7 @@ mod tests {
fn test_from_kventry() {
let kv_entry = KvEntry::new(
"keyFromKV",
"value from KV",
String::from("value from KV"),
std::time::Duration::from_secs(10),
);
let exp_item = ExpEntry::from_kv_entry(&kv_entry);
Expand Down
18 changes: 11 additions & 7 deletions src/kventry.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KvEntry {
#[derive(Debug, Clone)]
pub struct KvEntry<V = String> {
pub key: String,
pub value: String,
pub value: V,
pub expired_at: std::time::Instant,
}

impl KvEntry {
pub fn new(key: &str, value: &str, expiration: std::time::Duration) -> Self {
impl<V> KvEntry<V> {
pub fn new(key: &str, value: V, expiration: std::time::Duration) -> Self {
let expired_at: std::time::Instant = std::time::Instant::now() + expiration;
Self {
key: String::from(key),
value: String::from(value),
value,
expired_at,
}
}
Expand All @@ -22,7 +22,11 @@ mod tests {

#[test]
fn test_new() {
let item = KvEntry::new("key", "value", std::time::Duration::from_secs(10));
let item = KvEntry::new(
"key",
String::from("value"),
std::time::Duration::from_secs(10),
);
assert_eq!(item.key, "key");
assert_eq!(item.value, "value");
assert!(item.expired_at > std::time::Instant::now() + std::time::Duration::from_secs(9));
Expand Down
Loading
Loading