# Reproduce the checkout migration

Tested on September 21, 2026 with Ptah 0.7.0 and PostgreSQL/pgbench 18.6.
Use a disposable database. This example creates a million orders and performs
intentional failed statements. The SQL represents the insert-and-read portion
of a checkout request; no HTTP service or complete application was measured.

Run these commands from this directory with `ptah`, `psql`, and `pgbench`
installed. `DB_URL` is the connection URL of an empty PostgreSQL database. In
the measured run, all three programs ran beside PostgreSQL in a disposable
container on the explicitly selected Docker context `remote-dev-container`.
The published Ptah Linux binary was copied from its pinned image; no CLI
wrapper or modified Ptah build was used.

## Starting state

Create the original schema and insert the existing USD orders, whose currency
was implicit in the application:

```console
ptah migrations up --db-url "$DB_URL" --migrations-dir migrations --to-version 1790078400 --verify-sum
psql "$DB_URL" -v ON_ERROR_STOP=1 -f seed.sql
```

The desired original schema is `before.sql`. `unsafe.sql` attempts to set
`NOT NULL` directly. Running it now exits `3` because the column contains nulls:

```console
psql "$DB_URL" -v ON_ERROR_STOP=1 -f unsafe.sql
```

## Keep both writers running

In another terminal, run the two transaction bodies with equal probability.
Each inserts an order and reads it back. Version 1 omits currency and reads
`COALESCE(currency, 'USD')`; version 2 writes EUR explicitly. The database
query timeout is 1.5 seconds, and the arrival/transaction latency budget is
2 seconds. Automatic retries are disabled.

```console
PGOPTIONS='-c statement_timeout=1500' pgbench "$DB_URL" -n -c 8 -j 4 -T 90 -R 300 -L 2000 --max-tries 1 -P 5 -l --log-prefix=checkout-request -f checkout-v1.sql -f checkout-v2.sql
```

The workload lasts 90 seconds in the recorded run. Start the following steps
while it is running; increase the duration if you need more time to enter them.

After about five seconds, start `blocker.sql` in a third terminal. It holds a
read transaction for 15 seconds:

```console
psql "$DB_URL" -v ON_ERROR_STOP=1 -f blocker.sql
```

Immediately attempt the default migration. This command must run before the
blocker's transaction finishes:

```console
ptah migrations up --db-url "$DB_URL" --migrations-dir migrations --to-version 1790078460 --verify-sum --lock-timeout 200ms --statement-timeout 30s --json
```

Expect exit `2`, SQLSTATE `55P03`, and `dirty_revision.applied: 0`. Wait for the
blocker terminal to finish. Retry the unchanged source and continue through
backfill and constraint validation:

```console
ptah migrations up --db-url "$DB_URL" --migrations-dir migrations --to-version 1790078820 --verify-sum --allow-dirty --lock-timeout 200ms --statement-timeout 30s
```

The four backfill files cover the seeded ID range in separate transactions;
the last has no upper bound, so it includes legacy writes made before the
default took effect. Only null values change. A batch is a migration file,
not a custom script runner. Preserve the default `file` transaction mode:
`all` would retain the constraint-creation lock through validation.

Review the remaining migration:

```console
ptah migrations lint --dir migrations --dialect postgres --latest 1
```

This intentionally exits `1`: PG303 warns about not-null validation, and DS105
flags the removal of a check constraint. The validated check is still present
when `SET NOT NULL` runs. The second statement removes the redundant check.
`--allow-destructive` below acknowledges that specific, reviewed removal; it
is not a reason to allow arbitrary pending migrations.

Linting the entire directory with Ptah 0.7.0 also reports PG305 on the
`NOT VALID` check addition. That finding does not distinguish this syntax in
the tested release. The executed SQL, separate transaction boundaries, and
PostgreSQL's documented behavior are the evidence for this case; a clean lint
report is not claimed.

```console
ptah migrations up --db-url "$DB_URL" --migrations-dir migrations --verify-sum --lock-timeout 200ms --statement-timeout 30s --allow-destructive
```

## Check the result

After the workload ends:

```console
psql "$DB_URL" -v ON_ERROR_STOP=1 -f assert-data.sql
ptah schema compare --db-url "$DB_URL" --schema-file schema.sql --exit-code
ptah migrations status --db-url "$DB_URL" --migrations-dir migrations --verify-sum --exit-code
```

Expect 1,000,000 original orders; zero missing currencies, wrong USD/EUR values,
or changed totals; and nonzero counts for both writer versions. Writer counts
and latency vary between runs. Both Ptah checks exit `0`. An explicit-null
insert is rejected by PostgreSQL:

```console
psql "$DB_URL" -v ON_ERROR_STOP=1 -c "INSERT INTO orders (customer_id, total_cents, currency) VALUES (3, 2500, NULL);"
```

The committed down files retain backfilled currency values. Reverting schema
constraints should not erase the order's currency. Rolling back the initial
migration drops the table and its data; no rollback availability claim is made.

The complete directory was also replayed into another empty database, without
seed data, using `migrations up --verify-sum --allow-destructive`. Its final
schema comparison exited `0`.

## Recorded results

`measured/` contains output from the 90-second run with Ptah and PostgreSQL
colocated, including the deliberate lock timeout and subsequent retry:

- 26,855 completed transactions; zero failed or skipped transactions.
- Zero completed transactions exceeded the 2-second latency budget.
- Mean latency including scheduling delay: 7.031 ms.
- Final database counts: 13,538 legacy orders and 13,317 new-writer orders.
- All 1,000,000 original orders remained; no incorrect currency or total.
- Final schema comparison, migration status, and fresh replay checks passed.

Trailing whitespace and final blank lines in the psql tables are removed;
`verified.json` also records their original output hashes.

`commands.json` preserves the executed argument arrays and exit codes. The
runner captured `migrations up/status` with `--json` for evidence; the article
uses text output except when examining the failed revision. Command durations
include launching `docker exec` over SSH and are not database statement timings.
The URL in this disposable fixture uses a test-only credential.

`earlier-run/` preserves the first 120-second run. The laptop's Darwin CLI
connected over an SSH tunnel while operator images were compiling on the
shared host. It completed 35,655 transactions, skipped 191 arrivals, and
finished 19 transactions past the 2-second budget. Its data checks passed,
but its latency target failed. A subsequent 30-second control with no migration
completed 8,700 transactions with no failures, skips, or late transactions.
Changing both execution placement and background load does not isolate a cause.
Neither run establishes a production latency guarantee or replica behavior.

The immutable software identifiers and hashes of the published fixtures are
in `verified.json`. PostgreSQL's [ALTER TABLE reference](https://www.postgresql.org/docs/18/sql-altertable.html)
describes the validated-check optimization. Ptah's [zero-downtime guide](https://docs.ptah.run/edge/operate/zero-downtime-changes/)
covers the general lock and timeout behavior.
