title: Backfill & Resync Playbook updated: 2026-03-26#
Twisted has four recovery tools. Choose based on what broke.
| Situation | Recovery path |
|---|---|
| Search results wrong but documents exist | twister reindex |
| Documents missing because Tap never delivered them | twister backfill |
| Documents exist but derived metadata is empty or stale | twister enrich |
| Full database loss or migration to a fresh PostgreSQL instance | migrate, backfill, enrich, reindex |
Commands#
twister migrate#
Applies the embedded schema migrations for the configured database.
twister indexer#
Runs the Tap consumer continuously. Persists its cursor in sync_state.
twister backfill#
Default source is lightrail. Use graph mode only for targeted fallback.
twister backfill --dry-run
twister backfill
twister backfill --source graph --seeds seeds.txt --max-hops 2
Safe to rerun. Discovery is deduplicated and Tap registration is treated as idempotent.
twister reindex#
Re-upserts stored documents so PostgreSQL recomputes search state from the
canonical documents rows.
twister reindex
twister reindex --collection sh.tangled.repo
twister reindex --did did:plc:abc123
twister reindex --dry-run
twister enrich#
Fills missing author_handle, repo_name, and web_url.
twister enrich
twister enrich --collection sh.tangled.repo.issue
twister enrich --did did:plc:abc123
twister enrich --dry-run
Scenario Playbooks#
Search drift#
If search results look stale but the document rows are present:
twister reindex --dry-run
twister reindex
Missing documents#
If a record is fetchable through the API but not searchable:
- make sure Tap is tracking the DID
- run targeted
backfillif needed - let
indexerdrain - re-run
enrichif metadata is still incomplete
Metadata gaps#
If author_handle or repo_name is empty:
twister enrich --dry-run
twister enrich
twister reindex
Full PostgreSQL rebuild#
Use this after restoring to a fresh database or moving to a new PostgreSQL instance.
- run
twister migrate - start
indexer - run
twister backfill - run
twister enrich - run
twister reindex - verify
/readyz,/health, and smoke checks
This is the default migration path from the old Turso-backed deployment too.
Tap cursor reset#
If the Tap cursor is ahead of the retained event window:
DELETE FROM sync_state WHERE consumer_name = 'indexer-tap-v1';
Then restart the indexer.
Status Checks#
With admin routes enabled:
curl -H "Authorization: Bearer $ADMIN_AUTH_TOKEN" \
http://localhost:8080/admin/status
Watch:
tap.cursorjetstream.cursordocumentsread_through.pendingread_through.processingread_through.failedread_through.dead_letter