atproto pds in zig pds.zat.dev
pds atproto
zds docs getrepo-notes.md
4.1 kB

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.collectReachableBlocks to include only current MST and record blocks reachable from the commit's data CID.
  • Incremental export with since filters by repo_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.
  • subscribeRepos has a connection cap. Full getRepo now has its own lightweight route-local concurrency cap so backup/backfill exports cannot consume unbounded handler slots. Operators can tune it with ZDS_MAX_CONCURRENT_REPO_EXPORTS; the default is 4, matching Tranquil's current full-export default.

Open questions:

  • Should HEAD com.atproto.sync.getRepo stay semantically cheap, or should it compute/export-sized metadata? Today it is treated as a probe and still shares the same handler path.
  • Should getRepo backpressure 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:

  1. Adopted zat 0.3.9 for collectReachableBlocks.
  2. Added a regression test for update/delete followed by full getRepo; stale record CIDs are excluded from full export.
  3. Changed full export from whole-DID repo_blocks scan to current-root reachability.
  4. Added lightweight route backpressure for full getRepo, configurable with ZDS_MAX_CONCURRENT_REPO_EXPORTS.
  5. 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.
  6. Added just bench get-repo as the focused storage-pressure lane for this route. It measures full export construction, one-commit since export construction, and concurrent full export latency against one seeded repo.
  7. Added account-read and write latency to the focused benchmark after production traffic demonstrated that export throughput alone missed process-wide database contention.
  8. 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-1x machine 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:

  1. Add a larger deliberate just bench get-repo 100000 4 1 run when we want stress numbers comparable to large public repos.
  2. Add a cross-PDS HTTP/export comparison in atproto-bench once the ZDS-local storage seam is stable.