repo writes
repo writes
hat a PDS actually does when a record is written. the repo is a signed
content-addressed merkle search tree; every mutation produces a new signed
commit (did, version: 3, data MST root CID, rev TID, nullable prev,
sig), and paths are exactly <collection>/<rkey> — normalized NSID, valid
rkey, two segments, no leading slash. evidence throughout from
zds, a zig PDS.
serialize per repo, then profile the phases
writes take a per-DID lane lock (sharded), then a DB transaction wrapping
block/record/commit/event inserts — one writer per repo, many repos in
parallel. zds profiles the write path by phase (validation / lock_wait /
load_repo / stage_records / build_commit / sql / event_publish), which is what
makes "where did the write time go" answerable instead of vibes
(store.zig:1864,2029).
load the MST lazily
a repo's MST can be big; a write touches a narrow path through it. zds loads
the tree lazily against the block store (Mst.loadLazy over a repo_blocks
reader) instead of materializing it — the write only reads the blocks its path
crosses (store.zig:1896).
revs must increase even when the clock doesn't
rev is a TID derived from wall-clock microseconds, and the spec requires it
to be strictly increasing per repo. clocks stall and step backwards, so zds
guards: if the clock hasn't advanced past the current rev, force
current_timestamp + 1 (revForSeq, store.zig:2068). without this, two
writes in the same microsecond (or after NTP adjustment) produce a
non-advancing rev and downstream since semantics break.
a block keeps the rev of its first appearance
repo_blocks rows are stamped with the writing commit's rev, preserved on
conflict (COALESCE(repo_blocks.repo_rev, excluded.repo_rev),
store.zig:1941). a block re-referenced by a later commit keeps its original
rev — which is exactly what makes incremental export
(getRepo?since=<rev>, see car-export) return the blocks a
consumer is missing rather than everything the latest commit touches.
swap preconditions live under the write lock
swapCommit / swapRecord (compare-and-swap on the repo head or a record's
CID) are checked inside the same lock and transaction as the write
(store.zig:4544) — checking them before taking the lock is a TOCTOU bug.
the write is more than the record
one write, in order: repo-path validation → schema validation where available (see lexicons for what "validation" really means) → MST mutation → signed commit → block storage → firehose event (precomputed frame, see serving-event-streams) → blob reference bookkeeping (blobs) → crawler notification. skipping any step shows up later as a sync or verification failure someone else debugs.
service auth rides on the same keys
getServiceAuth mints a short-lived JWT signed by the account's key —
issuer: account DID, audience: service DID, optional lxm binding the token
to one method. the receiver verifies against the account's DID document. this
is the primitive under PDS-to-PDS and PDS-to-service calls.
sources
- zds —
src/atproto/store.zig,docs/architecture.md - repository spec
- reference implementations worth reading beside the spec: the official PDS, tranquil-pds, pegasus
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.