JSON key-value over WebSocket
HorizonDB¶
Memory above, PostgreSQL below.
The key-value server behind iHorizon. Reads and writes at memory speed, batched into PostgreSQL behind the scenes, spoken over one WebSocket from a zero-dependency Bun client.
What it is¶
HorizonDB is a JSON-oriented key-value database server written in Rust
(edition 2024, async Tokio, unsafe forbidden). Clients talk to it over a
WebSocket with small JSON packets. Every read and every write is served from an
in-memory store; a background task writes the changed keys to PostgreSQL in
batches every flush_interval_secs (300 s by default), and a graceful shutdown
flushes everything first.
import { HorizonDB } from "horizondb";
const db = await HorizonDB.connect({ url: "ws://127.0.0.1:8080", username: "admin", password: "secret" });
await db.set("guild.42.settings.prefix", "!"); // dot keys merge into one document
await db.add("guild.42.stats.messages"); // → 1, the new value, no extra read
await db.push("guild.42.mods", "1234", { returnValue: true }); // → ["1234"]
await db.cache("cooldown.42.daily", true, 86_400_000); // gone in 24 h
console.log(await db.get("guild.42"));
// { settings: { prefix: "!" }, stats: { messages: 1 }, mods: ["1234"] }
Why¶
A Discord bot hammers a handful of large JSON documents (a guild, a member, a
counter) from many shards at once. A SQL round trip on every add or push
makes that slow; a pure in-memory store forgets everything on restart.
HorizonDB sits on the line between the two: the working set lives in memory,
the durable copy lives in plain PostgreSQL tables you already know how to
query, back up and restore.
Key features¶
-
Memory above
Every operation is answered from memory. A
pushinto a multi-megabyte document edits it in place under a per-key lock: documents are never deep-copied on the hot path. -
The horizon
Dirty keys are batch-flushed to PostgreSQL (
UNNESTupserts,ANYdeletes) everyflush_interval_secs, and on SIGINT / SIGTERM. -
PostgreSQL below
One plain table per namespace:
id VARCHAR(255) PRIMARY KEY, value TEXT. Keys load lazily on first access, single-flighted. -
Bounded memory
Idle documents are packed to compact JSON text (about 10x smaller), and clean idle documents leave memory entirely. RAM follows the working set, not everything read since startup.
-
No lost updates
Read-modify-write operations are serialized per key. Sixteen workers on one counter end at the exact sum, checked on every benchmark run.
-
Nested keys
user.profile.nameaddresses inside one document per base key, up to 32 levels deep. Reads copy only the sub-tree you asked for. -
Answers, not acks
add/subreturn the new number,deletewhether the key existed,push/pullthe resulting array on request. -
Hardened by default
argon2id or constant-time auth, validated table names, bounded messages, per-connection backpressure, request deadlines, idle-link pings.
Where to next¶
-
Run a server and a PostgreSQL container with one script, then send your first request.
-
Every
[server],[database]and[auth]option, its default and its validation rule. -
Write-behind, dirty generations, flush batching, cold loads and the three memory tiers.
-
Packet shapes, every operation with request and response, errors and limits.
-
The Bun client: options, methods, error classes, reconnection.
-
systemd and pm2, graceful shutdown, the stats lines, memory tuning, backups.
-
Method, reference numbers, memory measurements and the interactive results.
-
Restarts, cold starts, memory usage, closed connections and other surprises.
Project status
HorizonDB is part of the iHorizon project. It is a work in progress: the wire protocol may still change, so upgrade the server and the SDK together.