Technical note
Our math¶
A PerformanceRegression is a verdict reached in seven steps from two cumulative counters. This note derives each step, states the assumptions it needs and measures where they fail. The figures run the engine's own arithmetic, in your browser.
OverviewSeven steps from a counter to a verdictEach stage can stop the analysis. The lab below plays a new query every few seconds; the stage that stops it turns red.¶
LabRun the whole engineThe Go correlation engine's own arithmetic, on a new synthetic query every few seconds. Watch which stage stops it.¶
- Regression size
- Noise, per-call CV
- Calls per scrape
- Lands after deploy
- Autocorrelation φ
- Scrape interval
Window 30 min each side · θ = 0.20 · α = 0.05 · floors 10 samples and 100 calls · tolerance max(2, 0.2 × 30) = 6 min.
DerivationFrom counters to samplesThe number PostgreSQL shows is not the number we analyse.¶
§1Interval means¶
pg_stat_statements exposes two monotone counters per query: the calls and the total execution time in milliseconds, both accumulated since the last reset. Its mean_exec_time is their ratio, which is a mean over the whole history. We scrape both counters and analyse the mean of the calls made since the previous scrape:
The cumulative mean is slow for a precise reason. If a query ran calls at mean and then calls at , the counter reports
so after a deploy it moves only by the share of calls made since. A step of +100% needs as many calls after the deploy as before it just to read +50%. The interval mean reads the step at the next scrape.
A counter that goes down (a restart, pg_stat_statements_reset(), a change in stats_since) makes negative. That interval is discarded, not clamped, because its calls belong to no window. The cumulative mean jumps at a reset; the interval means do not.
- Step
- Step lands at
- Counter reset
DerivationGates before statisticsFloors decide when we are allowed to answer, not how often the answer is wrong.¶
§2Floors and the pre-filter¶
Both windows (before and after the deploy) must clear two floors. Samples protect the machinery: the t-test and the energy statistic estimate a spread from the data, so they need points, . Executions bound the precision of the window's mean: the standard error of a mean of calls with per-call coefficient of variation is
With and , a window mean is good to about ±10% at one standard error. Windows with fewer calls return InsufficientData, which is different from NoRegression: the first says "we could not tell".
The pre-filter compares the call-weighted window means, , and stops when
It is an optimisation and a guard: the expensive stages run only on shifts big enough to matter.
- Per-call CV
- Calls in the window
- Scrapes in the window
Stage 1Finding the change with energyE-divisive means asks whether two stretches of the series come from the same distribution, not only whether their means differ.¶
§3The energy statistic¶
For two samples and let be the mean absolute distance across them. The energy distance of Székely and Rizzo [1] compares the cross distance with the distances inside each sample:
It is zero exactly when the two samples have the same distribution (in the limit) and grows with any difference: location, spread or shape. Scaled by the sample sizes it becomes the statistic the engine evaluates at every admissible split of the series, with within-sample distances averaged over unordered pairs [2]:
Here , , and keeps a change from being "found" in the first or last few points. A split is kept only if . Evaluating all splits costs because moving the split one step changes each sum by one row and one column.
Rolling deploys and connection draining spread the shift over minutes, and the collector adds scrape granularity. Letting the data place the split gives the shift's real start, and the next stage asks whether that start is close enough to the deploy to blame it.
- Shift in the middle
- Noise
Stage 1bWas it this deploy?A change point is only evidence about a deploy if it is close to it.¶
§4The tolerance window¶
Let be the deploy time and the located change. The change is attributed to the deploy only if
where is the analysis window. Making the allowance a fraction of the window keeps the same relative slack for a tight 10-minute window and a loose 60-minute one; the floor keeps short windows from shrinking it to nothing.
The order matters. The engine checks the tolerance before the significance test, so a shift that is very significant but starts twenty minutes before the deploy is reported as unrelated background change, never as a regression of this deploy. This is the step that turns a change detector into an attribution tool.
A wide tolerance catches slow rollouts and attributes more unrelated shifts to the deploy. A narrow one does the opposite. Under a Poisson process of unrelated shifts the expected number of coincidences grows linearly in .
- Window W
- Located change
Stage 2Is it more than noise?Welch's t-test on the two segments the data itself found.¶
§5Welch's t-test¶
The two segments on either side of are compared without assuming equal variances. A latency regression often changes the spread as well as the mean, so the pooled-variance Student test would be the wrong tool [3]:
The reference distribution is Student's with the Welch–Satterthwaite degrees of freedom,
and the two-sided p-value has a closed form through the regularised incomplete beta function, which is what the engine computes with a continued fraction [4]:
The test is applied to the per-interval means , not to individual calls. By the central limit theorem those are much closer to normal than the heavy-tailed per-call latencies, which is why a test that assumes normality is defensible here at all.
- Change in the mean
- Interval-mean CV
- Points before
- Points after
InterpretationWhat the score meansconfidenceScore is 1 − p. It ranks detections. It is not the probability that the regression is real.¶
§6A p-value is not a posterior¶
The p-value answers "if nothing changed, how often would noise alone look at least this extreme?". Reading as "the chance the change is real" inverts the conditional. How far apart the two can be is bounded, without any assumption about the prior, by the calibration of Sellke, Bayarri and Berger [5]. For the Bayes factor in favour of "no change" is at least , so with even prior odds
| p | score 1 − p | P(no change) ≥ |
|---|---|---|
| 0.05 | 0.95 | 0.29 |
| 0.01 | 0.99 | 0.11 |
| 0.001 | 0.999 | 0.018 |
| 0.0001 | 0.9999 | 0.0025 |
A score of 0.95 can leave a 29% chance that nothing changed. That is why the score is documented as a ranking and a threshold input, and why it should not be quoted as a probability.
- p-value
CaveatThe search inflates significanceChoosing the split from the data, then testing it as if it had been chosen in advance, is optimistic.¶
§7Maximally selected statistics¶
Stage 1 picks the split with the largest . Stage 2 then tests that split with a t-test whose reference distribution assumes the split was fixed beforehand. Under "no change" a t-statistic at a fixed split has , but the split that maximises a separation statistic is by construction the one where the two sides look most different, so the p-value at that split is too small:
The statistical literature calls this the problem of maximally selected statistics and corrects for it with a p-value that accounts for the search [6]. E-divisive means itself does so with a permutation test over the whole search [1].
The figure draws series with no change at all, applies stages 1 and 2 exactly as the engine does, and histograms the p-values. A correct test would be flat. With independent noise the lab typically puts 35–39% of them below 0.05; with autocorrelated noise, far more.
Three other gates sit around this one: the pre-filter (a real +20% is required), the tolerance (the located change must sit near the deploy) and the floors. Through the whole pipeline the measured share of no-change analyses reported as Detected is much lower than the raw p-value rate; the roadmap tracks the calibration work.
- Points per series
- Autocorrelation φ
Dashed line: what a correctly calibrated test would give.
DesignHow many calls you needSensitivity is bought with executions. This is the price list.¶
§8Sample size for a target effect¶
To detect a relative change in the mean at size (two-sided) with power , a two-sample comparison needs, per window,
using and . The cost is quadratic in both noise and ambition: halving the smallest change you care about quadruples the calls you need. A query with CV = 1 needs about 390 calls per window to see +20% reliably, and about 1,570 to see +10%.
This is the asymptotic normal approximation for means, and it ignores the search of §7, so it is optimistic for small windows. Treat it as a lower bound when choosing --min-executions and the window length.
- Per-call CV
- Smallest change δ
HonestyWhere the math stopsThe two assumptions that matter most, and what happens when they fail.¶
The t-test and the permutation argument assume the interval means are independent. Serially correlated noise (a slow checkpoint, a cache warming) inflates the false-positive rate, as the selection lab shows with φ > 0.
The mean of an interval hides its composition: a shift in the mix of parameter values looks the same as a slower plan. That is why a detection carries a plan diff and an externalCauseSuspected flag.
ReproducibilityThe page runs the engine's mathNot a re-implementation we hope is close.¶
Every widget above calls one dependency-free module, docs/assets/math/math-core.js, a line-by-line port of internal/correlation/engine.go: the interval means, the minimum segment length, the incremental energy statistic, the Lanczos log-gamma, the incomplete beta continued fraction and the decision order of evaluateQuery. A Go test (internal/correlation/mathpage_test.go) feeds fixed series, generated by that module, to the real engine and fails if the verdict, the change point, , or differ, so the page cannot drift from the code without CI noticing. Noise in the lab is a seeded log-normal per call (so the mean of calls has coefficient of variation ), with optional AR(1) correlation, from a fixed seed so every figure is reproducible.
For the full operational description of each stage, flag and CRD field, see the Detection Algorithm and the Configuration Reference. To run the engine itself on a workload, open the Playground.
References¶
- [1]D. S. Matteson and N. A. James, “A nonparametric approach for multiple change point analysis of multivariate data”, JASA 109(505), 2014. doi:10.1080/01621459.2013.849605
- [2]G. J. Székely and M. L. Rizzo, “Energy statistics: a class of statistics based on distances”, Journal of Statistical Planning and Inference 143(8), 2013.
- [3]B. L. Welch, “The generalization of ‘Student's’ problem when several different population variances are involved”, Biometrika 34, 1947.
- [4]W. H. Press et al., Numerical Recipes, §6.4 “Incomplete beta function” (continued fraction by the modified Lentz method).
- [5]T. Sellke, M. J. Bayarri and J. O. Berger, “Calibration of p values for testing precise null hypotheses”, The American Statistician 55(1), 2001.
- [6]R. Miller and D. Siegmund, “Maximally selected chi square statistics”, Biometrics 38, 1982; B. Lausen and M. Schumacher, “Maximally selected rank statistics”, Biometrics 48, 1992.
- [7]D. Daly et al., “The use of change point detection to identify software performance regressions in a continuous integration system”, ICPE 2020. arXiv:2003.00584
Licensed under Apache 2.0 — see LICENSE.