atproto pds in zig pds.zat.dev
pds atproto
Something went wrong. Try again.
Markdown
getRepo notes #
Context: on 2026-06-29, operators called out com.atproto.sync.getRepo as an expensive route for PDS hosts. The main concerns were compression cost, large repo export reads, avoiding disclosure of stale/deleted blocks, and isolating backup/backfill traffic from user-facing work.
ZDS is directly relevant because it serves com.atproto.sync.getRepo from src/atproto/sync.zig.
Current shape:
- ZDS serves raw CAR bytes and does not appear to add application-layer gzip, so it avoids the Node compression issue described in the operator thread.
- As of the 2026-06-29 getRepo pass, full repo export starts at the latest commit block and uses
zat.mst.collectReachableBlocksto include only current MST and record blocks reachable from the commit'sdataCID. - Incremental export with
sincefilters byrepo_rev. - Repo writes already use lazy MST loading backed by
repo_blocks, so ZDS is closer to a Hubble-style block/CAR-serving shape than an eager full-repo rebuild path. - File-backed exports use a dedicated read-only SQLite connection and WAL transaction. The root and reachable blocks come from one coherent snapshot; CAR encoding happens after that snapshot closes. Account, OAuth, and write operations continue on the primary connection instead of waiting behind repo traversal and encoding.
subscribeReposhas a connection cap. FullgetReponow has its own lightweight route-local concurrency cap so backup/backfill exports cannot consume unbounded handler slots. Operators can tune it withZDS_MAX_CONCURRENT_REPO_EXPORTS; the default is4, matching Tranquil's current full-export default.
Open questions:
- Should
HEAD com.atproto.sync.getRepostay semantically cheap, or should it compute/export-sized metadata? Today it is treated as a probe and still shares the same handler path. - Should
getRepobackpressure eventually become per-account/IP rate limiting in addition to process-local concurrency? - Should the benchmark suite keep a historical range-scan comparison around, now that production full export is reachability-based?
Done in this pass:
- Adopted
zat0.3.9 forcollectReachableBlocks. - Added a regression test for update/delete followed by full
getRepo; stale record CIDs are excluded from full export. - Changed full export from whole-DID
repo_blocksscan to current-root reachability. - Added lightweight route backpressure for full
getRepo, configurable withZDS_MAX_CONCURRENT_REPO_EXPORTS. - Ran
just bench repo-size; reachable full export measured about 1.4 ms for 100 records, 16.8 ms for 1,000 records, and 95.7 ms for 5,000 records on the local synthetic benchmark. - Added
just bench get-repoas the focused storage-pressure lane for this route. It measures full export construction, one-commitsinceexport construction, and concurrent full export latency against one seeded repo. - Added account-read and write latency to the focused benchmark after production traffic demonstrated that export throughput alone missed process-wide database contention.
- Moved file-backed full and incremental exports to independent read transactions. This follows the isolation boundary used by the reference PDS's actor-store reader and Tranquil's separate block store while retaining ZDS's SQLite/WAL architecture.
Validation on 2026-07-30:
- In the local 10k-record, four-export comparison, the previous implementation delayed an account lookup by 325 ms and a write by 336 ms. The isolated implementation kept their maxima to 0.08 ms and 1.92 ms respectively.
- On a disposable Fly
shared-cpu-1xmachine with 1 GB RAM, eight concurrent exports of a 10k-record repo completed without failure. Account lookups remained below 20 ms and writes below 44 ms even while the single CPU was saturated.
Suggested next pass:
- Add a larger deliberate
just bench get-repo 100000 4 1run when we want stress numbers comparable to large public repos. - Add a cross-PDS HTTP/export comparison in
atproto-benchonce the ZDS-local storage seam is stable.