Skip to content

m4rri4nne/db-performance-tests

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

9 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Database Performance Tests

Status Python PostgreSQL Docker pytest Grafana

A PostgreSQL performance testing suite focused on three scenarios:

  • N+1 Query Detection โ€” instrument queries, surface repeated patterns, compare bad vs. fixed implementations
  • Deadlock Simulation โ€” reproduce the classic reverse lock-order deadlock and document the fix
  • Query Regression Tracking Across Schema Changes โ€” benchmark before and after a migration, diff execution plans, gate on latency thresholds with pytest

Schema: users โ†’ orders โ†’ order_items + inventory


Prerequisites

  • Docker + Docker Compose
  • Python 3.10+

Setup

1. Start the database

docker compose -f docker/docker-compose.yml up -d

2. Create virtual environment and install dependencies

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

3. Configure environment variables

Create .env at the project root:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=perfdb
DB_USER=perftest
DB_PASSWORD=perftest

4. Apply the schema and seed data

python scripts/setup_schema.py --schema baseline/001_initial_schema
python data/seed.py 10000

setup_schema.py applies the SQL file and saves a schema_v1.sql snapshot next to it.


Scenarios

N+1 Query Detection

python analysis/n_plus_one_detector.py

Attaches a SQLAlchemy event listener, runs a simulated N+1 code path (one query per order row), then the fixed version (single JOIN). Prints a count of repeated normalized queries so the pattern is unmistakable.

[BAD]  20 orders fetched โ†’ 21 queries fired (1 + 20)
  N+1 candidates detected:
    [20x] SELECT EMAIL FROM USERS WHERE ID = $?

[GOOD] 20 orders+emails fetched โ†’ 1 query fired

To instrument your own code, import attach_logger, reset_log, and detect from analysis/n_plus_one_detector.py.


Deadlock Simulation

python analysis/deadlock_simulator.py

Spawns two threads that acquire row locks in opposite order using a threading.Barrier to make the deadlock deterministic. PostgreSQL detects the cycle and rolls back one transaction automatically. The script prints which transaction was the victim and shows the consistent lock-ordering fix.

Transaction A: committed
Transaction B: rolled back โ€” DeadlockDetected

PostgreSQL detected the cycle and rolled back Transaction B.

Query Regression Tracking Across Schema Changes

The workflow is: baseline โ†’ capture plan โ†’ migrate โ†’ measure impact โ†’ pytest gate.

1. Capture baseline performance

python reports/query_regression_report.py low

2. Capture EXPLAIN plan before migration

python analysis/explain_analyzer.py

Plans are saved as JSON to reports/plans/.

3. Apply migration

python scripts/setup_schema.py --schema v2_add_indexes/002_add_indexes

4. Measure the impact

python reports/query_regression_report.py low
Query            avg ms    p95 ms    max ms  ฮ” vs last
order_history      0.38      0.62      0.95  -88.1% โœ“
inventory_search   0.28      0.45      0.70  -74.5% โœ“

Queries that regressed by more than 20% are flagged with โš .

5. Run the full test suite

pytest -v

Runs all four test modules. Fails if any critical query exceeds SLOW_QUERY_THRESHOLD_MS (200 ms, set in config.py), if N+1 patterns appear in the optimized path, if deadlock detection doesn't behave as expected, or if the planner falls back to a Seq Scan on an indexed query.


Test Suite

Tests live in benchmarks/ and are discovered automatically via pyproject.toml. Run all:

pytest -v

Run a specific scenario with a marker:

pytest -v -m n_plus_one   # N+1 detection tests
pytest -v -m deadlock     # deadlock simulation tests
pytest -v -m explain      # EXPLAIN ANALYZE plan regression tests
File Marker What it checks
test_n_plus_one.py n_plus_one Bad path produces repeated queries; good path produces none
test_deadlock.py deadlock Exactly one transaction commits and one is rolled back
test_explain.py explain No Seq Scan on indexed queries; actual time within threshold
test_slow_queries.py (none) Five critical queries complete within 200 ms

Fixtures (engine, instrumented_engine) are defined in conftest.py. JUnit XML is written to reports/junit.xml after every run.


CI

Tests run automatically on every push and pull request to main via .github/workflows/performance-tests.yml. The workflow spins up a PostgreSQL 16 service container, applies the baseline schema with psql, seeds 1 000 rows, and runs pytest. Results are published as a check via dorny/test-reporter and the JUnit XML is uploaded as an artifact.


Grafana Dashboard

Benchmark results are automatically exported to a benchmark_results table in PostgreSQL and visualized in Grafana.

Start Grafana:

