SQL-Engineering-Handbook

Automated Performance Testing & CI/CD Integration

Introduction

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 transactions table (part of 00_Schema.sql), the module’s documented extension to the core employes/departments/locations schema.

Learning Objectives

Why This Exists

A 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.

Business Motivation

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.

Benchmarking Tools: pgbench and sysbench

See the accompanying ci/run_benchmark.sh for a runnable custom-harness example and ci/ for ready-to-use CI templates built around it.

Setting Performance Budgets

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.

GitHub Actions and GitLab CI: Blocking a PR on Regression

Both templates in ci/ follow the same three-stage shape:

  1. Seed a reproducible benchmark dataset (fixed row counts and value distributions — a benchmark against random or empty data produces meaningless plan shapes, per this handbook’s core “measure against production-realistic volume” principle).
  2. Run the query under test via ci/run_benchmark.sh, capturing p95 latency and the EXPLAIN plan shape.
  3. Compare against the committed baseline and fail the job — which blocks the PR merge on a protected branch — if the budget is exceeded.

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.

Monitoring Query Latency in Production

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:

Decision Tree: When to Automate vs. Manual Tuning

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.

Engineering Notes

MySQL / PostgreSQL / SQL Server Notes

Common Mistakes

Anti-patterns

Edge Cases

Troubleshooting Guidance

Scalability Considerations

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.

Additional Dialect Notes (Oracle, SQLite, DuckDB)

Interview Questions

Summary

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.

Practice Challenges

  1. Using 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.
  2. Walk the decision tree above for a full-text search query on a products table. Justify each branch you take.

Further Reading