This document describes a cross-database abstraction for doing PGO (Profile-Guided Optimization) validation on non I/O bound OLTP workloads.
Key constraints:
- focus on OLTP workloads that are expected to be primarily CPU-bound after warmup
- allow the benchmark tool to vary per database (not limited to sysbench)
- produce evidence that can explain why results changed (build/profile/config/system)
Non-goals:
- do not prescribe production-safe configs
- do not attempt to validate I/O-bound workload improvements (that is a different track)
PGO results are only meaningful when the workload shape matches your intent.
For this track (“non I/O bound OLTP”), the baseline/PGO runs must satisfy:
- low sustained
iowaitduring the core OLTP cases - disk throughput is not the dominant limiter (no sustained high read MB/s for cache-friendly cases)
- DB process CPU stays high during the measurement window
If the baseline is unintentionally I/O-bound (e.g. small buffer/cache config), fix config first and rerun baseline before discussing PGO deltas.
Think of the whole workflow as a reproducible pipeline with guardrails:
- Prepare host
- pin to a stable environment (CPU governor, turbo policy, background services)
- install toolchain and sampling tools (
mpstat/iostat/pidstatequivalents)
- Build normal (baseline) binary
- record compiler/version/flags
- Provision runtime + benchmark config
- generate DB config in a deterministic way
- ensure “benchmark-tuned” defaults are applied
- record a config snapshot (file +
SHOW/SELECT-style variables)
- Initialize dataset
- dataset size and access pattern must match the intended OLTP scenario
- Warmup + quiesce
- wait for startup quiesce (engine warmup, background writes settle)
- optionally add a fixed settle sleep
- Run baseline benchmark (with system sampling)
- enforce stability rules (reject suspicious runs)
- verify workload shape is non I/O bound
- Build PGO generate binary
- Profile generation run
- run a representative training workload
- record logs + system sampling
- Verify profile validity
- non-empty profiles
- profile paths match build root/object paths
- Build PGO use binary
- verify
-fprofile-use(or equivalent) is actually applied
- verify
- Run PGO validation benchmark
- same benchmark parameters + same workload shape checks
- Package + evidence bundle
- ship binaries + logs + summary + sampling
For every “PGO result” report, keep:
- normal benchmark log + summary
- pgo-gen benchmark log + summary
- pgo-use benchmark log + summary
- pgo-use build log showing profile-use flags
- profile stats (count/size/match)
- system sampling logs for baseline + pgo (CPU/IO + DB process)
- the final packaged binaries
- a short run-info file capturing versions/paths/parameters
PGO is sensitive to what you run during profile generation.
The default policy should:
- cover the key read paths you care about
- avoid injecting unnecessary I/O noise when you’re validating a CPU-bound target
- remain stable across versions and environments
For Percona/MySQL in this repo, the standard profile-gen mode is joint_read and validation is readonly.
See docs/pgo_train_modes.md.
Benchmark intent:
point_select/read_onlyshould be mostly CPU-bound after warmup
Guardrails:
- ensure the benchmark config does not fall back to upstream defaults (example real incident:
innodb_buffer_pool_size=128MBcausedpoint_selectto become strongly I/O-bound and invalidated PGO comparisons) - record:
innodb_buffer_pool_sizeperformance_schemainnodb_flush_method
- always apply startup quiesce and stability gating
Config basis in this repository:
- 8.x runtime config is generated by
lib/mysql.sh:mysql_emit_config() - historically the benchmark tuning came from the legacy template
build-normal/init_conf.sh - refactors must explicitly migrate “workload-shape” parameters, otherwise baseline can silently become I/O-bound
Reference checklist:
docs/pgo_validation_checklist.md
Benchmark tool:
- typically
pgbenchfor baseline/read-only/read-write style OLTP - for more realistic app mixes, consider ecosystem tools (e.g. OLTPBench) depending on the target scenario
Equivalent guardrails (high-level):
- confirm the working set fits in memory caches for CPU-bound validation
- tune memory/cache/logging/checkpoint settings to avoid the benchmark becoming WAL/checkpoint I/O dominated
- record an explicit config snapshot (
SHOW/SELECT/config file) - apply the same warmup/quiesce + stability gating + system sampling
Note:
- the “right” knobs are DB-version and workload dependent; the abstraction is to prove workload shape and record the knobs, not to hard-code a single magic config.
Adopt the same abstraction:
- pick the ecosystem-standard benchmark tool
- define the CPU-bound target window
- prove workload shape via system sampling
- record config and runtime identity
- keep evidence bundle complete
- baseline accidentally becomes I/O-bound (cache too small / durability too strict / background jobs)
- benchmark starts before startup/quiesce completes
- profile generated but not actually consumed
- workload parsing/phase confusion (wrong log, wrong binary, wrong run)
- unstable runs accepted as “results”
The takeaway:
- first make results trustworthy (shape + identity + stability + evidence)
- then evaluate whether deltas are good or bad