📈 GH Trending Explorer 🎲 Surprise me

← all repos

ReazGan / tarndb

git clone https://github.com/ReazGan/tarndb.git

Crash-safe embedded key-value store in C

—
stars, 24h
+50
stars, 7 days · #896
+50
stars, 30 days · #5,902
50
★ total · +50% this week
21
forks · 0 open issues

Star history starts 2026-10-20; the chart appears after the second daily snapshot.

LanguageC
LicenseMIT
Created2026-10-15 (5d ago)
Last push5d ago
Watchers0
OwnerUser

GitHub topics

Links

README excerpt
# tarn
A small embedded key-value store in C11 that does not lose writes.
Every put is appended to a segment file and fsynced before the call returns.
After a crash, a `kill -9` or a power cut, tarn comes back with every write it
acknowledged and never hands back a half-written one. No dependencies; builds
on Linux, macOS and Windows.
 [code] 
The whole API is in include/tarn.h.
## Build
 [code] 
This gives you `libtarn`, the `tarn` command line tool and `tarn_bench`.
To use it from another CMake project, `add_subdirectory(tarn)` and link
against `tarn`.
## How it works
**Segments.** Data lives in append-only files named `00000001.seg`,
`00000002.seg` and so on. Records are only ever added at the end of the
newest one; when it passes 64 MiB a new one is started. Nothing is ever
overwritten in place.
**Records.** Each record has a 16 byte header: a CRC32C of the header itself,
the key length, the value length and a CRC32C of the key and value. A delete
is a record with no value. The header checksum is separate on purpose: if a
value gets damaged, the record boundary is still trustworthy and only that
one record is lost instead of everything after it.
**Index.** An in-memory hash table maps every key to the segment and offset
of its newest record, so a get is one read. The table uses linear probing
with backward-shift deletion (no tombstones, lookups stay short as keys come
and go) and keyed SipHash-2-4, so keys picked by an attacker cannot be made
to collide.
**Recovery.** On open every segment is read front to back and every record
is checked. What the checker finds decides what happens:
- A torn record at the very end of the newest segment is what a crash in
 the middle of a write looks like. It is cut off and appends continue
 from there. Its unfinished bytes are never searched for records, so a
 value that happens to contain a copy of a tarn record cannot be mistaken
 for one.
- A record with a sound header but a bad value is skipped. The header still
 says where the next record starts.
- A damaged header hides where the next record starts. tarn slides forward
 a byte at a time until a header and the record behind it both check out,
 so one bad sector does not take the rest of the file with it.
- A zero-filled tail, which some file systems leave after a power cut, is
 treated as torn.
Skipped records are counted in `tarn_stats.corrupt_records`, bytes cut off in
`recovered_bytes`. Values are checked again on every `tarn_get`, so damage
that happens while the store is open is reported as `TARN_CORRUPT`, never
returned as data.
**Compaction.** `tarn_compact` copies every live record out of the old
segments into one new file, then renames it over the newest of them. The
rename is the commit point. The new file's header records the range of
segments it replaces, so if the process dies after the rename but before the
old files are deleted, the next open finds them and finishes the job. A
deleted key never comes back from a half-finished compaction;