The A/B stack's Loki (test/e2e-compat/loki-local-config.yaml) runs with
align_queries_with_step on and max_cache_freshness_per_query 0s; Loki's
defaults are off and 10m (v3.7.9 queryrangebase/roundtrip.go:49,
validation/limits.go:407-408). Next to it a proxy build that does not align
queries with the step answers a refresh that Loki serves from its results
cache in 3-5 ms, and a confirm pass that re-ran windows Loki had cached
printed a cache-served Loki cold as a plain number and a ratio.
Every pass now runs against one of two profiles on the one seeded stack:
"aligned" (the stack as it is; proxy builds with -align-queries-with-step=true)
and "loki-defaults" (docker-compose.ab-loki-defaults.yml: the two Loki flags
back at Loki's defaults, applied to the same data by recreating Loki; proxy
builds on their defaults). The shared e2e Loki config is untouched.
- A build without the flag (stack.probe_align_support on the binary) runs
without it and its aligned rows are marked not like-for-like: shown, never
compared (verdict, PR / Loki, gate, history, ratchet, evidence). A build whose
own stack flags carry the flag has it removed in loki-defaults.
- pr_smoke --profiles auto runs loki-defaults for the selection and aligned for
its range-metric shapes when a build supports the flag; both, aligned and
loki-defaults choose otherwise. The seeded window's end is put ahead of now
(anchor_lead) so Loki's 10-minute freshness horizon applies to the
loki-defaults windows.
- A row is above 2x Loki when it is in either profile; the comment, the
annotations and verdict.json give both ratios. The baseline
bench/ab/history/over-2x-loki.json, the registry bounds' gates (locked rows
judged in each profile), the band distribution, history/acceptance.json, the
daily results and history files and the conformance evidence carry the
profile; the 165 listed rows are migrated as aligned, proxy_side unaligned.
- A Loki cold enters a ratio only when Loki computed it. A cache-served cold is
marked and left out of every ratio, in the comment, the trend and the
evidence; the confirm pass keeps the first pass's cold columns whenever its
own Loki cold was cache-served.
Tests: bench/ab/tests (Python unittest, as the rest of the harness).
What
The A/B harness now compares a proxy build with Loki like for like. No product code, no CHANGELOG entry, no release (
check_changelog_pr.py: "skipped, no releasable changes").The stack's Loki (
test/e2e-compat/loki-local-config.yaml) hasalign_queries_with_step: trueandmax_cache_freshness_per_query: 0s. Loki's defaults are off and 10m (v3.7.9queryrangebase/roundtrip.go:49,validation/limits.go:407-408). Next to that Loki a build that does not align queries answered against exact results-cache hits (3-5 ms), and a confirm pass that re-ran windows Loki had already cached printed its cache-served cold as a plain number and put it in a ratio.Every pass now runs against one of two profiles on the one seeded stack, and every comment row, gate finding, history line and evidence record names its profile:
aligned-align-queries-with-step=trueloki-defaultsbench/ab/docker-compose.ab-loki-defaults.yml:-querier.align-querier-with-step=false -frontend.max-cache-freshness=10m(flags win over the config file, as in the uncached overlay). The shared e2e config is not touchedstack.probe_align_support:proxy -align-queries-with-step=true -helpstops at the flag for a build without it, at-helpfor one with it; anything else counts as unsupported). A build without the flag runs without it inaligned;meta.jsonand the comment say so and its rows are marked‡: shown, never compared (no verdict, PR ÷ Loki, gate, ratchet, above-2× row, evidence). A build whose own stack flags carry the flag (once the flag branch merges every compose proxy does) has it removed inloki-defaults.stack.set_profilerecreates only Loki on the same volume, checks/configshows the profile's two values, and waits until Loki and VictoriaLogs count the same lines again. Builds restart per profile with their own work dir and log; each profile has its own block of windows.--profiles auto(CI):loki-defaultsfor the whole selection, plusalignedfor its range-metric shapes (the only queries the flag changes) when a build supports the flag.both,aligned,loki-defaultschoose otherwise.::warning::annotations andverdict.jsongive both PR ÷ Loki ratios (not runfor a profile that did not run). The warn-only switch (--over-2x-loki) is unchanged.over-2x-loki.jsonkeys on the profile; its 165 rows are migrated asaligned,proxy_side: unaligned;loki-defaultsstarts empty (seed it from its first daily run, with your approval).perf:bounds of ci: lock the performance acceptance tiers in the registry and the regression gates #893) are judged against their bound in each profile, as are the band distribution, its ratchet,history/acceptance.json(nowdays[].profiles.<profile>), the daily results (daily-<set>.jsonaligned,daily-<set>.loki-defaults.json), the history files (<set>.jsonlaligned and every older line,<set>.loki-defaults.jsonl),perf_evidence.py(prefersloki-defaultsof the same date) and the baseline-run candidate files (ci: a CI-only pull request runs only the jobs that verify what it changes; a big change runs a full baseline #895).†and in no ratio (comment, trend, evidence). The confirm pass keeps the first pass's cold columns, with their marks, whenever its own Loki cold was cache-served (cold_from_first_pass,¹), for every re-measured row, not only the above-2× ones.perf_matrix.plan_windows): the pass that models a dashboard refresh at now is the nearest to now. Before, it sat below the first pass, inside the extents the first pass had put in Loki's cache, so Loki's "refresh" was mostly a cache hit by construction (refresh is off in CI and has no row in the baseline list). Raw rows carry the request time (at);meta.jsonrecords per profile the youngest and oldest age of a requested window (window_age_s).loki-defaultsfirst (the Loki the seed wrote to, as the history always had), andalignedsecond; its lines carryloki_restartedand take no part in the cold ratchet. Every range-metric shapealignedruns also runs, cold included, inloki-defaults.Validation (isolated stack,
DOCKER_CONTEXT=colima-lvp, projectablike, ports 24101/24428/24190)pr_smoke.py --base origin/main --head origin/main --ranges 1h,6h --refresh-s 60 --profiles both, selection: the 8 control smoke shapes +unwrap-conversion-error. 483 s.meta.json:align_flag_supported{base: false, pr: false}, so the wholealignedprofile is‡/ "not like-for-like" (32 rowsn/a), the 32loki-defaultsrows aresame, 0 new differences, 24 pre-existing (as on the base), 0 new above-2× rows./configread back: alignedtrue/0s, loki-defaultsfalse/10m.Loki p50 (ms), refresh pass, control set 1h (the first block, windows 2-3 minutes old when requested,
loki-defaultswindow ages 165-1719 s):alignedloki-defaultsloki-defaultsThe exact-hit 3-5 ms Loki refresh figures are gone in both profiles (they came from the refresh windows sitting inside the first pass's cached extents). Lower blocks of a selection are traversed after their windows are older than 10 minutes, where Loki's freshness horizon no longer applies (unwrap set: 17-138 ms in
loki-defaults); the README says so under "Like for like".Cache-served cold, a real run (before the profile order was flipped, same confirm code): the confirm pass of
E2 json duration_ms p95measured Loki's cold at 3 ms, counterscold_cache_served: [loki]. Before: printed as "Loki 3 ms", PR ÷ Loki cold 32× / 150×. Now the row keeps the first pass's computed cold (128 ms, 208 ms) with¹, PR ÷ Loki 0.77× / 2.17×.†itself (a first-pass cache-served cold) is covered by unit tests only; no real run produced one.A build that supports the flag, same stack (
--head perf/align-queries-with-step, 12322ed3):proxy_align: {base: unsupported, pr: on},not_like_for_like: [base], PR vs Loki compared, the base marked‡.Daily, real run with writes (
daily.py --sets control --max-range 1h --runs 3 --profiles both, then the generated files reverted):daily-control.json,daily-control.loki-defaults.json,control.jsonl+control.loki-defaults.jsonllines withprofile,like_for_like; trend sections per profile; alerts ran; 2.0 min.CI minutes (local: a laptop with a warm Go build cache; control smoke +
unwrap-conversion-error, 1h, 4 runs, no refresh)loki-defaultsonlyloki-defaults+alignedsubsetToday: no extra cost (the single pass is
loki-defaults; the probe is a 0.1 s-help). Once a build supports the flag: about +70 s here (recreate 44 s with the count check, 3 s of proxy starts, the range-metric subset's matrix); the 190 s also holds the build of the older branch used as the flag-supporting build. On a 17-minute hosted-runner run: about +10-20% (not measured on a runner). The seed does not grow.timeout-minutes: 30has room. Daily: the second pass roughly adds the range-metric part of the matrix and one recreate with a 24h count check; its current duration was not available, compare the first two-profile run withtimeout-minutes: 240(until a build supports the flag,autoskipsalignedand it costs nothing extra).Tests
Python
unittestinbench/ab/tests(wired inci.yamlbydiscover), 340 tests, all green;check_conformance.pyok;selection.py --checkok; actionlint, shellcheck onvalidate_compose.sh. They cover: the overlay against the base command (drift),/configparsing by section,set_profile, the flag probe against fake binaries,align_argsfor every spelling of the flag,start_proxiesstates, profile planning and window blocks,measureanddaily.mainwith both profiles (order, restarts, unlike lists, files), the confirm-cold rule, the profile-keyed gate, baseline migration, either-profile merge with both ratios, locked rows per profile, history files/ratchet/over-2x/distribution/acceptance per profile,loki_restarted,like_for_like, report and comment marks, the baseline-candidate files. Deviation to decide: the owner rule says new tests are Go; these test Python harness code, I found no Go test that runs the Python harness, so they follow the existing pattern (#876, #889, #893).Known limits
endare older than the horizon when requested. Documented, with the recorded window ages.-align-queries-with-stepthe wholealignedprofile is not run inauto(andalignedhistory lines stop;last_committakes the newest line of either profile).ab-tree-*directories in$TMPDIRdated Oct 25/26 are not from this change.