a love letter to tangled (android, iOS, and a search API)
1---
2title: Backfill & Resync Playbook
3updated: 2026-03-26
4---
5
6Twisted has four recovery tools. Choose based on what broke.
7
8| Situation | Recovery path |
9| --- | --- |
10| Search results wrong but documents exist | `twister reindex` |
11| Documents missing because Tap never delivered them | `twister backfill` |
12| Documents exist but derived metadata is empty or stale | `twister enrich` |
13| Full database loss or migration to a fresh PostgreSQL instance | migrate, backfill, enrich, reindex |
14
15## Commands
16
17### `twister migrate`
18
19Applies the embedded schema migrations for the configured database.
20
21### `twister indexer`
22
23Runs the Tap consumer continuously. Persists its cursor in `sync_state`.
24
25### `twister backfill`
26
27Default source is `lightrail`. Use graph mode only for targeted fallback.
28
29```sh
30twister backfill --dry-run
31twister backfill
32twister backfill --source graph --seeds seeds.txt --max-hops 2
33```
34
35Safe to rerun. Discovery is deduplicated and Tap registration is treated as
36idempotent.
37
38### `twister reindex`
39
40Re-upserts stored documents so PostgreSQL recomputes search state from the
41canonical `documents` rows.
42
43```sh
44twister reindex
45twister reindex --collection sh.tangled.repo
46twister reindex --did did:plc:abc123
47twister reindex --dry-run
48```
49
50### `twister enrich`
51
52Fills missing `author_handle`, `repo_name`, and `web_url`.
53
54```sh
55twister enrich
56twister enrich --collection sh.tangled.repo.issue
57twister enrich --did did:plc:abc123
58twister enrich --dry-run
59```
60
61## Scenario Playbooks
62
63### Search drift
64
65If search results look stale but the document rows are present:
66
67```sh
68twister reindex --dry-run
69twister reindex
70```
71
72### Missing documents
73
74If a record is fetchable through the API but not searchable:
75
761. make sure Tap is tracking the DID
772. run targeted `backfill` if needed
783. let `indexer` drain
794. re-run `enrich` if metadata is still incomplete
80
81### Metadata gaps
82
83If `author_handle` or `repo_name` is empty:
84
85```sh
86twister enrich --dry-run
87twister enrich
88twister reindex
89```
90
91### Full PostgreSQL rebuild
92
93Use this after restoring to a fresh database or moving to a new PostgreSQL
94instance.
95
961. run `twister migrate`
972. start `indexer`
983. run `twister backfill`
994. run `twister enrich`
1005. run `twister reindex`
1016. verify `/readyz`, `/health`, and smoke checks
102
103This is the default migration path from the old Turso-backed deployment too.
104
105### Tap cursor reset
106
107If the Tap cursor is ahead of the retained event window:
108
109```sql
110DELETE FROM sync_state WHERE consumer_name = 'indexer-tap-v1';
111```
112
113Then restart the `indexer`.
114
115## Status Checks
116
117With admin routes enabled:
118
119```sh
120curl -H "Authorization: Bearer $ADMIN_AUTH_TOKEN" \
121 http://localhost:8080/admin/status
122```
123
124Watch:
125
126- `tap.cursor`
127- `jetstream.cursor`
128- `documents`
129- `read_through.pending`
130- `read_through.processing`
131- `read_through.failed`
132- `read_through.dead_letter`