Skip to content

MOD-18822 Fail the benchmark job on any KPI floor breach and report it for triage - #1642

Open
LiranAbir wants to merge 12 commits into
masterfrom
MOD-18822-benchmark-gate-consecutive-breach
Open

LiranAbir wants to merge 12 commits into
masterfrom
MOD-18822-benchmark-gate-consecutive-breach

Conversation

@LiranAbir

@LiranAbir LiranAbir commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Policy this implements

A benchmark result under its kpis floor is a result more than 5% below its
baseline (update_kpis.py sets floors at measured × 0.95). For one of those:
the job goes red, a ticket is filed, and the floor is not moved — on the
first occurrence, not on a repeat.

What was wrong

redisbench-admin checks the floors at the end of run-remote and exits non-zero,
but one exit code carries two different findings: a benchmark came in under its
floor, or a benchmark never ran at all. json_nummultby_num_2 had errored on
every run since at least 2026-08-09, so the step was red every night regardless,
and separating a real breach from that meant grepping the log:

"Condition on ... is False. Failing test expectations"  -> breach
"Failed to run remote benchmark for test '...'"        -> MOD-18654

Nothing carried the breaches out of the run either, so filing tickets from them
meant re-reading logs per shard.

What changes

run-remote keeps measuring but no longer owns the verdict
(continue-on-error: true); a new tests/benchmarks/check_kpis.py does:

  • Any breach fails the job, with the shortfall named per benchmark.
  • Breaches are written to kpi-state/findings.json, uploaded as an artifact
    (30 days), and rendered as a table in the run summary — worst shortfall first,
    so the nightly analysis can file from it without opening a log. Nothing is
    filed automatically.
  • A benchmark that produced no result is reported as such, not as a breach, and
    a benchmark with no floor is listed as ungated and does not decide the job.
  • A run where nothing measured at all fails, so handing the verdict over cannot
    hide a broken harness.

No baseline is touched: the 42 kpis values are exactly as #1641 left them, and
update_kpis.py can still only raise a floor, never lower one.

Updated after merging master: #1643 fixed MOD-18654, and
json_nummultby_num_2 produced a result for the first time since 2026-08-09 —
102,553 rps on the push-to-integ run of e1c81941. The gate therefore passes no
--known-broken at all: with that benchmark running, an exemption for it would
be a hole rather than a safety valve. It still has no floor (update_kpis.py
seeds one only from a run that returned a result), so it is reported as ungated
with the reason it now has — no floor seeded yet — and it qualifies for one.

check_kpis.py reuses update_kpis.py's result-file matching and floor parsing
rather than repeating either, and --self-test covers the verdicts, the
findings output and the boundary at exactly-the-floor.

Validation

Replayed against the uploaded result artifacts of the four nightlies of 09-18 →
09-21, per shard and merged:

Night Run merged shard 1 shard 2 shard 3
09-18 35391591250 pass pass pass pass
09-19 35467408910 fail, 8 breaches fail, 1 fail, 7 pass
09-20 35535586550 pass pass pass pass
09-21 35651243916 fail, 12 breaches fail, 4 pass fail, 8

Findings counts match the failures (8 and 12), worst shortfalls -10.9% on 09-19
and -20.4% on 09-21.

Worth knowing what those two nights are: all four ran the identical commit
a3b2edb63, and the harness's own run-to-run spread is 22-49% for the
benchmarks above ~100k rps — wider than the 5% margin the floors sit at. On
09-21 every breached benchmark hit its own four-night minimum, and 24 of the 42
benchmarks did, which is a slow host rather than 12 slow commands. So the
tickets filed off these two nights are the record of that spread, which is
MOD-18823 — the gate cannot get quieter than the measurement it reads.

Jira: MOD-18822 (blocks MOD-18184)

🤖 Generated with Claude Code


Note

Low Risk
Changes are limited to CI workflows and a new benchmark gate script; no runtime RedisJSON or auth/data-path changes.

