# Chapter 4 wait-capture environment
Chapter 4 wait diagrams must come from `pg_wait_tracer`'s exact capture data,
not `EXPLAIN ANALYZE` timing. The capture image supplies the Linux BPF
toolchain; the traced PostgreSQL instances remain the disposable plan-audit
containers.
Build the PostgreSQL-version-matched image from the manuscript repository:
```sh
docker build -t learnyouapg-pgwt-capture -f scripts/wait_capture/Dockerfile .
```
For Citus, pass the audited Citus image explicitly:
```sh
docker build --build-arg BASE_IMAGE=citusdata/citus:14.1.0 \
-t learnyouapg-pgwt-capture-citus \
-f scripts/wait_capture/Dockerfile .
```
The capture runner is intentionally not enabled by ordinary manuscript builds.
It needs a Linux host with BPF and tracefs support. Docker Desktop's Linux VM
can expose a target process but does not expose the scheduler tracepoints that
the full-fidelity tracer needs, so it is not a valid capture host.
## The operation-card capture contract
Generate the exact-capture matrix before touching the database:
```sh
make capture-matrix
```
It currently enumerates 34 transcluded cards plus two published Operations
reference cards: 34 single-postmaster stock PostgreSQL/pgvector captures, plus
`Broadcast` and `Redistribute`, which need separate coordinator-and-worker
Citus archives. Every row contains the real
query (with its `EXPLAIN` wrapper removed), a unique statement marker, the
required target, its raw-trace directory, and its final OTLP destination.
On a prepared Linux capture host, run the stock sweep explicitly:
```sh
make capture-waits-stock \
AUDIT_DATABASE=postgresql://
[email protected]/plan_audit \
POSTMASTER_PID="$(head -1 "$PGDATA/postmaster.pid")" \
PGWT_ROOT=/opt/pg_wait_tracer
```
The runner starts `pg_wait_tracer --mode full` for each card, runs the marked
query in a transaction that rolls back setup and data changes, retains the raw
archive under `captures/chapter4/raw/<operation>/`, and exports only that
marked execution into `captures/chapter4/<operation>.otlp.json`. It rejects a
missing query marker, a non-exact export, or an ambiguous selection. It never
rewrites a manuscript card or attaches a diagram by itself.
`GatherMerge` also has an explicit capture-only scale overlay. The 10k-row
plan fixture launches workers but finishes before their PID metadata can be
retained. Run that one capture with `--prepare-capture-overlay`; the overlay
extends `animals` to five million rows while leaving the card's literal query
and operator unchanged.
The five `Operations/Other` cards use tracked operator-preserving capture SQL
under `scripts/wait_capture/fixtures/other_*.sql`. Their tiny manuscript queries
remain the literal plan examples; the capture-only workloads make the same node
observable without changing those plans. CTE Scan crosses `work_mem`, Function
and Values Scan execute literal sleeping expressions, Subquery Scan retains its
`OFFSET 0` boundary around measured inner work, and Foreign Scan uses a loopback
`postgres_fdw` socket because `file_fdw` does not publish its child-pipe block as
a PostgreSQL wait event. Recapture just this batch with:
```sh
python3 scripts/wait_capture/capture_chapter4_stock.py \
--section Other \
--database "$AUDIT_DATABASE" \
--postmaster-pid "$POSTMASTER_PID" \
--pgwt-root "$PGWT_ROOT" \
--sudo
```
`Broadcast` and `Redistribute` are intentionally not sent through that command.
Their capture requires an isolated Citus cluster plus one exact tracer archive
for the coordinator and for each worker. Attach a Citus graph only after the
coordinator's marked execution, every participating worker PID, and their
overlapping execution window have been retained and correlated. A coordinator-
only export is not complete distributed-operation evidence.
For each completed capture:
1. Run the exact tracer (`pg_wait_tracer --mode full`) around the deterministic
operation fixture on the matching PostgreSQL, pgvector, or Citus target.
2. Export the selected execution with the tracer project's
`tools/export_otel.py`. Its `execution_detail` request is exact-fidelity
only and preserves leader and parallel-worker lanes.
3. Commit the resulting OTLP JSON under `captures/chapter4/`, named after the
operation (for example, `captures/chapter4/nested_loop.otlp.json`). This is
the auditable primary evidence, not an intermediate build product.
4. Change that manifest entry to `runtime_status:
wait-capture-attached`, `trace_kind: captured-wait-trace`, and add both the
OTLP `capture_asset` and generated SVG `trace_asset` paths.
5. Add the SVG image line immediately after the card's literal `EXPLAIN`
output. `make chapter-4` will render it and refuse missing source evidence.
The renderer only accepts `capture-required` or `captured-wait-trace`; it
cannot turn `EXPLAIN ANALYZE` timing, a sampled trace, or a teaching fixture
into a Chapter 4 wait diagram.
After the manifest points to the accepted OTLP capture, regenerate only the
affected card instead of the complete ledger:
```sh
make operation-trace OP=NestedLoop
make operation-assets OP=NestedLoop # plan and trace together
```
Use `make list-operations` when the Make key is not obvious from the Markdown
filename. Rendering is deliberately separate from this capture procedure, so
rerunning either command cannot silently execute a query or replace evidence.