|
| 1 | +import type { Sql } from 'postgres' |
| 2 | +import { runUnknownTableRowProvenanceRepair } from './0005_repair_unknown_table_row_provenance' |
| 3 | +import type { ScriptMigration } from './types' |
| 4 | + |
| 5 | +/** |
| 6 | + * Clears the rows that went `unknown` after 0005 had already run. |
| 7 | + * |
| 8 | + * 0005 was correct and is finished: the runner records a name in `script_migrations` and never |
| 9 | + * offers it again, which is exactly the contract a run-once repair wants. But it cleared the |
| 10 | + * backlog that existed at the instant it ran, and the writers that produced that backlog kept |
| 11 | + * running afterwards — a table write whose block could not project one input latched the run's |
| 12 | + * registry, so its rows were stored unrecorded, for as long as that bug was live. |
| 13 | + * |
| 14 | + * Nothing heals such a row in place; a partial cell update keeps it unknown and only a full replace |
| 15 | + * carrying complete provenance clears it. So each one goes on reporting on every later read, which |
| 16 | + * is why a few dozen rows account for thousands of log lines a week. A second pass is the whole |
| 17 | + * remedy. |
| 18 | + * |
| 19 | + * A new entry rather than deleting 0005's tracking row: the registry is append-only, and a repair |
| 20 | + * that ran twice should say so twice. |
| 21 | + * |
| 22 | + * It assumes the writer that produced these is already live-fixed, and that is a deploy-ordering |
| 23 | + * requirement rather than something this file can enforce. `promote-images` needs `migrate`, so a |
| 24 | + * script migration runs while the previous image is still serving: any row an old instance creates |
| 25 | + * between this walk and the end of the rollout is behind the cursor, and the name is recorded on |
| 26 | + * success, so it is never offered again. Widening the walk would not help — it is the minutes after |
| 27 | + * it returns that are exposed, not the milliseconds during. Ship this in a release *after* the |
| 28 | + * writer fix is already promoted, and the window closes because nothing is producing rows to miss. |
| 29 | + * |
| 30 | + * Shares 0005's implementation rather than restating it. The walk locks the parent row before the |
| 31 | + * sidecar to match the application writer's order, and re-checks `status` under that lock so a |
| 32 | + * concurrently committed exact sidecar is never deleted — subtleties worth having once, not twice. |
| 33 | + * Idempotent, so it costs one empty query when there is nothing left to repair. |
| 34 | + */ |
| 35 | +export const repairUnknownTableRowProvenanceSecondPass: ScriptMigration = { |
| 36 | + name: '0006_repair_unknown_table_row_provenance_second_pass', |
| 37 | + up: (sql: Sql) => runUnknownTableRowProvenanceRepair(sql), |
| 38 | +} |
0 commit comments