Skip to content

Key-value store

The KV engine is a sharded, in-memory store with a lazy slab-pool allocator, TTL, optional mmap persistence, and optional per-shard Raft replication. You use it through the rostam.Store facade (any backend) or, for a standalone in-process cache, through cache.Cache directly.

Core operations

_ = store.Put(ctx, []byte("user:42"), payload, 5*time.Minute) // ttl 0 = no expiry
v, err := store.Get(ctx, []byte("user:42"))                   // rostam.ErrNotFound on miss/expiry
existed, err := store.Del(ctx, []byte("user:42"))

Beyond get/put/del, two built-in atomic ops run server-side through Call — no read-modify-write race, no extra round trips:

// counter += 1, returns the new value as big-endian int64
res, err := store.Call(ctx, "incr", ops.EncodeIncrArgs([]byte("views:42"), 1))
n, _ := ops.DecodeIncrResult(res)

// refresh a TTL without rewriting the value
_, err = store.Call(ctx, "expire", ops.EncodeExpireArgs([]byte("user:42"), time.Hour))

Call(ctx, name, args) dispatches any registered op by name. Read-only ops execute locally on the routed shard; read-write ops serialize through the shard's Raft log on Embedded (and under the shard lock on Direct). This is the extension point for custom ops and WASM procedures.

TTL semantics

TTLs are absolute deadlines computed at write time. Expiry is enforced lazily on read plus by a background sweeper (cache.Config.TTLSweepIntervalMs, default 1000 ms; 0 disables the sweeper, lazy expiry still applies).

Leadership helpers

On replicated backends, writes must reach the shard leader. The facade exposes IsLeader(key) and LeaderAddr(key); the smart client uses the same topology data to route automatically. A write landing on a non-leader returns rostam.ErrNotLeader.

Standalone cache: allocation-free reads

The rostam.Store facade wraps a cache internally but does not expose it. When you need an allocation-free hot loop, use cache.New directly as a standalone session cache:

c, err := cache.New(cache.Config{})
_ = c.Put([]byte("user:42"), []byte(`{"coins":100}`), 5*time.Minute)

buf := make([]byte, 0, 256)
buf, err = c.GetInto(buf[:0], []byte("user:42")) // 0 allocs on a hit

What Get returns depends on the eviction policy. Under PolicyRejectWrites it is a slice aliasing the backing arena — zero-copy, but don't retain it across writes. Under the default PolicyRingbufEvict it is a freshly allocated copy you own and may retain freely, because eviction can overwrite live page bytes and an alias would be unsafe; that costs one allocation per hit. GetInto copies into your reusable buffer on either policy, is always the safe form to retain, and stays allocation-free. Configuration knobs (shards, page size, eviction policy, mmap durability) are covered in Cache tuning.

Backends at a glance

Direct Embedded Client
Process in-process in-process remote (TCP)
Consensus none per-shard Raft server-side
Get / Put (measured) ~29 ns / ~240 ns ~222 ns / ~12.7 µs (no-sync) ~1.7 µs / ~1.8 µs (loopback, Direct server)
Durability optional mmap Raft log + mmap warm-start server's

The backends and their configs are documented in Deployment modes; performance methodology in Performance.