Two small future-Claude friction points that survived the cleanup pass: 1. SKILL.md never mentioned `tests/test_common.py`. A future Claude tweaking `_common.py` (e.g. nudging a band, adjusting `iso_week`) wouldn't know there's a unittest harness gating the change. Added a short "Editing this skill" section at the bottom of SKILL.md pointing at the harness and stating the binding contract: methodology rubric is the source of truth; if tests fail, fix code or rubric so they agree. 2. `methodology.md` Significance bands table listed 5/15 % for rps and 10/30 % for p99/mem with no calibration source. A reviewer asked "where do these numbers come from?" had nothing to point at. Added one paragraph: PR `#102739` (typos-only, cannot affect Keeper performance) shows ~5 % rps and ~10 % p99 swing under PR-branch isolation; clean = noise floor, watch = 3× noise floor. So if the dataset's noise characteristics change, recalibrate against the next clean-PR control rather than guessing. Doc-only; tests still pass.
6.5 KiB
Methodology — how to compare Keeper-stress data correctly
This file is the deep-dive companion to the SKILL.md workflow. Read it when you need to choose between comparison methods, justify a delta, or explain why a number is below noise.
The two comparison methods
A. Adjacent-nightly comparison (per-PR)
For a specific PR or for narrow attribution:
pre= the master nightly that finished before the PR merged (matched on kind: no-fault for no-fault scenarios, fault for fault scenarios)post= the first master nightly that started after the PR merged (kind-matched)- Δ =
(post - pre) / pre × 100
Use this when:
- The user asks "did PR #X regress anything?"
- You need to localize a delta to a specific commit window
Limitations:
- Single-nightly endpoints carry ±3-5 % run-to-run noise on rps/p99
- If multiple PRs landed in the same nightly window, the Δ is joint across all of them — list them as
co_mergedand never credit one PR alone for the whole movement
B. Median-of-3 window comparison (cumulative)
For "what changed over the past N weeks":
baseline= median over the first 3 no-fault nightlies in the windowcurrent= median over the last 3 no-fault nightlies in the window- Δ =
(median_current - median_baseline) / median_baseline × 100
Use this when:
- The user asks "what's the cumulative effect across many PRs"
- You want to smooth out single-nightly noise
Limitations:
- Cannot attribute to specific PRs at sub-5 % precision
- Bench-harness changes during the window are also captured (use
references/known_confounds.mdto check)
C. PR-branch isolated comparison (cleanest per-PR but limited scenarios)
PR branches each run a 3-scenario smoke stress (prod-mix-no-fault[default], read-multi-no-fault[default], write-multi-no-fault[default]). Their values are systematically offset from master nightlies by ~+12 % because they run on different infra (less-loaded runners, fewer parallel scenarios).
To isolate a single PR's effect:
- Take the PR's branch HEAD value on the 3 scenarios
- Take the median of all OTHER PR-branch runs in the same week (the "pool")
- Δ =
(branch_head - pool_median) / pool_median × 100
Use this when:
- You want a per-PR effect that's free of co-merge contamination
- Master adjacent-nightly comparison is contaminated by other PRs in the window
Limitations:
- Only 3 scenarios available per PR branch
- Pool size depends on how many PRs were active that week
- Sanity-check: PR
#102739(typos) shows ±5 % rps Δ with this method, so anything below ±5 % is below the noise floor
Kind-matched baseline + primary fallback (Method A detail)
In Method A (adjacent-nightly Δ) the baseline is the nightly nearest the PR's
merge time. Some nightlies run only fault-sweep scenarios, others only
no-fault, so a per-PR pre/post pair sometimes can't be drawn from the
matching kind. build_per_pr_metrics_tsv.py resolves this in two steps:
- Kind-matched first. For each row's scenario kind (
faultorno-fault), usepre_fault_sha8/post_fault_sha8(orpre_nofault_sha8/post_nofault_sha8) frompr_to_nightly.tsv. - Primary fallback. When the matching-kind nightly is missing, fall
back to
pre_sha8/post_sha8(the first post-merge nightly regardless of kind). Thebaseline_kindcell inper_pr_metrics_long.tsvreflects this — e.g.fault(fallback-primary-pre)means "scenario is fault, pre-baseline came from the kind-agnostic primary because no fault-only pre-nightly was available".
This is intentional behavior, not a bug: cross-kind comparisons are noisier than same-kind, so prefer kind-matched but don't drop the row when only the primary baseline is usable.
Significance bands (when to call something "real")
| Metric | Direction | clean | watch | regression |
|---|---|---|---|---|
rps |
higher better | Δ ≥ −5 % | Δ −5 % to −15 % | Δ < −15 % |
read_p99_ms, write_p99_ms |
lower better | Δ ≤ +10 % | Δ +10 % to +30 % | Δ > +30 % |
error_pct |
lower better | absolute ΔPP < 0.05 | 0.05 to 0.5 | ≥ 0.5 |
peak_mem_gb |
lower better | Δ ≤ +10 % | +10 % to +30 % | > +30 % |
| Hard-failure counters | absolute | exactly 0 | n/a | any non-zero |
Why these specific numbers. Calibration came from PR #102739 (a typos-only PR that cannot affect Keeper performance): it showed ~5 % rps swing and ~10 % p99 swing under PR-branch isolation. So 5 % is the rps noise floor and 10 % is the p99/mem noise floor; the bands above set "clean" at the noise floor and "watch" at 3× the noise floor. If the dataset's noise characteristics change, recalibrate against the next clean-PR control.
Hard-failure counters are the trump card: any non-zero value of any of these means the cluster broke a guarantee:
KeeperCommitsFailedKeeperSnapshotCreationsFailedKeeperSnapshotApplysFailedKeeperRequestRejectedDueToSoftMemoryLimitCount
Choosing the right method per question type
| User asks… | Use |
|---|---|
| "Did PR #X regress anything?" | Adjacent-nightly (kind-matched) + PR-branch isolated as second source |
| "What changed between dates A and B?" | Median-of-3 window |
| "Validate these N PRs" | Per-PR scenario deltas + summary table; honor co-merge contamination |
| "Why did metric M change on date D?" | Time-series check on D, then cross-reference known_confounds.md |
| "How big is the noise floor?" | Reference #102739 typos PR (~±5 % rps) and large-payload-no-fault rps stddev (CV 0.2 %) |
Always do these checks before quoting a number
-
Memory — if a peak-memory delta is reported, run BOTH
container_memory_bytes(cgroup) ANDKeeperApproximateDataSize(Keeper-state). They can disagree because the cgroup is dominated by page cache. Only the Keeper-reported metric reflects actual Keeper state. If the cgroup moved butKeeperApproximateDataSizedidn't, the delta is NOT a Keeper change. -
CPU —
container_cpu_usage_usecrate can spike to spurious values (18-38 cores on prod-mix scenarios) due to counter discontinuities (container restarts inside the run). Usep95_cpu_cores, nevermax_cpu_cores. -
Step changes — if you see a single-day step in any metric, cross-reference
known_confounds.md. The step is usually a bench-harness change, not a Keeper change. -
Server-side failures — confirm zero across the full window. If non-zero anywhere, that overrides any "ship them all" verdict.
-
Co-merged PRs — when listing per-PR deltas, always include the
co_mergedfield. Don't credit a single PR for a joint window.