Skip to content

walrusd

Embedded SQLite runtime

walrusd

Write-Ahead Log in object storage

A horizontally scalable, multi-tenant SQLite runtime with one logical database per user.

  • No writer fleet
  • No routing layer
  • No sticky sessions

How it works

Stateless compute. Object storage is the database.

walrusd embeds the database runtime directly in each API process. Redis/Valkey serializes writes per database, while Litestream's VFS turns object storage into the durable SQLite state.

  1. 01

    Your code

    Go services import the core directly; Node.js and Bun use the same core through @walrusd/db.

  2. 02

    Embedded runtime

    walrusd runs inside a stateless API process, so API instances remain disposable compute.

  3. 03

    Redis / Valkey lease

    A per-database lease uses conditional CAS to serialize writes across API instances.

  4. 04

    SQLite + Litestream VFS

    Writes run through Litestream write mode; reads use the remote replica without normal full local hydration.

  5. 05

    LTX in object storage

    The durable state is an ordered LTX chain in an S3-compatible store; conditional writes are not required.

Write lifecycle

The write path

Every mutation follows the same five steps. Success is acknowledged only after the LTX data is in object storage.

  1. 01

    Acquire the lease

    Before any SQLite write begins, the runtime conditionally acquires the database's Redis/Valkey lease.

  2. 02

    Open a write-mode session

    The runtime opens or refreshes the remote Litestream VFS state and enables write mode.

  3. 03

    Run the SQL transaction

    Your callback or statement batch runs and commits inside one SQLite transaction.

  4. 04

    Flush the LTX file

    Disabling write mode is the mandatory flush barrier. walrusd waits for the synchronous remote flush to succeed.

  5. 05

    Release the lease

    Only after the confirmed flush does the runtime conditionally release the lease and return the remote TXID.

Durability boundary

The acknowledgement means the data is in object storage.

Durability is confirmed by flush, never by elapsed time. Any API instance can serve any request because mutual exclusion comes from the lease, not from request routing.

Read behavior

Reads see only remote-committed state.

Reads acquire no lease. They observe only state that Litestream has committed remotely through the VFS, with no local hydration.

Runtime properties

Small surface, explicit guarantees.

The runtime keeps coordination narrow and leaves durable state in object storage.

Placement

Any instance

Any API instance can serve any request for any user, with no writer fleet, routing layer, or sticky sessions.

Writes

Flush-backed acknowledgement

Writes are acknowledged only after the transaction is flushed to object storage. Durability is confirmed by flush, never by elapsed time.

Reads

Remote committed state

Reads see only remote-committed state through Litestream's VFS, with no local hydration.

Bindings

One core, three runtimes

A Go core (CGO) is exposed to Node.js and Bun through one Node-API addon over a narrow C ABI.

Retries

Idempotent by key

Retrying with the same idempotency key is deduplicated; after a timeout or crash, the recorded result is returned instead of re-running the mutation.

Storage

S3-compatible by default

Any S3-compatible store can hold the replica, including Wasabi, Backblaze B2, and Sliplane; conditional writes are not required.

Quickstart

Write, flush, read.

This TypeScript example is the repository quickstart for the Node.js and Bun binding.

quickstart.ts
import { WalrusdDatabase } from "./bindings/node/src/index";
const db = new WalrusdDatabase({ owner: "my-api-instance" });
// The descriptor is issued by your control plane - never by end users.
const d = {
database_id: "users/user_1", // your ID; objects land at <root_prefix>/users/user_1/
storage: { provider: "file", file_root: "/tmp/walrusd-quickstart" },
credentials: {},
};
// One write = one batch = one transaction = one lease = one confirmed flush.
await db.write({
database: d,
idempotencyKey: "schema",
statements: [{ sql: "CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT)" }],
});
const { txid } = await db.write({
database: d,
idempotencyKey: "set-greeting", // retrying this key is safe: deduplicated
statements: [{ sql: "INSERT OR REPLACE INTO kv (k, v) VALUES ('greeting', 'hello from walrusd')" }],
});
console.log("durable at txid", txid);
// Reads see only flushed, remote-committed state.
const { rows } = await db.read({ database: d, sql: "SELECT v FROM kv WHERE k = 'greeting'" });
console.log("read:", rows[0].v);
await db.close();

Documentation

Read the design. Then embed the runtime.

The specification explains the invariants; the usage guide covers the Go core, the Node.js and Bun binding, the C ABI, and the error model.

Read the docs