Overview
Benchmark CI no longer treats redisbench-admin run-remote’s exit code as the gate: that step runs with continue-on-error, logs to run-remote.log (bash + pipefail), and a new Check KPI floors step runs tests/benchmarks/check_kpis.py, which fails the job on floor breaches, benchmarks that did not run (from the log), unreadable results, or an unexplained non-success run outcome. Findings go to kpi-state/findings.json, a job summary table, and a benchmark-kpi-findings-* artifact for triage.

test-summary.yml merges shard findings into the workflow summary and adds a Benchmark KPI floors section to nightly Slack messages. json_nummultby_num_2 is no longer exempt (MOD-18654 fixed); benchmarks without a seeded floor are reported as ungated and do not fail the job.

Reviewed by Cursor Bugbot for commit 9677bbf. Bugbot is set up for automated code reviews on this repo. Configure here.

redisbench-admin judges each `kpis` floor on a single sample, and the
harness's own run-to-run spread is 22-49% above ~100k rps against floors
set 5% low. Four consecutive nightlies on the identical commit a3b2edb
recorded 20 breaches between them, so the gate currently fails quiet
nights and would pass a real slowdown.

Move the verdict into the repo: run-remote keeps measuring but no longer
decides (continue-on-error), and check_kpis.py compares each result
against its floor and fails only when the previous run saw the same
benchmark breach. The verdict is carried between runs in the Actions
cache, keyed by ref, shard and run id, so a PR run cannot inherit
master's breaches and each of the three shards judges its own subset.

Replaying the four nights through the new gate reproduces the 8 and 12
breaches on 09-19 and 09-21 and exits 0 on all four, per shard and
merged; a 30% slowdown injected into two consecutive nights is reported
on the first and fails the job on the second.

