celld makes two guarantees: one node owns a cell at a time, and a write is durable before celld acknowledges it. This page explains the mechanism behind each guarantee so you can verify it against the source code and evaluate candidate object stores. The short version: ownership records use conditional writes; the replication stream uses plain writes because the epoch in the key is the fence; and the acknowledgement path re-reads the ownership record, so a stale node cannot make a promise that the fleet does not keep.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/denoland/celld/llms.txt
Use this file to discover all available pages before exploring further.
The ownership record
Each cell has exactly one ownership record in the bucket. The record names the owning node’s session identifier and carries a fencing epoch. A node acquires a cell with a conditional write:- If no record exists: a conditional create that fails if the object is already present.
- If a record exists: a compare-and-swap on the previous record that fails if the record changed since the read.
Epoch prefix in replication
The replicator copies SQLite data for each cell to the bucket under an epoch-scoped prefix:PUTs — intentionally so. The fence is the epoch in the key, not a condition on each request. A node that lost ownership can continue writing, but its writes land in a superseded prefix. When another node restores the cell it selects the current lineage, so the stale node’s writes cannot corrupt the new owner’s data, and the data path pays no conditional-write cost per segment.
The prefix protects the data. It does not, alone, protect the promise to the client. The next two mechanisms close that gap.
The acknowledgement rule (RPO=0)
An output gate holds the HTTP response for each write until the replicator proves the write is present in the bucket. After that proof, celld performs one additional step before acknowledging:- Read the ownership record.
- Acknowledge the write only if the record still names this node at this epoch.
CELLD_OUTPUT_GATE=0 to remove this replication wait and acknowledge without proof. This trades the RPO=0 guarantee for lower write latency at the cost of possible data loss on node failure.
The epoch seal
A restore selects the newest epoch prefix that contains data. Because segment writes are unconditional, a fenced node can append to that prefix after the takeover. Without a further rule, a later restore could read that appended tail — writes that no client ever received an acknowledgement for. The seal closes this hole. The first activation that restores from an epoch writes a seal object at:Self-fencing
A node that cannot reach the bucket cannot renew its lease and cannot replicate. Such a node must not own cells, so it fences itself: it stops issuing writes and releases its residency. Other nodes can then acquire those cells through the normal ownership-record mechanism. The failure of a node is a normal operational input, not a recovery procedure. The fleet heals automatically through the lease and ownership mechanisms rather than through a separate consensus or failure-detection service.Required bucket properties
celld requires three properties from the object store:Conditional create
A write that creates an object must fail if the object already exists. This is the mechanism that prevents two nodes from acquiring the same cell simultaneously.
Conditional overwrite
A write that updates an object must fail if the object changed after the read that preceded the write. This is the compare-and-swap used when transferring ownership.
S3-compatible stores
On S3-compatible buckets celld uses two standard conditional headers:| Header | Semantics |
|---|---|
If-None-Match: * | Conditional create — fail if any object already exists at this key |
If-Match: <etag> | Conditional overwrite — fail if the current etag does not match |
Google Cloud Storage
Ags:// bucket selects the Cloud Storage XML API. celld uses the x-goog-if-generation-match precondition with OAuth credentials. The condition compares the object generation rather than an etag. celld does not send the S3 request dialect to Cloud Storage because Cloud Storage does not apply If-Match to a PUT.
Stores that do not qualify
The following stores do not implement the required conditional writes and therefore are not correct for celld:- MinIO (community edition)
- Backblaze B2
- Hetzner Object Storage
- DigitalOcean Spaces