DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
dbm.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file dbm.h
6 * @brief Log-structured hash key-value store on the WAL (DETWS_ENABLE_DBM, requires DETWS_ENABLE_WAL).
7 *
8 * A Bitcask-style key-value store: the value data lives append-only in the write-ahead log (wal_store.h)
9 * and an in-RAM open-addressed hash index maps each live key to where its latest value sits in the log.
10 * This is the design the measured SD envelope wants (docs/FEATURE_PERFORMANCE.md): every write is one of
11 * the WAL's fast sequential appends, never a slow durable random write.
12 *
13 * - **put / delete** append one record to the WAL and update the index. Writes are batched (unsynced);
14 * call ::detws_dbm_sync to checkpoint the WAL and make them durable.
15 * - **get** looks up the index and re-reads the value straight from the log (no per-key RAM copy).
16 * - **open** rebuilds the index by scanning the WAL, replaying puts and deletes in order, so the live
17 * key set is exactly what survived the last mount of the underlying store.
18 *
19 * The index is a fixed BSS array of ::DETWS_DBM_SLOTS slots (no heap); keys are bounded by
20 * ::DETWS_DBM_KEY_MAX and values by ::DETWS_DBM_VAL_MAX. Everything fails closed at those bounds. Like the
21 * other services, drive it from one context (a worker / loop), not concurrently.
22 *
23 * On-media record payload (inside a WAL record): `[op u8][key_len u16][val_len u32][key][value]` (LE),
24 * op 0 = put, op 1 = delete (a tombstone, val_len 0).
25 */
26
27#ifndef DETERMINISTICESPASYNCWEBSERVER_DBM_H
28#define DETERMINISTICESPASYNCWEBSERVER_DBM_H
29
30#include "ServerConfig.h"
31
32#if DETWS_ENABLE_DBM
33
35#include <stddef.h>
36#include <stdint.h>
37
38/** @brief One in-RAM index slot. `state`: 0 empty, 1 live, 2 deleted (tombstone, still probed through). */
39struct DetwsDbmSlot
40{
41 uint8_t state;
42 uint16_t key_len;
43 uint64_t hash;
44 uint64_t val_off; ///< data-region offset of the value bytes in the WAL
45 uint32_t val_len;
46 char key[DETWS_DBM_KEY_MAX];
47};
48
49/** @brief A dbm handle bound to a mounted ::WalStore. Declare one (static for BSS); no heap. */
50struct DetwsDbm
51{
52 WalStore *wal;
53 uint32_t count; ///< live keys
54 DetwsDbmSlot slots[DETWS_DBM_SLOTS];
55};
56
57/**
58 * @brief Bind @p db to a mounted @p wal and rebuild the index by replaying the log.
59 * @return false if the log holds more distinct live keys than ::DETWS_DBM_SLOTS (index would overflow).
60 */
61bool detws_dbm_open(DetwsDbm *db, WalStore *wal);
62
63/**
64 * @brief Insert or overwrite @p key -> @p val. Appends a WAL record and updates the index (not synced).
65 * @return false if @p key_len > ::DETWS_DBM_KEY_MAX, @p val_len > ::DETWS_DBM_VAL_MAX, the index is full
66 * (a new key with no free slot), or the WAL is full.
67 */
68bool detws_dbm_put(DetwsDbm *db, const char *key, uint16_t key_len, const uint8_t *val, uint32_t val_len);
69
70/**
71 * @brief Fetch @p key's value into @p buf (up to @p cap).
72 * @return the value length on success, or -1 if the key is absent or the value is larger than @p cap.
73 */
74long detws_dbm_get(DetwsDbm *db, const char *key, uint16_t key_len, uint8_t *buf, size_t cap);
75
76/**
77 * @brief Delete @p key (appends a tombstone record and drops it from the index).
78 * @return true if the key existed (and the tombstone was appended); false if absent or the WAL is full.
79 */
80bool detws_dbm_del(DetwsDbm *db, const char *key, uint16_t key_len);
81
82/** @brief @return true if @p key is live. */
83bool detws_dbm_contains(DetwsDbm *db, const char *key, uint16_t key_len);
84
85/** @brief @return the number of live keys. */
86uint32_t detws_dbm_count(DetwsDbm *db);
87
88/** @brief Make all writes since the last sync durable (checkpoints the WAL). @return false on I/O failure. */
89bool detws_dbm_sync(DetwsDbm *db);
90
91/** @brief Per-key callback for ::detws_dbm_iterate; return false to stop early. The key bytes are not
92 * NUL-terminated. Do not put/delete during iteration (it mutates the index). */
93using DetwsDbmIterCb = bool (*)(const char *key, uint16_t key_len, void *ctx);
94
95/** @brief Visit every live key (unordered). @return the number of keys visited. */
96uint32_t detws_dbm_iterate(DetwsDbm *db, DetwsDbmIterCb cb, void *ctx);
97
98/**
99 * @brief Bytes the live keys would occupy after a compaction (the summed framed size of one WAL record per
100 * live key). The current log (::wal_store_used on the bound store) is always at least this large; the
101 * difference is reclaimable dead space from overwritten and deleted keys. Pair the two to decide when the
102 * dead fraction is worth a ::detws_dbm_compact.
103 */
104uint64_t detws_dbm_live_bytes(DetwsDbm *db);
105
106/**
107 * @brief Compact the store: copy only the live keys (the latest value each, no tombstones) into a freshly
108 * formatted destination @p dst, checkpoint it, then rebind @p db to @p dst and rebuild the index - reclaiming
109 * all space held by overwritten / deleted keys (Bitcask-style merge to new, never in place).
110 *
111 * @p dst must be a mounted, freshly formatted ::WalStore backed by a DIFFERENT device than @p db's current
112 * log. On success @p db reads and writes through @p dst (the old device can then be reused); on failure
113 * (@p dst too small, or an I/O error) @p db is left UNCHANGED on its original log, so no data is lost and the
114 * caller can retry or keep using the old store.
115 * @return true when every live key was copied, the destination checkpointed, and the index rebuilt.
116 */
117bool detws_dbm_compact(DetwsDbm *db, WalStore *dst);
118
119#endif // DETWS_ENABLE_DBM
120#endif // DETERMINISTICESPASYNCWEBSERVER_DBM_H
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_DBM_SLOTS
#define DETWS_DBM_KEY_MAX
Durable write-ahead store: A/B superblock + checkpoint over a block-device seam (DETWS_ENABLE_WAL).