Skip to content
PtahPtah

Migrate a source that is not changing

If nothing writes to the source during the migration, there is nothing to catch up on. This is the cheapest path and the one with the fewest moving parts — as long as the claim is true.

  • A table loaded in batches, and you are running between loads.
  • A read replica or a restored snapshot.
  • A table behind a feature flag you turned off.
  • A maintenance window in which writes are stopped.

It does not apply to a table that is merely quiet. “Nothing has written for an hour” is not the same as “nothing will write”, and the difference is a row that silently keeps a vector computed from text it no longer has.

source:
mutable: false
consistency:
mode: immutable

No triggers are installed, no companion table is created, and no catch-up is needed.

Terminal window
ptah inference prepare --spec spec.yaml --db-url "$DB" --run-id "$RUN"
ptah inference backfill --spec spec.yaml --db-url "$DB" --run-id "$RUN"
ptah inference index --spec spec.yaml --db-url "$DB" --run-id "$RUN"
ptah inference verify --spec spec.yaml --db-url "$DB" --run-id "$RUN"

catchup is not in the sequence. Running it against this mode is refused rather than reported as success:

Terminal window
error: catch-up needs a consistency mode that records changes, and this
specification selects "immutable"

That refusal is deliberate. A catch-up that “succeeded” over a mode recording nothing would be a run reporting itself caught up on a source it never watched.

plan says what the mode means before anything runs:

Consistency mode: immutable
- this requires writes to be paused for the duration, and the run refuses to
declare itself ready if they are not

Verification’s freshness layer is what catches a broken promise: a row whose source version moved after its vector was computed is reported. If writes happened during the backfill despite the declaration, that is where it surfaces.

What Ptah cannot do is prevent the writes. mutable: false is your statement about your system, and Ptah takes it and then measures the result.

You have three options, in increasing cost:

  1. Re-run the backfill. It is resumable and it re-embeds what changed only if your version_strategy can see the change. With input_hash it can.
  2. Switch the specification to outbox and start over. The generation identity does not include the consistency mode, so this does not invalidate the vectors already written — but the boundary and the triggers have to be installed by a fresh prepare.
  3. Stop the writes properly and repeat.

The second is what most people want after discovering the source was not as paused as they thought.