chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
Backfill is one listRecords call per collection per account, unverified
Closedchadtmiller.com mentioned this in A backfill never puts back what the feed has since changed or deleted
chadtmiller.com mentioned this in A backfill reads each account whole from its PDS, checked, and only those that changed
chadtmiller.com mentioned this in A backfill asks a directory which accounts to read, and reads them from a mirror as STAR-lite
Done, in A backfill reads each account whole from its PDS, checked, and only those that changed and A backfill asks a directory which accounts to read, and reads them from a mirror as STAR-lite:
- Who to read:
--directory(lightrail in prod) is askedlistReposByCollectionfirst, then the relay. - One archive per account, from a mirror:
--mirror(hubble in prod) serves STAR-lite or a CAR, and the account's PDS is the fallback. - Verified: the MST root is rebuilt while the archive streams, and the commit's signature is checked against the DID document's key.
- Retries: each repository has its own
retry_atand backs off. - Newer rev wins: records keep the rev they came at.
- No full sweep: the 10-minute sweep compares
getLatestCommitand reads only the repositories that changed.
The STAR reader was not shared with space_sync.rs. That can be its own change if grain needs it.
What happened
knotless-serve's backfill (crates/knotless-serve/src/lib.rs,accounts,pages,backfill,sweep) works like this:listReposByCollectiononce for each of the 14 or more indexed collections. If any one of those calls fails, it falls back tolistRepos, which on a relay lists every account.com.atproto.repo.listRecordsthrough every indexed collection, one account at a time, against that account's own PDS.sweepruns the whole thing again every 10 minutes to recover what Jetstream dropped.That costs about 15 requests per account per pass, spread over every PDS on the network, and grows with the number of accounts. For grain, which is moving onto a stack modelled on knotless, this would be the slow part.
opensocial already solves this in
packages/group-sites/src/repoArchive.tsandnetworkIndex.ts:listReposByCollectionfromlightrail.microcosm.bluefirst, then the relay.getRepofromhubble.microcosm.bluewithAccept: application/x.microcosm.star-liteand zstd. STAR-lite is every key and record in key order with the commit in its header and no tree nodes. If the mirror is unreachable, a CAR from the account's PDS, one at a time, capped in size.retry_at, and a few workers. Retries back off at 1 minute per try, up to an hour. A 4xx gives up on that repo. The mirror's 429 pauses every worker for itsretry-after. A watchdog drops a download that goes quiet for 30 s or runs past 10 minutes.For knotless, step 5 could also replace the 10-minute full sweep: compare each repo's
com.atproto.sync.getLatestCommitrev with the last one indexed, and queue only the repos that differ.This would also settle two items in "knotless-serve: quirks in backfill, labels and caches": backfill aborting on the first failed account, and the
listReposfallback.Reusing the STAR reader in
space_sync.rscrates/knotless/src/space_sync.rsalready reads STAR for spaces, but a different version: magic*l\x01\x00, a header that is one frame holding the commit with its MAC, and an LtHash set hash checked against the commit. Hubble's STAR-lite is version 0: magic*l\x00, a 36-byte root CID, then a varint-length signed commit. The records after the header are framed the same way in both (varint key, varint value, in key order).Reusable as is:
Frames, the key-order check, splitting a path, DRISL decoding andcid(). New:RootBuilder, about 80 lines)&[u8], so a large repo is never held in memoryFactoring that out of
space_sync.rsinto a STAR module both versions use would let grain take the same code.Things to work out