aevumdb
AevumDB is an embedded time-travel key/value database for Go. It turns application state into an immutable sequence of atomic revisions, so a program can ask not only “what is the value now?” but also “what exactly was the state at revision 184, and what changed since then?”
It is designed for configuration engines, local-first applications, agent checkpoints, feature flags, workflow state, deployment metadata, audit trails, and any tool that needs SQLite-like portability but event-log semantics.
- One database is one ordinary file.
- The library has zero third-party dependencies.
- Every transaction is one durable revision.
- Reads can target the latest state or any historical revision.
- TTL values behave correctly in historical snapshots.
- The append-only log uses CRC32 checksums.
- An incomplete final write is truncated automatically on the next open.
- Corruption inside a completed record is reported and never silently ignored.
- Optimistic transactions detect write conflicts.
- Commit watches support replay plus live streaming.
- A complete CLI is included.
Why this is different
Заголовок раздела «Why this is different»Most embedded stores overwrite values, making history an application-level concern. Event stores preserve history but force applications to rebuild projections. AevumDB keeps the event-log durability model and maintains an immediately queryable temporal projection in memory. The same API supports current reads, historical reads, snapshots, diffs, transactions, TTL, and commit streaming.
AevumDB is intentionally a single-process embedded database. A lock file prevents two processes from opening the same database simultaneously. Inside one process, DB is safe for concurrent use.
Requirements
Заголовок раздела «Requirements»- Go 1.23 or newer
go test ./...go build -o aevum ./cmd/aevumOn Windows PowerShell:
go test ./...go build -o aevum.exe ./cmd/aevumThe release archive also contains ready-built linux-amd64 and windows-amd64 binaries in dist/.
CLI quick start
Заголовок раздела «CLI quick start»# Create a databaseaevum init state.aevum
# Store valuesaevum put state.aevum app/mode productionaevum put --ttl 15m state.aevum lease/worker worker-7
# Read the latest valueaevum get state.aevum app/mode
# Print only the raw valueaevum get --value-only state.aevum app/mode
# Read the value as it existed at revision 1aevum get --rev 1 state.aevum app/mode
# Inspect history and compare statesaevum history state.aevum app/modeaevum diff state.aevum 1 2
# Export a complete snapshotaevum export state.aevum
# Validate every record checksum by opening the fileaevum verify state.aevumCLI flags must appear before positional arguments. Passing - as the value reads bytes from standard input:
cat certificate.der | aevum put state.aevum tls/certificate -Atomic batch from JSON
Заголовок раздела «Atomic batch from JSON»aevum apply applies the entire array in one transaction. Either all changes receive the same revision or none are written.
[ {"op":"put", "key":"service/url", "value":"https://internal.example"}, {"op":"put", "key":"lease/owner", "value":"worker-7", "ttl":"30s"}, {"op":"delete", "key":"legacy/flag"}]aevum apply state.aevum changes.jsonBinary values can use "encoding":"base64".
Library quick start
Заголовок раздела «Library quick start»package main
import ( "fmt" "log" "time"
"aevumdb")
func main() { db, err := aevumdb.Open("state.aevum") if err != nil { log.Fatal(err) } defer db.Close()
rev1, err := db.Put("mode", []byte("safe")) if err != nil { log.Fatal(err) }
tx, err := db.Begin() if err != nil { log.Fatal(err) } if err := tx.Put("mode", []byte("fast")); err != nil { log.Fatal(err) } if err := tx.PutTTL("lease", []byte("worker-7"), 30*time.Second); err != nil { log.Fatal(err) } rev2, err := tx.Commit() if err != nil { log.Fatal(err) }
previous, _ := db.GetAtRevision("mode", rev1) changes, _ := db.Diff(rev1, rev2) fmt.Printf("previous=%s changed_keys=%d\n", previous.Value, len(changes))}Transactions
Заголовок раздела «Transactions»Begin captures a stable snapshot revision and time. Reads inside the transaction see staged writes first, then the captured snapshot. On commit, AevumDB checks each written key. If another transaction changed any of those keys after the snapshot revision, commit returns ErrConflict and writes nothing.
tx, err := db.Begin()if err != nil { return err}current, err := tx.Get("counter")if err != nil && !errors.Is(err, aevumdb.ErrNotFound) { return err}fmt.Printf("current value: %q\n", current.Value)
if err := tx.Put("a", []byte("1")); err != nil { return err}if err := tx.Put("b", []byte("2")); err != nil { return err}revision, err := tx.Commit()An empty transaction commits no record and returns its base revision.
Historical TTL semantics
Заголовок раздела «Historical TTL semantics»A TTL is stored as an absolute expiration time calculated from the commit timestamp.
Getevaluates expiration against the current clock.GetAtRevisionandSnapshotevaluate expiration against the target revision’s commit timestamp.
This means a value can correctly exist in an old snapshot even though it has expired today, while a later historical snapshot can show it as absent without requiring an explicit delete record.
Watches
Заголовок раздела «Watches»events, cancel, err := db.WatchFrom(lastProcessedRevision, 64)if err != nil { return err}defer cancel()
for event := range events { if event.Err != nil { // ErrWatchLagged means the consumer could not keep up. return event.Err } process(event.Commit)}WatchFrom first emits durable commits after the supplied revision and then continues with live commits. Writers never block on a slow subscriber; a lagging subscription is closed.
Main API
Заголовок раздела «Main API»| API | Purpose |
|---|---|
Open, OpenWithOptions | Open or create a database |
Put, PutTTL, PutUntil, Delete | Single-operation atomic commits |
Begin | Start an optimistic snapshot transaction |
Get, GetAtRevision | Read current or historical values |
History | Read every version and tombstone for a key |
Keys, KeysAtRevision | Enumerate visible keys |
Snapshot | Materialize a complete state at a revision |
Diff | Compare two revisions |
Commits | Read a range of immutable commits |
Watch, WatchFrom | Replay and stream commits |
Stats, Verify | Inspect and validate the database |
ForceUnlock | Remove a stale lock after a crashed process |
Durability and crash behavior
Заголовок раздела «Durability and crash behavior»The file starts with an eight-byte format marker. Each following record contains:
- a 32-bit payload length;
- a 32-bit CRC32 checksum;
- a JSON-encoded atomic commit payload.
With the default options, AevumDB calls fsync before reporting a commit as successful. If a process dies during the final record, the next Open truncates only that incomplete tail. A checksum mismatch in a fully sized record returns CorruptionError with the byte offset.
Options{NoSync: true} is available for disposable caches and tests where operating-system page-cache durability is sufficient.
Operational rules
Заголовок раздела «Operational rules»- Call
Closeduring normal shutdown. - Do not copy a database file while it is open; close it first or copy from a filesystem snapshot.
- Only one process may open a database file. Multiple goroutines may share one
DBinstance. aevum unlock DBremoves a stale lock file. Use it only after confirming that no process has the database open.- The append-only file is the audit history. Its size grows with committed changes by design.
- A single atomic commit payload is limited to 256 MiB.
Run the example
Заголовок раздела «Run the example»go run ./examples/basicLicense
Заголовок раздела «License»MIT
Исходный код · MIT