Every optimization technique in Lessons 01–09 and every cost technique in
Lesson 10 shares the same failure mode: it works the day it’s written and
silently regresses six months later when the data volume triples, an index
gets dropped in a migration, or a well-intentioned refactor reintroduces a
non-SARGable predicate. Manual EXPLAIN review at PR time catches the
obvious cases. It does not catch a regression introduced by a change to a
different query, a schema migration, or gradual data growth. This lesson
treats query performance as a tested, gated property of the codebase —
exactly like unit tests — instead of a one-time review artifact.
Schema note: this lesson’s SQL lab uses the handbook’s shared
transactionstable (part of00_Schema.sql), the module’s documented extension to the coreemployes/departments/locationsschema.
pgbench and sysbenchA query optimized in Lessons 01–09 and validated for cost in Lesson 10 is only
proven correct at the moment it was reviewed. Schemas migrate, indexes
get dropped by a “cleanup” PR that didn’t know they mattered, ORMs
regenerate query shapes on library upgrades, and data grows. None of these
events trigger a manual EXPLAIN review by default. A performance test
suite wired into CI is the only mechanism that re-validates every one of
this handbook’s lessons on every single change, automatically, forever.
A fraud-review workbench query is tuned, reviewed, and shipped at 45ms p95. Eight months later, an unrelated PR adds a new nullable column to the same table and the migration tool regenerates the table without preserving a covering index the query depended on. Nobody reviews the workbench query — the PR touched a different feature entirely. The regression ships, the workbench’s p95 climbs past a second, and the team learns about it from a reviewer complaint, not a test. A performance budget wired into CI turns this into a blocked PR with a clear failure message instead of a production incident.
pgbench (PostgreSQL-native): built-in TPC-B-like workload generator,
or a custom -f script.sql workload for testing specific queries under
concurrency. Reports transactions per second (TPS) and latency
percentiles directly.sysbench (engine-agnostic, MySQL/MariaDB/PostgreSQL via Lua scripts):
the standard choice when the CI pipeline needs one benchmarking tool
across multiple database engines rather than a Postgres-only workflow.pgbench TPC-B run,
because it fails on the exact query that regressed rather than an
aggregate throughput number that requires further investigation to
attribute.See the accompanying ci/run_benchmark.sh for a runnable custom-harness
example and ci/ for ready-to-use CI templates built around it.
A performance budget is a concrete, versioned number a query must not exceed — the CI equivalent of the SLA thinking introduced in Lesson 10’s “Business Motivation” section:
| Budget dimension | Example threshold | Where it’s enforced |
|---|---|---|
| p95 latency | ≤ 50ms for a fraud-review workbench lookup query | CI gate against a seeded benchmark dataset |
| Rows examined / EXPLAIN cost | No more than 3× the rows returned (SARGability regression proxy) | CI gate, static EXPLAIN parse |
| Plan shape | Must remain an index scan on idx_transactions_emp_date, must not regress to a sequential/full scan |
CI gate, EXPLAIN plan-node assertion |
| Regression tolerance | Fail if p95 grows more than 20% versus the baseline committed alongside the query | CI gate, diff against a stored baseline artifact |
Budgets must be committed alongside the query they govern, not stored
in a separate, easily-stale spreadsheet — the same principle Lesson 02
applies to keeping EXPLAIN review close to the code it evaluates.
Both templates in ci/ follow the same three-stage shape:
ci/run_benchmark.sh, capturing p95
latency and the EXPLAIN plan shape.See ci/github-actions-perf-gate.yml and ci/gitlab-ci-perf-gate.yml for
the full, ready-to-use workflow definitions, and ci/run_benchmark.sh for
the benchmarking script both templates invoke.
A CI gate only proves a query performs acceptably against the benchmark environment’s data shape. Production monitoring is the feedback loop that validates whether that benchmark environment still reflects reality:
pg_stat_statements
(Postgres), Performance Schema digest tables (MySQL), or an APM’s
query-level tracing.flowchart TD
A[New or changed query] --> B{Does this query run on a hot path -- fraud-review workbench, auth, dashboard load?}
B -- No --> C[Manual EXPLAIN review at PR time is sufficient]
B -- Yes --> D{Will this query's plan shape be affected by future schema or data changes outside this PR?}
D -- No, isolated and stable --> C
D -- Yes --> E{Is the regression pattern expressible as a concrete, versioned budget -- p95, plan-node type, rows examined?}
E -- No, requires expert judgment case by case --> F[Manual tuning review by a senior engineer, on a recurring cadence]
E -- Yes --> G[Automate: commit a performance budget and wire it into the CI gate]
G --> H[Re-review the budget itself quarterly -- budgets go stale exactly like the code they govern]
The decision that matters most is the third diamond: automation only pays off when the regression pattern can be expressed as a number or a plan-node assertion a CI job can evaluate without human interpretation. A query whose correct plan depends on judgment calls this handbook covers narratively (Lesson 07’s tuning workflow) is a poor candidate for a rigid CI gate and a good candidate for a recurring manual review cadence instead.
EXPLAIN plan shape (Lesson 02) is
usually more reliable in CI than a regression gate on raw latency,
because plan shape doesn’t vary with shared-runner CPU contention the way
wall-clock time does.pgbench -f custom_script.sql -T 60 -c 10 gives
throughput and latency percentiles directly; EXPLAIN (FORMAT JSON)
output can be parsed programmatically in CI to assert plan-node type
without a human reading the plan.sysbench with a custom Lua script is the standard
benchmarking path; EXPLAIN FORMAT=JSON provides the same
programmatically-parseable plan structure as Postgres’s JSON format.SET STATISTICS TIME, IO ON combined with Extended
Events session capture gives CI-parseable timing and I/O counters;
sqlpackage/tSQLt are common CI-integration points for teams already
standardized on SQL Server tooling.A performance test suite has to scale in two dimensions simultaneously: the number of queries under test grows with the codebase, and the seed data volume for each query needs periodic recalibration as production data grows. Budget CI runtime accordingly — running every performance test against full production-scale seed data on every PR is rarely affordable; a common pattern is a fast subset on every PR and a full-scale nightly run that reports regressions without blocking merges.
SQL Performance Analyzer and SQL Plan Baselines provide
native regression-detection tooling that maps closely to this lesson’s
“commit a plan-shape baseline” pattern, built directly into the engine
rather than requiring an external CI harness..db file fixture rather than throughput/concurrency
benchmarking — sysbench/pgbench-style tools don’t apply.EXPLAIN plan shape often more reliable in CI than
asserting on raw query latency?”A performance budget committed alongside the query it governs, enforced by a CI gate that fails the build on regression, closes the gap every other lesson in this handbook leaves open: proving a query is fast today says nothing about whether it will still be fast after the next schema migration, data-growth cycle, or unrelated refactor. Automate what can be expressed as a concrete, versioned threshold; reserve manual tuning judgment for regressions that genuinely require expert interpretation.
ci/run_benchmark.sh as a starting point, add a plan-shape
assertion (not just a latency threshold) for one of the queries in
Lesson 04’s join examples.products table. Justify each branch you take.pgbenchsysbench official documentation and Lua scripting reference