Embedded dual-layer persistent store new¶
A lightweight, disk-persisted key/value store for the server/HOST's occasionally-referenced,
long-lived data (profiles, inventories, session/world metadata). It is self-contained (no
external DB) and built on the same worker job pool as the web fetch, so every
disk touch runs off the fixed-tick main thread and its results are delivered on the tick
thread inside lattice_runner_tick() — the pump never blocks on disk.
Architecture (CQRS + cache-aside):
flowchart LR
G["lattice_store_get"] -->|memcache HIT| HIT[return same tick<br/>from_cache=1]
G -->|miss| RD[(durable READ store<br/>replica)]
P["lattice_store_put / delete"] --> WR[(durable WRITE store<br/>source of truth, fsync'd)]
WR -->|sync per-key / async-batch| RD
WR -->|update or evict| MC[in-memory LRU<br/>memcache]
MC -.front of.-> RD
A GET is a synchronous memcache hit (no disk) or an async read-store read. A PUT/DELETE persists to the write store, syncs the key to the read store, and updates-or-evicts dependent memcache entries — so a read-after-write is coherent (in the default per-key sync mode).
Threading contract
All lattice_store_* calls are main-thread (same thread as lattice_runner_tick). The
on_store_result callback and lattice_store_poll only ever surface results on the tick
thread.
lattice_store_open / close / configure¶
LATTICE_API lattice_result lattice_store_open(lattice_runner* r, const lattice_store_config* cfg);
LATTICE_API void lattice_store_close(lattice_runner* r);
LATTICE_API lattice_result lattice_store_configure(lattice_runner* r, const lattice_store_config* cfg);
open opens the store under cfg->dir, replaying the on-disk logs (data persists across
restart; the read replica is reconciled to the write store on reopen). close leaves the data on
disk (writes were already fsync'd). configure reconfigures sync mode / write policy / memcache
capacity in place (dir is fixed at open). Role: authority. Returns: LATTICE_OK or an
error.
lattice_store_config¶
typedef struct {
const char* dir; /* dir for write.log + read.log; CALLER must create it */
lattice_store_sync_mode sync_mode;
lattice_store_write_policy write_policy;
uint32_t memcache_capacity; /* 0 => a small default */
double compact_ratio; /* dead-byte fraction to compact; 0 => default */
uint64_t compact_min_bytes; /* gate so a tiny log is never churned */
} lattice_store_config;
typedef enum {
LATTICE_STORE_SYNC_PER_KEY = 0, /* mirror on the SAME commit (read-after-write coherent) */
LATTICE_STORE_SYNC_ASYNC_BATCH = 1 /* mirror in batches (eventual; reads may briefly lag) */
} lattice_store_sync_mode;
typedef enum {
LATTICE_STORE_CACHE_EVICT = 0, /* drop the dependent entry; next read re-fetches (default) */
LATTICE_STORE_CACHE_UPDATE = 1 /* overwrite a single-key dependent entry in place */
} lattice_store_write_policy;
/* caller must ensure the dir exists */
lattice_store_config sc = {0};
sc.dir = "/var/lib/mygame/store";
sc.sync_mode = LATTICE_STORE_SYNC_PER_KEY;
sc.write_policy = LATTICE_STORE_CACHE_EVICT;
sc.memcache_capacity = 4096;
lattice_store_open(r, &sc);
lattice_store_get¶
Async GET. cache_on_read (1/0): on a read-store read, whether to populate the memcache with the
value (tagged with key, so a later write to it invalidates the entry). Returns: a non-zero
handle; the result is delivered via on_store_result /
lattice_store_poll. A memcache hit still returns a handle and delivers
a result (from_cache = 1) — on the same tick, with no disk read.
lattice_store_put / delete¶
LATTICE_API uint64_t lattice_store_put(lattice_runner* r, const char* key,
const uint8_t* value, uint32_t value_len);
LATTICE_API uint64_t lattice_store_delete(lattice_runner* r, const char* key);
put durably persists the value, syncs the read replica, and updates/evicts dependent cache
entries. delete tombstones the key in the write store, mirrors to the read store, and evicts the
cache. Returns: a non-zero handle; the result arrives later.
lattice_store_poll¶
LATTICE_API int lattice_store_poll(lattice_runner* r,
uint64_t* out_handle, int* out_op, int* out_ok, int* out_found,
int* out_from_cache, uint8_t* out_value, uint32_t out_value_cap,
uint32_t* out_value_len);
Main thread: poll the next completed store op (callback-free alternative). Fills the out params
(any may be NULL): *out_handle, *out_op (a lattice_store_op),
*out_ok, *out_found, *out_from_cache. The GET value is copied into out_value up to
out_value_cap and *out_value_len is the full value length. Returns: 1 if a result was
dequeued, 0 if none ready. FIFO.
lattice_store_build_key¶
LATTICE_API uint32_t lattice_store_build_key(uint32_t func_id, uint64_t user_id,
const uint64_t* params, uint32_t param_count,
char* out, uint32_t cap);
Deterministic composite key builder. Builds a stable string key from a function id, a user id,
and param_count params, so the same logical read derives a byte-identical key on any peer
/ any run. Writes up to cap bytes (including the NUL) into out and returns the full key
length (excluding the NUL), which may exceed cap-1 (truncated but always NUL-terminated when
cap > 0).
char key[128];
uint64_t params[] = { season_id, slot };
lattice_store_build_key(/*func_id*/ FN_INVENTORY, user_id, params, 2, key, sizeof key);
lattice_store_get(r, key, /*cache_on_read*/ 1);
Reacting — on_store_result¶
void (*on_store_result)(void* user_data, uint64_t handle, int op, int ok, int found,
int from_cache, const uint8_t* value, uint32_t value_len);
Fires synchronously inside lattice_runner_tick() on the main thread. handle is the value
the call returned; op is a lattice_store_op (GET/PUT/DELETE); ok is 1 if the op
completed without an I/O error; found is 1 if the key existed (a GET miss is ok=1, found=0;
PUT is always found=1; DELETE reports whether the key had existed); from_cache is 1 if a GET
was served from the memcache; value/value_len is the GET value (owned by the core, valid only
for the call). A NULL pointer (the default) means "poll for results via lattice_store_poll
instead".
typedef enum {
LATTICE_STORE_GET = 0,
LATTICE_STORE_PUT = 1,
LATTICE_STORE_DELETE = 2
} lattice_store_op;
Introspection / metrics¶
LATTICE_API uint64_t lattice_store_read_store_reads(lattice_runner* r); /* real read-store reads done */
LATTICE_API uint64_t lattice_store_cache_hits(lattice_runner* r);
LATTICE_API uint64_t lattice_store_cache_misses(lattice_runner* r);
LATTICE_API uint32_t lattice_store_memcache_size(lattice_runner* r);
LATTICE_API uint64_t lattice_store_write_count(lattice_runner* r); /* live keys in the write store */
LATTICE_API uint64_t lattice_store_read_count(lattice_runner* r); /* live keys in the read store */
LATTICE_API uint64_t lattice_store_compactions(lattice_runner* r); /* write-store compactions run */