Rindle is one binary, rindle. On a single machine it is one server process and one SQLite file.
To scale, it splits into a write master and any number of read followers, the way Postgres
scales reads with replicas, except that followers also run the live queries.
This is the same architecture Cloud Rindle runs.
Single server
The simplest deployment, and what ./rindle up runs locally. Write a config:
{
"db": "/var/lib/rindle/data.db",
"standalone": true,
"httpPort": 7600,
"bindHost": "0.0.0.0",
"authToken": "…",
"sqlAuthToken": "…"
}And start it:
rindle serve --config rindle.jsonOne process serves reads, writes, migrations, and live queries on one port. The data is a single SQLite file in wal2 mode, with one writer and a pool of query workers, one per CPU by default.
A single server has no built-in replication. Back it up with volume or filesystem snapshots. To move to the replicated setup later, export and import.
Replicated
| Process | Command | Does |
|---|---|---|
| Write master | rindle replicator |
Commits every write and keeps the journal of changes |
| Follower | rindle serve |
Holds a full replica, serves reads, and runs live queries |
| Edge | rindle edge |
The one public endpoint. Routes writes to the master and sessions to followers |
| Backup producer | rindle backup producer |
Turns the archived journal into periodic snapshots |
Your app talks only to the edge, with the same RINDLE_URL and client code as a single server.
Writes
All writes go to the master. Its storage is SQLite’s HCTree backend, which runs many BEGIN CONCURRENT transactions in parallel with snapshot isolation, checking for conflicts
at commit. Each commit gets a sequence number, and its journal entry is written in the same
transaction as the data, so the journal can never disagree with the database.
The master streams the journal to every follower and ships it to object storage. A fencing token stops a master that has been replaced from accepting writes.
Reads and live queries
Each follower holds a full copy of the database. It tails the master’s journal, applies each commit, and pushes the change through its live queries, so subscribers get only the rows that changed. A query’s state is shared by every subscriber to it, and queries are spread across a pool of worker threads.
A follower that restarts resumes from the last commit it applied. A new follower, or one that fell too far behind, restores the latest snapshot from object storage and catches up from there. It reports not-ready, and takes no traffic, until it has caught up.
Followers are the unit of scale. Add one to add capacity for reads and live queries; the master and the other followers don’t change.
Sessions
A session lives on one follower. When a client connects, the edge assigns it a follower, balancing load across them, and signs a short-lived ticket so that every later request for that session reaches the same follower.
Durability
The master ships its journal to object storage (S3, R2, MinIO, or a filesystem) continuously. The backup producer turns it into daily snapshots and keeps the last seven. Together they give point-in-time restore to any commit in that window:
rindle backup ls
rindle backup restore-master --source main --out master.db --at 1791500000000
rindle backup restore-follower --source main --out follower.db --cid 4812Every snapshot the producer writes is itself a full restore of the archive, so restores are tested continuously. See Status for the one durability gap that remains.
Running it
Master:
{
"db": "/var/lib/rindle/master.db",
"name": "main",
"httpPort": 7611,
"wsPort": 7610,
"bindHost": "0.0.0.0",
"authToken": "…",
"sqlAuthToken": "…",
"backup": { "type": "s3", "generation": "prod-1" }
}rindle replicator --config master.jsonEach follower:
{
"db": "/var/lib/rindle/follower.db",
"httpPort": 7600,
"bindHost": "0.0.0.0",
"authToken": "…",
"sources": [{
"kind": "replicator",
"name": "main",
"url": "ws://master.internal:7610/subscribe",
"backup": { "type": "s3" }
}]
}rindle serve --config follower.jsonThe edge lists the master and the followers:
{
"app": "main",
"port": 7650,
"bindHost": "0.0.0.0",
"key": "…",
"masterUrl": "http://master.internal:7611",
"followers": [
{ "mid": "f1", "wsUrl": "ws://f1.internal:7600", "controlUrl": "http://f1.internal:7600" },
{ "mid": "f2", "wsUrl": "ws://f2.internal:7600", "controlUrl": "http://f2.internal:7600" }
]
}rindle edge --config edge.jsonObject-storage credentials come from the standard AWS_* environment variables. Every server
signals readiness to systemd (Type=notify).
Operations
- Health. Followers serve
/readyz,/version, and/stats. The master serves Prometheus/metricsonRINDLE_METRICS_ADDR. - Auth. A server bound to anything but loopback refuses to start without tokens.
authTokencovers the internal routes;sqlAuthTokenis the one your app uses. - TLS. Terminate it at your load balancer or proxy, in front of the edge.
- Migrations.
rindle migrate apply --url <edge>applies them on the master. Followers receive the schema change through the journal. - Upgrades. Upgrade the master first, then the followers one at a time.
Limits
- One write master. Writes scale up, across concurrent transactions on one machine, not out.
- Manual failover. To replace a lost master, restore it from object storage with
restore-master, which promotes the copy to a new master.