json_nummultby_num_2 (MOD-18654, Won't Do) has no floor, so it is
listed as ungated rather than failed -- otherwise the job stays red
every night and its status still means nothing. A run where no
benchmark produced a result does fail, so continue-on-error cannot hide
a broken harness.

This is a stopgap for the gate, not for the measurement: a regression
smaller than the harness's spread stays invisible. Narrowing that spread
is MOD-18823; per-benchmark margins should follow from its findings.
@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.97%. Comparing base (e1c8194) to head (9677bbf).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1642   +/-   ##
=======================================
  Coverage   85.97%   85.97%           
=======================================
  Files          15       15           
  Lines        5306     5306           
=======================================
  Hits         4562     4562           
  Misses        744      744           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

A benchmark under its floor is under a baseline already set 5% below a
measured value, so any breach is worth a ticket -- whether or not it
repeats the next night. The previous commit reported a first-time breach
only as a log line, which is too easy to lose.

Findings now leave the run in a form the nightly analysis can triage
from: kpi-state/findings.json, uploaded as an artifact, and a table in
the run summary listing every breach worst-shortfall-first. Nothing
files a ticket automatically; that decision stays with the nightly
analysis.

Job status is unchanged and still fails only on a breach the previous
run saw too. Failing on a single one would put the job back to red on
roughly half the nights -- 20 breaches in four nights of identical code
-- which is what MOD-18822 is about.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread .github/workflows/benchmark-flow.yml Outdated
A result under its floor is a result more than 5% below the baseline,
and the policy for one is red job, ticket filed, floor left alone --
first occurrence, not on the second. So the two-consecutive-runs rule
and all the state it needed are gone: no Actions cache, no previous
verdict to restore or save, and the check's exit code now follows
directly from this run's results.

What stays is the part that makes a breach actionable. redisbench-admin
fails the step for a floor breach and for a benchmark that never ran
with the same exit code, so telling them apart meant grepping the log;
the check names each one, writes the breaches to kpi-state/findings.json
with their shortfall for the nightly analysis to file from, and puts the
same table in the run summary. A run where nothing measured at all still
fails, so handing it the verdict cannot hide a broken harness, and
json_nummultby_num_2 (MOD-18654, Won't Do, no floor) is reported as
ungated rather than deciding the job.

Replaying the four nights of 09-18..09-21: 09-19 and 09-21 fail with 8
and 12 breaches, 09-18 and 09-20 pass, per shard and merged. Those two
nights ran the same commit as the two that passed, so the tickets filed
from them are about the harness's own 22-49% spread -- MOD-18823 -- which
is the thing that has to shrink for this gate to be quiet.
@LiranAbir LiranAbir changed the title MOD-18822 Fail the benchmark gate only on a breach seen twice MOD-18822 Fail the benchmark job on any KPI floor breach and report it for triage Sep 22, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread tests/benchmarks/check_kpis.py Outdated
Comment thread tests/benchmarks/check_kpis.py
@LiranAbir
LiranAbir requested a review from gabsow September 22, 2026 12:12
Since run-remote continues on error, an errored benchmark left no trace:
its result file is simply absent, and absent is indistinguishable from
"this shard was never given that benchmark", so a timeout or a single
test error after some successes left the job green. Before this PR
run-remote's own exit code covered that case.

The run log is the only place that records a benchmark was meant to run
and did not, so capture it (tee) and read the
"Failed to run remote benchmark for test '<name>'" lines from it. Each
one is a finding and fails the job, except the names passed as
--known-broken -- json_nummultby_num_2 (MOD-18654, Won't Do) and nothing
else.

Reported by Cursor Bugbot on the PR.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread tests/benchmarks/check_kpis.py Outdated
Comment thread .github/workflows/benchmark-flow.yml
Two more holes of the same shape as the last one: with run-remote under
continue-on-error, anything that leaves a benchmark merely absent read
as "this shard was never given it" and passed.

measured() discarded the reason result_metric returns when a result file
exists but cannot be used -- ambiguous duplicates, or no known
throughput metric in it. That benchmark looked unmeasured and its floor
was never applied; it is now reported unreadable and fails the job.

A step timeout or a crash partway through leaves no log line at all for
the benchmarks that never started, so the log cannot catch it. The
gate now also takes run-remote's step outcome: a non-success that
nothing in the log accounts for -- no breach, no failed-to-run line, not
even a known-broken one -- fails the job as an incomplete run. A timeout
on a night when a known-broken benchmark also errored still reads as
accounted-for; closing that needs each shard's expected benchmark list,
which redisbench-admin does not expose (neither i % 3 nor contiguous
thirds of the sorted yml list reproduces the real 14/13/15 split).

Both reported by Cursor Bugbot. Four-night replay unchanged.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread .github/workflows/benchmark-flow.yml
The benchmark gate writes its table on each of the three shard jobs,
which is not where anyone looks. The nightly summary is, and it carried
only the job name: "benchmark - job failed", with no indication of which
benchmark or by how much.

So the summary job now restates the breaches, in the same table the
benchmark job renders, and sends them to Slack as one section of their
own -- a floor breach is a performance finding, not a failed test, so it
does not belong folded into the failed-OS list. No new download: the
existing step already pulls every artifact of the run, kpi findings
included. The three shards each report the same ungated benchmark, hence
unique_by.

Rendered against last night's run (35780480546), the Slack section reads:

  *Benchmark KPI floors:*
  • *json_set_key_empty* - 134217 vs floor 163624 (-18%)
  • *json_vs_hashes_hset_key_simple* - 136977 vs floor 165889 (-17.4%)
  • *json_set_sclr_pass_100_json* - 116244 vs floor 129489 (-10.2%)
  • *json_vs_hashes_json.set_key_simple* - 133769 vs floor 135841 (-1.5%)

The block is built with jq rather than interpolated into the payload
format string, so a benchmark name or a percentage cannot break it, and
the step output is passed in as an env var rather than expanded inside
the script.
A bare `run:` gets `bash -e {0}`, without pipefail -- confirmed in the
job log of run 35780480546, `shell: /usr/bin/bash -e {0}`. So piping
run-remote into tee reported tee's exit code instead: the step would
have been `success` after any failure, and the --run-outcome guard added
in the previous commit could never fire.

`shell: bash` gives `bash --noprofile --norc -eo pipefail {0}`, so the
non-zero survives the pipe, the step outcome is truthful again, and an
incomplete run is still caught.

Reported by Cursor Bugbot.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread .github/workflows/test-summary.yml
Brings in #1643 (MOD-18654), which bounds json_nummultby_num_2 so the
benchmark runs again -- the premise this branch was written against.
#1643 bounds that benchmark's multiplier, and it produced a result for
the first time since at least 2026-08-09: 102,553 rps on the push-to-integ
run of e1c8194. So --known-broken json_nummultby_num_2 is no longer a
safety valve, it is a hole -- if that benchmark stops running again the
gate would wave it through. The workflow passes no --known-broken at all
now; the flag stays, unused, for the next benchmark that breaks for good.

It still has no `kpis` floor, since update_kpis.py seeds one only from a
run that returned a result and none had. So it is still reported as
ungated, and the wording follows the reason: "no floor seeded yet"
rather than MOD-18654. Seeding one is a update_kpis.py run and a PR,
which it now qualifies for.

Four-night replay unchanged with no exemption passed (8 and 12 breaches
on 09-19 and 09-21). On the 09-23 integ run the gate reports it as
"UNGATED: json_nummultby_num_2 (no floor)" -- measured, unfloored --
alongside 7 breaches, which are the sustained step-change from 09-21
(MOD-18823).
The summary only branched on the breach count, so a run whose findings
held an errored or unreadable benchmark and no breach announced that
every gated benchmark met its floor and sent Slack a green check --
while the shard job that produced it was red. The rows were emitted
after that line without a table header, so they did not render either.
Same bug for a night with only an ungated row, which is the common case.

The table header now depends on there being any row at all, the rows go
out in severity order, and the verdict line and the Slack bullets depend
on breach *or* errored/unreadable, so a green check means all three are
clear. Checked against three inputs: last night's real findings
(4 breaches + 1 ungated), an ungated-only night, and a synthetic
errored + unreadable run with no breach.

Reported by Cursor Bugbot.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread .github/workflows/test-summary.yml Outdated
Comment thread tests/benchmarks/check_kpis.py
Two findings on the previous commit, both about the same blind spot: an
ungated benchmark is reported by every shard, but only the shard that
was given it has a number.

The summary merged those copies with unique_by, which keeps whichever
came first, so the nightly table and the Slack block could say "no
result" for a benchmark another shard had timed. It now dedupes on
(benchmark, status) preferring the copy with a measured value. This
only became visible with #1643: before it, json_nummultby_num_2 had no
result on any shard.

And verdict() classified an unusable result before checking for a
floor, so a floor-less benchmark with an unreadable result produced an
UNREADABLE row with floor=None, which the summary table formats as a
number -- TypeError, after findings.json was already written. Ungated is
now decided first: with no floor there is nothing for an unusable result
to be measured against. Covered in --self-test, which also renders the
step summary for that row rather than only checking the verdict.

Four-night replay unchanged. Reported by Cursor Bugbot.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread tests/benchmarks/check_kpis.py Outdated
Comment thread tests/benchmarks/check_kpis.py
Both of these are the asymmetry the previous commit fixed in the nightly
summary and left in the gate's own table.

An ungated benchmark is listed but is not a finding, and step_summary
appended the file-a-ticket footer whenever the list was non-empty -- and
with a floor-less benchmark in the set it is never empty. So a clean run
told the reader to file a ticket, which on push-to-integ is the only KPI
report they see. The footer now follows the rows that need a ticket, and
an otherwise clean run says its floors were met.

And a benchmark that failed to run was reported twice when it also had
no floor: once as errored from the run log, once as ungated from the
verdict, both asking for a ticket, and the nightly merge groups by
(benchmark, status) so both survived. The errored row is the actionable
one and now the only one.

Rendered for both cases: the 09-18 artifacts produce the ungated row
plus "Every gated benchmark met its floor", 09-21 produces the 12
breaches plus the footer. Reported by Cursor Bugbot.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9677bbf. Configure here.

jq -s 'add
| group_by([.benchmark, .status])
| map(sort_by(.measured == null)[0])
| sort_by(-(.shortfall_pct // 0))' $FINDINGS > kpi-findings.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge duplicates errored ungated benchmarks

Medium Severity

The nightly KPI merge dedupes on (benchmark, status), so a floor-less benchmark that failed to run still appears twice: errored from the shard that ran it, and ungated / no result from the other shards. The summary table and the “each row is worth a ticket” footer then treat both as separate findings, which is the report nightly triage files from.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9677bbf. Configure here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant