|
| 1 | +# Benchmarks |
| 2 | + |
| 3 | +Performance benchmarks for PgDog. Results are stored in |
| 4 | +`target/pgdog_benches/` and compared automatically between runs. |
| 5 | + |
| 6 | +## Structure |
| 7 | + |
| 8 | +``` |
| 9 | +benches/ |
| 10 | + bench.sh shared runner library |
| 11 | + compare.py result comparison table |
| 12 | + resharding/ resharding benchmark suite |
| 13 | + pgdog.toml config: 3 source shards (pgdog1-3) → 4 destination shards (shard_0-3) |
| 14 | + users.toml |
| 15 | + setup.sql schema + tables + publication (run per source shard) |
| 16 | + fill.sql synthetic data generator (BENCH_SCALE env var or \set scale) |
| 17 | + prepare.sh hyperfine --prepare: drop destination schema, run schema-sync |
| 18 | + copy_data/ |
| 19 | + run.sh entry point: setup sources, then benchmark data-sync --sync-only |
| 20 | +``` |
| 21 | + |
| 22 | +## Running |
| 23 | + |
| 24 | +```sh |
| 25 | +# Setup sources and benchmark (compares against the previous run automatically) |
| 26 | +bash benches/resharding/copy_data/run.sh |
| 27 | + |
| 28 | +# Save as a named baseline |
| 29 | +bash benches/resharding/copy_data/run.sh --save-baseline main |
| 30 | + |
| 31 | +# Compare against a named baseline (errors immediately if it does not exist) |
| 32 | +bash benches/resharding/copy_data/run.sh --baseline main |
| 33 | + |
| 34 | +# Fewer runs for a quick check |
| 35 | +RUNS=1 WARMUP=0 bash benches/resharding/copy_data/run.sh |
| 36 | +``` |
| 37 | + |
| 38 | +### Flow |
| 39 | + |
| 40 | +1. **Setup** (once per invocation): drops and recreates `bench_copy` schema on each |
| 41 | + source shard (`pgdog1`, `pgdog2`, `pgdog3`), then fills each shard with its own |
| 42 | + slice of rows using a stepped `generate_series` keyed on `tenant_id`, so the source |
| 43 | + data is pre-distributed before the copy benchmark runs. |
| 44 | + Set `BENCH_SCALE=N` before running, or edit `\set scale` in `resharding/fill.sql` directly. |
| 45 | + |
| 46 | +2. **Benchmark** (timed, N runs): hyperfine calls `prepare.sh` before each iteration |
| 47 | + (drops destination schema, runs `schema-sync`), then times `data-sync --sync-only`. |
| 48 | + This measures pure data-copy throughput with no WAL replication involved. |
| 49 | + |
| 50 | +On first run there is no history yet: |
| 51 | + |
| 52 | +``` |
| 53 | +No previous run found -- run again to see comparison. |
| 54 | +``` |
| 55 | + |
| 56 | +On subsequent runs a comparison table is printed automatically: |
| 57 | + |
| 58 | +``` |
| 59 | +copy_data |
| 60 | +
|
| 61 | + latest current change |
| 62 | + ---------- ---------- ------------- ---------- |
| 63 | + mean 24.904 s 22.194 s -10.88% |
| 64 | + median 24.100 s 21.900 s -9.13% |
| 65 | + min 24.904 s 22.194 s -10.88% |
| 66 | + max 24.904 s 22.194 s -10.88% |
| 67 | + stddev 1.200 s 0.900 s -25.00% |
| 68 | + user 2.226 s 11.234 s +404.63% |
| 69 | + system 962.045ms 1.419 s +47.50% |
| 70 | + max_mem 48.3 MB 46.1 MB -4.55% |
| 71 | +
|
| 72 | + Performance has improved. |
| 73 | +``` |
| 74 | + |
| 75 | +`latest` is always the previous run. Named baselines persist in |
| 76 | +`target/pgdog_benches/` until manually removed. |
| 77 | + |
| 78 | +If a run is interrupted, the result file is left empty. The next run detects |
| 79 | +this, skips the snapshot, and prints a notice instead of crashing. The run |
| 80 | +after that resumes normal comparison automatically. |
| 81 | + |
| 82 | +## Default approach: bench.sh + compare.py |
| 83 | + |
| 84 | +`bench.sh` is a sourceable shell library that provides a single function: |
| 85 | + |
| 86 | +```sh |
| 87 | +bench_run NAME COMMAND [--prepare CMD] [--save-baseline NAME] [--baseline NAME] |
| 88 | +``` |
| 89 | + |
| 90 | +It handles: |
| 91 | +- Building pgdog in release mode (sets `PGDOG_BIN`, skipped if already set) |
| 92 | +- Setting `RUST_LOG=error` to suppress log I/O overhead |
| 93 | +- Running the command under [hyperfine](https://github.com/sharkdp/hyperfine) |
| 94 | +- Passing `--prepare CMD` to hyperfine when provided (runs before each timed iteration) |
| 95 | +- Saving results as JSON to `target/pgdog_benches/<name>.json` |
| 96 | +- Snapshotting the previous result to `<name>.prev.json` before each run |
| 97 | +- Calling `compare.py` to render the comparison table |
| 98 | + |
| 99 | +`compare.py` reads two hyperfine JSON files and prints a Criterion-style table |
| 100 | +with mean, median, min, max, stddev, user time, system time, and peak memory |
| 101 | +(`max_mem` — peak of `memory_usage_byte` samples), relative change per field, and an overall pass/regression verdict. It is independent of `bench.sh` |
| 102 | +and can be called directly: |
| 103 | + |
| 104 | +```sh |
| 105 | +python3 benches/compare.py target/pgdog_benches/before.json \ |
| 106 | + target/pgdog_benches/after.json \ |
| 107 | + --prev-label before --curr-label after |
| 108 | +``` |
| 109 | + |
| 110 | +Benchmarks are not required to use this approach. A test can drive hyperfine |
| 111 | +differently, use a different timing tool entirely, or produce its own output — |
| 112 | +`bench.sh` is a convenience, not a contract. |
| 113 | + |
| 114 | +## Adding a new benchmark |
| 115 | + |
| 116 | +Create `benches/<suite>/run.sh`, source `bench.sh`, and call `bench_run`: |
| 117 | + |
| 118 | +```sh |
| 119 | +#!/usr/bin/env bash |
| 120 | +set -euo pipefail |
| 121 | + |
| 122 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 123 | +BENCH_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" |
| 124 | + |
| 125 | +source "${BENCH_DIR}/bench.sh" |
| 126 | + |
| 127 | +bench_run "<name>" "<command to benchmark>" "$@" |
| 128 | +``` |
| 129 | + |
| 130 | +To reset state between hyperfine iterations, pass `--prepare`: |
| 131 | + |
| 132 | +```sh |
| 133 | +bench_run "<name>" "<command>" --prepare "<reset command>" "$@" |
| 134 | +``` |
| 135 | + |
| 136 | +The benchmarked script reads these variables from the environment at runtime, |
| 137 | +so the same env vars control both the benchmark harness and the integration |
| 138 | +test when run directly — the integration test simply uses its own defaults. |
0 commit comments