docker compose -f docker/docker-compose.yml up -d

Open http://localhost:3000 โ€” login admin / admin.

The Query Benchmark Results dashboard loads automatically. No manual setup needed: the PostgreSQL datasource and dashboard are provisioned on startup.

Panels:

Panel What it shows
Avg Latency Over Time avg_ms per query, color-coded โ€” turns yellow at 100 ms, red at 200 ms
P95 Latency Over Time p95_ms per query โ€” highlights tail latency spikes across migrations
Latest Benchmark Run Table of the most recent run: avg / p95 / max per query

Use the Volume dropdown at the top to switch between low, medium, and high data sets.

Every time you run the regression report, results are written to the table automatically:

python reports/query_regression_report.py low
# โ†’ runs benchmarks, saves JSON, exports to benchmark_results, prints delta table

To backfill Grafana from previously saved JSON files:

python reports/export_metrics.py

Seeding data

python data/seed.py 1000      # quick smoke test
python data/seed.py 10000     # development
python data/seed.py 100000    # regression benchmarks

Each run truncates all tables. The seed is deterministic (SEED = 42).


Project Structure

.
โ”œโ”€โ”€ config.py                          # DB_URL and SLOW_QUERY_THRESHOLD_MS
โ”œโ”€โ”€ conftest.py                        # pytest fixtures: engine, instrumented_engine
โ”œโ”€โ”€ pyproject.toml                     # pytest config and markers
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ analysis/
โ”‚   โ”œโ”€โ”€ n_plus_one_detector.py         # N+1 detection and simulation
โ”‚   โ”œโ”€โ”€ deadlock_simulator.py          # Concurrent deadlock demo
โ”‚   โ””โ”€โ”€ explain_analyzer.py            # EXPLAIN plan capture and diff
โ”œโ”€โ”€ benchmarks/
โ”‚   โ”œโ”€โ”€ queries/                       # Raw .sql files (12 queries)
โ”‚   โ”œโ”€โ”€ scenarios/
โ”‚   โ”‚   โ””โ”€โ”€ run_benchmark.py           # Volume benchmark runner
โ”‚   โ”œโ”€โ”€ test_n_plus_one.py             # N+1 detection tests
โ”‚   โ”œโ”€โ”€ test_deadlock.py               # Deadlock tests
โ”‚   โ”œโ”€โ”€ test_explain.py                # EXPLAIN ANALYZE plan tests
โ”‚   โ””โ”€โ”€ test_slow_queries.py           # Latency threshold gate (5 critical queries)
โ”œโ”€โ”€ data/
โ”‚   โ”œโ”€โ”€ seed.py
โ”‚   โ””โ”€โ”€ distributions.json
โ”œโ”€โ”€ migrations/
โ”‚   โ”œโ”€โ”€ baseline/
โ”‚   โ”‚   โ””โ”€โ”€ 001_initial_schema.sql
โ”‚   โ””โ”€โ”€ v2_add_indexes/
โ”‚       โ””โ”€โ”€ 002_add_indexes.sql        # Sample migration for regression demo
โ”œโ”€โ”€ reports/
โ”‚   โ”œโ”€โ”€ query_regression_report.py     # Delta reporter (also exports to Grafana)
โ”‚   โ”œโ”€โ”€ export_metrics.py              # Writes results to benchmark_results table
โ”‚   โ”œโ”€โ”€ output/                        # Timestamped benchmark JSON results
โ”‚   โ””โ”€โ”€ plans/                         # Saved EXPLAIN plans
โ”œโ”€โ”€ scripts/
โ”‚   โ””โ”€โ”€ setup_schema.py                # Apply migration + snapshot schema
โ”œโ”€โ”€ .github/
โ”‚   โ””โ”€โ”€ workflows/
โ”‚       โ””โ”€โ”€ performance-tests.yml      # CI: schema โ†’ seed โ†’ pytest
โ””โ”€โ”€ docker/
    โ”œโ”€โ”€ docker-compose.yml             # PostgreSQL + Grafana
    โ”œโ”€โ”€ init.sql
    โ””โ”€โ”€ grafana/
        โ”œโ”€โ”€ provisioning/
        โ”‚   โ”œโ”€โ”€ datasources/postgres.yml
        โ”‚   โ””โ”€โ”€ dashboards/dashboard.yml
        โ””โ”€โ”€ dashboards/benchmark.json  # Auto-provisioned dashboard

References

About

PostgreSQL performance testing suite โ€” benchmarks query latency, detects N+1 patterns, simulates deadlocks, and tracks regressions across schema versions with Grafana dashboards. ๐Ÿ˜๐Ÿ“Š

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

โšก