claude exhaust

3.0 kB · Created 2mo ago
Markdown

getting atproto records at every scale — measured #

all numbers measured 2026-08-17 against real infrastructure (stream.waow.tech = jetstream v2 instance w/ full-network archive; lightrail/slingshot indexes; real PDSes). same collections, same day.

method statusphere (1,654 repos) lexidraw.scene (52 repos) what you get
jetstream v2 archive slice (collections + kinds=commit in the plan) 17,537 rows · 4m12s · 497 MB 615 rows · 15s · 27 MB full event history: creates, updates, deletes, plus rows from repos no longer reachable
lightrail → listRecords per repo 13,145 rows · 4m53s · 3.6 MB · 71 repos failed — current state only; misses unreachable repos + all deletion history
lightrail → getRepo CAR walk (not run: ~1,654 full repos) 49/52 repos · 26s · 622 MB exact current state, but you download every creator's entire repo
single-repo CAR (PDS direct) n/a — one repo only n/a unbeatable for one DID: the repo is the optimal layout of itself

two sharp edges worth knowing #

  1. kinds:["commit"] must be in the plan request, not just client-side filtering. the planner otherwise admits every marker-bearing block: for statusphere the plan is 140,705 blocks (~24 GB) without it vs 1,684 blocks (~500 MB) with it — an 83× difference. easy to miss: a client can filter markers locally while still fetching all their blocks.
  2. low observed MB/s during backfill usually isn't bandwidth. the same ~2 MB/s shows up from a datacenter box one hop from the server — it's the block-fetch pipeline (concurrency × per-request RTT). more parallel getBlock requests raise it; total bytes stay fixed.

the structural limit: archive blocks are ~4,096 events in arrival order, so a sparse collection scattered across the network costs (blocks touched) × (~300 KB), regardless of how few rows you want. 17K statusphere rows live in 1,686 blocks → ~500 MB. no client fixes that; only smaller blocks, per-collection layout, or sub-block reads server-side would.

why you'd pay it anyway: the archive slice returned +33% more rows than listRecords — deleted and updated records, plus everything from the 71 repos (4%!) that were unresolvable or unreachable at fetch time — from one source, with zero per-PDS failure handling, and a gapless cutover into the live tail when you're done. that's the local-first sync story: history + push, not just state + poll.

rule of thumb

  • one repo → fetch its CAR from the PDS. always.
  • many repos, current state, bytes matter → lightrail + listRecords (accept the reachability holes)
  • many repos, full history / deletes / unreachable repos / then go live → jetstream v2 slice with plan-level kinds=commit
  • freshly-bootstrapped archives flatter per-DID slices (bootstrap lays repos out contiguously); the numbers decay as live arrival-order accumulates — benchmark against an aged archive before concluding