The progress snapshot is the machine-readable observation a caller receives when it polls a
running schema change through the *WithProgress executor entry points. It is the one JSON
shape an operator or orchestrator consumes to display or act on execution progress. This
document is the contract: the fields, the closed vocabularies, and the behavior required of
a consumer. The Go source of truth is pkg/progress; TestSnapshotJSONShape pins the exact
keys, including the example at the end of this page.
Every snapshot carries format_version. A consumer that does not recognize the version must
reject the snapshot — never guess at field semantics. The version covers more than the
field shape: the closed vocabularies below (phases, operations) are pinned to it. Adding a
phase or operation value is a contract change and bumps format_version, even if no field
is added or renamed.
The plan report, lint report, and
suggest report are separate contracts with their own format_version;
all version independently.
phase and detail.operation draw from the closed vocabularies below. A consumer that
meets a value it does not recognize must treat the execution's state as unknown — never
map it onto a known value and proceed. Progress is observational: an unknown value never
licenses a consumer to intervene in the change itself.
| Field | Type | Presence | Meaning |
|---|---|---|---|
format_version |
int | always | Contract version; reject unknown versions. |
phase |
string | always | Overall execution phase (see Phases). |
step |
int | after the first step starts | 1-based position in a multi-step sequence. Absent before execution reaches step 1. |
total_steps |
int | after Start |
Number of steps in the execution; 1 for single-statement entry points. |
elapsed_ns |
int | always | Nanoseconds since execution started. For a terminal phase, frozen at the instant the outcome was recorded — a late poll reports the execution's duration, not the observation's age. |
step_elapsed_ns |
int | always | Nanoseconds since the current step started; frozen the same way at a terminal phase. |
detail |
object | always | The operation currently executing (below). |
| Field | Type | Presence | Meaning |
|---|---|---|---|
operation |
string | once execution starts | The current operation's execution class (see Operations). |
server_phase |
string | active concurrent build only | PostgreSQL's own phase string from pg_stat_progress_create_index, verbatim. |
active |
bool | always | Whether an operation is executing now. false with phase: "running" means a concurrent build's progress row has left the server view. |
attempt |
int | bounded retries only | The current attempt number when the executor is inside its bounded retry loop. |
work |
object | server-observed work only | Present exactly when the server published a progress row; then every counter below is present, so a fresh build reports honest zeros rather than an empty object. |
blocks_done / blocks_total and tuples_done / tuples_total come from
pg_stat_progress_create_index during a concurrent index build. rows_copied /
rows_total and bytes_copied / bytes_total are reserved for copy-and-swap and are 0
on every native operation — the engine never fabricates copy counters.
| Value | Meaning |
|---|---|
pending |
Execution has not started. |
running |
Execution is active. |
finished |
Terminal: completed successfully. |
failed |
Terminal: reached a terminal failure. |
A terminal snapshot is immutable: once finished or failed is observed, every later poll
returns the identical snapshot, elapsed values included.
| Value | Meaning |
|---|---|
admitting |
A sequence's steps are still being validated; no statement has run yet. |
optimistic |
One bounded direct native attempt. |
brief |
A brief transactional sequence step. |
validate-constraint |
A constraint-validation scan. |
concurrent-index-build |
A concurrent index build (the one operation with server-observed work). |
The tracker is caller-owned and has no goroutines or timers: polling lifetime is exactly the
caller's context. A poll during an active concurrent index build performs one read of the
server's progress view over the executor's reserved session; every other poll is pure
memory. On a query error the returned snapshot still carries the last-known tracker state —
phase is never empty — with the error returned alongside for the caller to classify.
A poll during step 2 of a 3-step sequence, mid concurrent index build:
{
"format_version": 1,
"phase": "running",
"step": 2,
"total_steps": 3,
"elapsed_ns": 2750000000,
"step_elapsed_ns": 750000000,
"detail": {
"operation": "concurrent-index-build",
"server_phase": "building index",
"active": true,
"attempt": 2,
"work": {
"rows_copied": 0,
"rows_total": 0,
"bytes_copied": 0,
"bytes_total": 0,
"blocks_done": 11,
"blocks_total": 40,
"tuples_done": 7,
"tuples_total": 21
}
}
}