Clusters
Reed-Solomon erasure sets one shard per node, failure domains a layout respects, write quorums, background healing, layouts changed while serving, and members admitted over mutual TLS.
Setting TESSARIDB_S3_ERASURE makes a node a cluster member. Without it the node
stores whole objects on its own drive.
Erasure coding
A cluster writes objects with a Reed-Solomon code k+m (k, m ≥ 1, k + m ≤ 16):
each object becomes k data shards and m parity shards, one per node of an erasure
set. Objects are placed on the sets by TESSARIDB_S3_CLUSTER_SECRET, which is the
same on every node, so every node computes the same placement without asking anyone.
- A write is acknowledged on its quorum: k shards durable, or k + 1 when k = m, so that two halves of a split cluster cannot both accept a write.
- A read takes any k shards that verify. A missing or damaged shard is rebuilt from parity for that read and the object is listed for healing.
Failure domains
TESSARIDB_S3_ZONE names the failure domain a node is in: a zone, a rack, a room.
A layout never puts more of one object's shards in one zone than its parity can
lose, so losing a whole zone leaves every object readable. The first layout takes
the registered nodes in id order, passing over a node whose zone already holds m
shards.
| code | zones | survives |
|---|---|---|
| 4 + 2 | 3 zones, 2 nodes each | any one zone, or any two nodes |
| 8 + 4 | 4 zones, 3 nodes each | any one zone and one more node |
| 6 + 3 | 3 zones, 3 nodes each | any one zone |
Healing
Every member runs a healing pass (TESSARIDB_S3_HEAL_INTERVAL_SECS, a minute by
default) that rewrites the missing or damaged shards of objects listed for healing,
one node at a time per object, under a claim in TessariDB. Shard bytes no object can
reach any more are swept.
Changing the layout
A layout holds one or more erasure sets. Root changes it in the console: a layout is staged over the current version, judged against the registered nodes and their zones, and applied as the next version. Either step is refused if the layout or the staging changed meanwhile, needs a fresh sign-in and a reason, and is recorded.
Once a layout is applied, each member moves objects written under an older layout
onto it: it copies and verifies the shards whose node changes before switching the
object, and removes the old shards once the reclaim grace has passed, at most
TESSARIDB_S3_MOVE_BYTES_PER_SEC (32 MiB a second by default; 0 pauses moving).
The console's cluster view counts the objects still to move.
Membership
Members are admitted, not discovered:
- A cluster begins from its founding members (
TESSARIDB_S3_FOUNDING_MEMBERS). - Any other node joins only when root approves it in the console, or with a
one-time join token (
TESSARIDB_S3_JOIN_TOKEN) issued in the console, which binds the node's certificate. A node that is not an admitted member does not start. - Root removes nodes for good; a removed node is never admitted again, and the removal reaches every node within 5 seconds.
The internal shard surface speaks mutual TLS only (TLS 1.2 and 1.3). Each node's
certificate is issued for <node id>.peer.tessari-s3; a node whose certificate names
another node does not start, and a call reaches another node only when its
certificate is issued for the node dialled and that node is an admitted member.
Regions
A region today is a cluster of its own, with its own metadata store; the server's
region (TESSARIDB_S3_REGION) is the one requests must be signed for. Replicating
buckets between regions is not in this build.
Measured
The first numbers, from six processes as one 4 + 2 cluster on a single laptop with
all six data directories on one SSD, metadata in a TessariDB 0.34.0-beta node, release
build, warm cache, median of three runs, every GET returning exactly the bytes PUT:
| 4 KiB | 1 MiB | 64 MiB | |
|---|---|---|---|
| PUT, one client | 81 ms | 83 ms | 525 ms |
| GET, one client | 2.3 ms | 2.7 ms | 59 ms |
| HEAD, most per second | ≈ 16,000 | ≈ 15,500 | ≈ 8,600 |
On that machine writes are limited by the drive: every PUT syncs its six shard files, and on macOS each sync flushes the whole drive's cache. With one node of the six stopped every request still succeeded; reads took about twice as long, because a missing shard is rebuilt from parity on every read.