Skip to content

fix: reject group(), label_join() and other names Loki does not have, and accept keywords in any case, like Loki - #748

Draft
szibis wants to merge 5 commits into
mainfrom
fix/group-aggregation-not-in-loki
Draft

szibis wants to merge 5 commits into
mainfrom
fix/group-aggregation-not-in-loki

Conversation

@szibis

@szibis szibis commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

This adds a new gap row, semantics/proxy-accepts-operators-not-in-loki. The project rule is that the proxy accepts exactly the LogQL Loki accepts.

What Loki does. Loki 3.7.7 turns a name into a function token only if it appears in functionTokens (pkg/logql/syntax/lex.go:95-137). Any other name is an IDENTIFIER the grammar cannot place. Loki then answers parse error at line 1, col N: syntax error: unexpected IDENTIFIER.

What the proxy did.

  • It kept every unknown name(...) as an opaque expression, and implemented group(...) and label_join(...) as extensions.
  • It answered 200 for group, label_join, abs, sgn, clamp*, round, ceil, floor, sqrt, exp, ln, log2, log10, sin, cos, scalar, timestamp, hour, quantile, present_over_time, increase, delta, irate, absent and histogram_quantile.
  • For time(), day_of_week() and count_values() it answered 400 with its own text.
  • Loki's lexer also lower-cases keywords (lex.go:226-252), so TOPK(3, RATE(...)), SUM BY, ON, BOOL and | JSON | DROP are valid in Loki. The proxy rejected them or sent different backend queries; GROUP(...) even answered a 200 streams result.

Fix.

  • The scanner reads keywords in any case. A label named Rate or Count stays a label.
  • The parser keeps only label_replace, vector and approx_topk opaque. Any other name gets Loki's exact error text and column, before any backend call.
  • A request middleware lower-cases keywords in the query once, so every handler and the multi-tenant fan-out see Loki's spelling.
  • The group() and label_join() translation and response rewriting are removed: about 340 lines.

Tests

  • TestValidateLogQL_NamesLokiDoesNotKnowAreRejected and TestValidateLogQL_KeywordsAreCaseInsensitive. Their expected texts come from running Loki's own syntax.ParseExpr, not from hand-typing. 77 subtests failed before the fix.
  • TestLowercaseKeywords.
  • TestUnknownLogQLNamesRejectedLikeLoki: 11 queries on /query and /query_range through the mux. It checks a 400 with Loki's text and no backend call.
  • TestUppercaseKeywordsSendTheLowercaseBackendRequests: GET, form POST and multi-tenant.
  • e2e vs Loki: TestCompat_OperatorsNotInLokiRejected and TestCompat_UppercaseKeywordsMatchLoki.

Behaviour change

A selector label spelled like a keyword in any case ({By="x"}, {On="x"}) is now rejected, as Loki does.

Merge order

Merge after #735, #744 and #741. Those PRs still carry label_join and group handling, and this PR removes it when it is rebased.

Pending before ready

  • Run the e2e against Loki on VictoriaLogs v1.52.0, and re-run TestLogQL_Exhaustive_*.
  • benchstat for LowercaseKeywords, which runs on every routed request whose query has an uppercase letter.
  • The control-set A/B and the load test.
  • Register the new gap drafts:
    • approx_topk is answered as a log query, while Loki evaluates it;
    • parse-text differences for -group, keyword-named selector labels, unwrap in a log query, sum({...}), and avg_over_time without unwrap.
  • Recheck semantics/count-values-unsupported: Loki 3.7.7 has no count_values, so that row's premise is false.

… accept keywords in any case

Loki 3.7.7 has no group aggregation and no label_join: its lexer makes a function token only of the names in functionTokens, so any other name is an IDENTIFIER the grammar cannot place and the query is a 400 parse error at the column of the name. The proxy kept every unknown name(...) as an opaque call and answered it. The parser now keeps opaque only label_replace, vector and approx_topk and reports every other name the way Loki does, also inside a label_replace argument.

Loki lower-cases a word before it looks it up, so TOPK(3, X), SUM BY (a) (X), RATE(...) and OFFSET are the keywords they spell, while a label called Rate or Count stays a label. The scanner reads keywords in any case and the query parameter is written with lower-case keywords before a handler reads it.
…e rewriting

Loki has neither, and the validator now rejects both before the translator runs, so the marker suffixes, their parsers and the proxy-side rewrites of a matrix response were unreachable.
… and document the change

The e2e suite runs the queries Loki rejects against Loki and every Loki-compatible proxy on both query endpoints and compares status and text, and runs uppercase keyword queries and compares the series. group() and label_join() move from the parity and known-gap lists to the error parity list.
…pare the proxy with itself in the e2e

The multi-tenant fan-out re-reads the query from the URL, so the lower-case rewrite covers the URL query as well as the parsed form. The proxy tests now send each uppercase query as a GET, a form-encoded POST and a multi-tenant GET. The e2e compares the upper-case answer of the proxy with its own lower-case answer; Loki only has to answer 200 for both spellings.
@github-actions github-actions Bot added size/XL Extra large change scope/translator LogQL translator scope/proxy Proxy core scope/docs Documentation scope/tests Tests bugfix Bug fix and removed size/XL Extra large change labels Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

⚪ Performance A/B: no change beyond noise

125 shape×range — 125 ⚪ same · results vs Loki: 105 same, 0 new difference(s), 20 pre-existing

Every shape is within noise of the base build (±30% and 100 ms).

125 unchanged shape×range (within noise)
shape range base p50 PR p50 change Loki p50 PR ÷ Loki result vs Loki
⚪ bytes_over_time by app 1h 67 ms 72 ms ⚪ +7% 91 ms 0.79× ✅ same
⚪ drilldown service landing 1h 28 ms 32 ms ⚪ +14% 25 ms 1.28× ✅ same
⚪ instant json filtered instant 346 ms 346 ms ⚪ +0% 75 ms 4.61× ✅ same
⚪ instant json volume instant 392 ms 382 ms ⚪ -3% 95 ms 4.02× ✅ same
⚪ instant ungrouped rate instant 5 ms 5 ms ⚪ +0% 74 ms 0.07× ✅ same
⚪ json rate by level (no filter, no drop) 1h 400 400 ⚪ +0% 400 — ✅ same
⚪ json volume no filters 1h 178 ms 178 ms ⚪ +0% 3 ms 59× ✅ same
⚪ logfmt count by level, filter, drop 1h 400 ms 406 ms ⚪ +2% 55 ms 7.38× ✅ same
⚪ logfmt volume 1h 279 ms 289 ms ⚪ +4% 60 ms 4.82× ✅ same
⚪ logs plain 1h 56 ms 53 ms ⚪ -5% 37 ms 1.43× ✅ same
⚪ logs | json 1h 78 ms 73 ms ⚪ -6% 58 ms 1.26× ✅ same
⚪ logs | logfmt 1h 125 ms 127 ms ⚪ +2% 47 ms 2.70× ✅ same
⚪ stream-label count by app 1h 29 ms 24 ms ⚪ -17% 39 ms 0.62× ✅ same
⚪ topk rate by app 1h 43 ms 43 ms ⚪ +0% 88 ms 0.49× ➖ differs (as base)
⚪ ungrouped rate 1h 11 ms 11 ms ⚪ +0% 90 ms 0.12× ✅ same
⚪ DB1 label breakdown pod 1h 85 ms 101 ms ⚪ +19% 121 ms 0.83× ✅ same
⚪ DB2 label breakdown namespace 1h 17 ms 19 ms ⚪ +12% 37 ms 0.51× ✅ same
⚪ DB3 json field breakdown user_id 1h 56 ms 48 ms ⚪ -14% 26 ms 1.85× ✅ same
⚪ DB4 json field breakdown batch_id 1h 97 ms 94 ms ⚪ -3% 62 ms 1.52× ✅ same
⚪ X1 json filter level_extracted, by level 1h 324 ms 331 ms ⚪ +2% 42 ms 7.88× ✅ same
⚪ X2 json group by level_extracted 1h 273 ms 273 ms ⚪ +0% 44 ms 6.20× ✅ same
⚪ X3 json rate, level_extracted equality 1h 248 ms 251 ms ⚪ +1% 35 ms 7.17× ✅ same
⚪ X4 json bytes, level_extracted regexp 1h 341 ms 341 ms ⚪ +0% 92 ms 3.71× ✅ same
⚪ X5 logfmt filter level_extracted, by level 1h 612 ms 610 ms ⚪ -0% 51 ms 12× ✅ same
⚪ E1 logs, json parser over stream labels 1h 103 ms 101 ms ⚪ -2% 59 ms 1.71× ✅ same
⚪ E2 logs, logfmt parser over stream labels 1h 60 ms 63 ms ⚪ +5% 40 ms 1.57× ✅ same
⚪ E3 logs, json, existence filter on level_extracted 1h 129 ms 139 ms ⚪ +8% 73 ms 1.90× ✅ same
⚪ E4 logs, logfmt, existence filter on level_extracted 1h 75 ms 82 ms ⚪ +9% 54 ms 1.52× ✅ same
⚪ E5 logs, json, drop level_extracted 1h 101 ms 99 ms ⚪ -2% 59 ms 1.68× ✅ same
⚪ E6 logfmt count by level_extracted 1h 40 ms 43 ms ⚪ +7% 7 ms 6.14× ✅ same
⚪ E7 json count by level_extracted 1h 130 ms 127 ms ⚪ -2% 14 ms 9.07× ✅ same
⚪ E8 instant logfmt by level_extracted instant 8 ms 6 ms ⚪ -25% 10 ms 0.60× ✅ same
⚪ A explore volume, 2 filters, drop 1h 243 ms 186 ms ⚪ -23% 154 ms 1.21× ✅ same
⚪ B grouped sum, filter, no drop 1h 104 ms 102 ms ⚪ -2% 99 ms 1.03× ✅ same
⚪ C ungrouped sum, filter, no drop 1h 98 ms 97 ms ⚪ -1% 98 ms 0.99× ✅ same
⚪ D volume, no filters 1h 193 ms 201 ms ⚪ +4% 119 ms 1.69× ✅ same
⚪ E field breakdown pipeline (explore) 1h 100 ms 99 ms ⚪ -1% 37 ms 2.68× ✅ same
⚪ F field breakdown service_version (explore) 1h 137 ms 133 ms ⚪ -3% 43 ms 3.09× ✅ same
⚪ G field breakdown pipeline (drilldown) 1h 46 ms 44 ms ⚪ -4% 24 ms 1.83× ✅ same
⚪ H field breakdown service_version (drilldown) 1h 76 ms 78 ms ⚪ +3% 27 ms 2.89× ✅ same
⚪ I grafana volume of B 1h 103 ms 100 ms ⚪ -3% 36 ms 2.78× ✅ same
⚪ O1 explore logs, underscore structured-metadata filter 1h 316 ms 317 ms ⚪ +0% 352 ms 0.90× ✅ same
⚪ O2 explore log volume (Grafana form), underscore structured-metadata filter 1h 67 ms 62 ms ⚪ -7% 36 ms 1.72× ✅ same
⚪ O3 explore logs, dotted structured-metadata filter 1h 400 400 ⚪ +0% 400 — ✅ same
⚪ O4 explore log volume (Grafana form), dotted structured-metadata filter 1h 400 400 ⚪ +0% 400 — ✅ same
⚪ drilldown detected fields 1h 24 ms 22 ms ⚪ -8% 17 ms 1.29× ➖ differs (as base)
⚪ drilldown detected labels 1h 18 ms 19 ms ⚪ +6% 3 ms 6.33× ➖ differs (as base)
⚪ drilldown landing volume 1h 53 ms 55 ms ⚪ +4% 23 ms 2.39× ➖ differs (as base)
⚪ explore label values env 1h 9 ms 11 ms ⚪ +22% 11 ms 1.00× ✅ same
⚪ explore label values service_name 1h 5 ms 5 ms ⚪ +0% 15 ms 0.33× ✅ same
⚪ explore labels (label browser) 1h 6 ms 9 ms ⚪ +50% 3 ms 3.00× ✅ same
⚪ explore labels with selector 1h 7 ms 7 ms ⚪ +0% 13 ms 0.54× ✅ same
⚪ refresh label values env 1h 21 ms 21 ms ⚪ +0% 10 ms 2.10× ✅ same
⚪ refresh label values service_name 1h 10 ms 13 ms ⚪ +30% 14 ms 0.93× ✅ same
⚪ refresh labels (label browser) 1h 13 ms 12 ms ⚪ -8% 3 ms 4.00× ✅ same
⚪ refresh labels with selector 1h 13 ms 16 ms ⚪ +23% 13 ms 1.23× ✅ same
⚪ DL1 drilldown field breakdown json-expr, level filter 1h 458 ms 464 ms ⚪ +1% 23 ms 20× ✅ same
⚪ DL2 drilldown field breakdown, level + stream-label filter before json 1h 61 ms 47 ms ⚪ -23% 8 ms 5.88× ✅ same
⚪ DL3 drilldown logfmt field breakdown, level filter 1h 573 ms 558 ms ⚪ -3% 107 ms 5.21× ➖ differs (as base)
⚪ DL4 instant count, level filter, json+logfmt, 1h instant 475 ms 481 ms ⚪ +1% 1.31 s 0.37× ✅ sum ±0.26%
⚪ DL6 explore volume, level filter before json 1h 474 ms 492 ms ⚪ +4% 40 ms 12× ✅ sum ±0.26%
⚪ OR1 logs, or alternatives 1h 66 ms 61 ms ⚪ -8% 63 ms 0.97× ✅ same
⚪ OR2 logs, negated or alternatives 1h 64 ms 67 ms ⚪ +5% 40 ms 1.68× ✅ same
⚪ OR3 logs, regexp or alternatives 1h 50 ms 51 ms ⚪ +2% 62 ms 0.82× ✅ same
⚪ OR4 count by level, or alternatives with parser 1h 76 ms 77 ms ⚪ +1% 31 ms 2.48× ✅ same
⚪ OR5 rate, negated regexp or alternatives 1h 53 ms 57 ms ⚪ +8% 85 ms 0.67× ✅ same
⚪ OR6 instant, or alternatives instant 7 ms 8 ms ⚪ +14% 63 ms 0.13× ✅ same
⚪ PR1 count by level, parenthesised json 1h 165 ms 164 ms ⚪ -1% 44 ms 3.73× ✅ same
⚪ PR2 rate, parenthesised line filter 1h 46 ms 47 ms ⚪ +2% 65 ms 0.72× ✅ same
⚪ PR3 bytes by app, parenthesised regexp 1h 51 ms 50 ms ⚪ -2% 70 ms 0.71× ✅ same
⚪ PR4 count by level, parenthesised parser, offset 1h 181 ms 182 ms ⚪ +1% 48 ms 3.79× ✅ same
⚪ PR5 rate by app, pipeline after the range 1h 196 ms 196 ms ⚪ +0% 110 ms 1.78× ✅ same
⚪ PR6 doubly parenthesised range 1h 48 ms 49 ms ⚪ +2% 65 ms 0.75× ✅ same
⚪ PR7 instant, parenthesised line filter instant 7 ms 6 ms ⚪ -14% 61 ms 0.10× ✅ same
⚪ P1 logs, json, level equals 1h 119 ms 131 ms ⚪ +10% 73 ms 1.79× ✅ same
⚪ P2 logs, logfmt, level not equal 1h 27 ms 27 ms ⚪ +0% 5 ms 5.40× ✅ same
⚪ P3 logs, json, level existence 1h 108 ms 106 ms ⚪ -2% 63 ms 1.68× ✅ same
⚪ P4 logfmt count by level 1h 32 ms 28 ms ⚪ -12% 7 ms 4.00× ✅ same
⚪ P5 json count by level 1h 71 ms 71 ms ⚪ +0% 14 ms 5.07× ✅ same
⚪ P6 json count, level filter 1h 15 ms 16 ms ⚪ +7% 13 ms 1.23× ✅ same
⚪ P7 json rate by level, level regexp 1h 16 ms 17 ms ⚪ +6% 14 ms 1.21× ✅ same
⚪ P8 json count by a name that is no stream label 1h 76 ms 76 ms ⚪ +0% 13 ms 5.85× ✅ same
⚪ P9 instant logfmt by level instant 6 ms 5 ms ⚪ -17% 10 ms 0.50× ✅ same
⚪ SF1 logs, json extraction list 1h 82 ms 80 ms ⚪ -2% 40 ms 2.00× ✅ same
⚪ SF10 volume, structured-metadata filter 1h 13 ms 13 ms ⚪ +0% 30 ms 0.43× ✅ same
⚪ SF2 logs, logfmt extraction list 1h 137 ms 142 ms ⚪ +4% 41 ms 3.46× ✅ same
⚪ SF3 logs, json then line_format 1h 89 ms 88 ms ⚪ -1% 57 ms 1.54× ✅ same
⚪ SF4 logs, line_format without a parser 1h 58 ms 63 ms ⚪ +9% 35 ms 1.80× ✅ same
⚪ SF5 logs, regexp capture 1h 135 ms 132 ms ⚪ -2% 45 ms 2.93× ✅ same
⚪ SF6 logs, structured-metadata filter 1h 57 ms 54 ms ⚪ -5% 122 ms 0.44× ✅ same
⚪ SF7 logs, filter on a line key without a parser 1h 74 ms 70 ms ⚪ -5% 20 ms 3.50× ✅ same
⚪ SF8 volume, json extraction list 1h 22 ms 20 ms ⚪ -9% 43 ms 0.47× ✅ same
⚪ SF9 volume, logfmt label_format rename 1h 483 ms 480 ms ⚪ -1% 58 ms 8.28× ✅ same
⚪ E1 json duration_ms sum by service 1h 77 ms 77 ms ⚪ +0% 14 ms 5.50× ➖ differs (as base)
⚪ E2 json duration_ms p95 by service 1h 304 ms 294 ms ⚪ -3% 15 ms 20× ✅ same
⚪ E3 logfmt latency_ms rate, production 1h 450 ms 459 ms ⚪ +2% 125 ms 3.67× ➖ differs (as base)
⚪ E4 logfmt size_bytes max by service 1h 39 ms 39 ms ⚪ +0% 14 ms 2.79× ➖ differs (as base)
⚪ E5 json duration_ms sum, instant instant 26 ms 23 ms ⚪ -12% 25 ms 0.92× ✅ same
⚪ E6 logfmt ttl unit string (Loki 400) 1h 400 400 ⚪ -2% 400 — ✅ same
⚪ E7 json hexadecimal trace_id (Loki 400) 1h 400 400 ⚪ +2% 400 — ✅ same
⚪ E8 json duration_ms sum by service, namespace prod 1h 171 ms 178 ms ⚪ +4% 82 ms 2.17× ➖ differs (as base)
⚪ E9 logfmt latency_ms sum by service, namespace prod 1h 268 ms 272 ms ⚪ +1% 91 ms 2.99× ➖ differs (as base)
⚪ U1 logfmt amount rate 1h 34 ms 32 ms ⚪ -6% 14 ms 2.29× ✅ same
⚪ U2 json duration_ms rate 1h 79 ms 80 ms ⚪ +1% 23 ms 3.48× ✅ same
⚪ U3 json duration_ms rate, error filter 1h 80 ms 79 ms ⚪ -1% 27 ms 2.93× ✅ same
⚪ U4 logfmt latency_ms rate, production 1h 318 ms 317 ms ⚪ -0% 124 ms 2.56× ➖ differs (as base)
⚪ U5 logfmt amount rate, instant instant 6 ms 7 ms ⚪ +17% 12 ms 0.58× ✅ same
⚪ U6 json duration_ms rate, instant instant 11 ms 11 ms ⚪ +0% 19 ms 0.58× ✅ same
⚪ U7 json duration_ms sum_over_time, instant instant 13 ms 13 ms ⚪ +0% 20 ms 0.65× ✅ same
⚪ U8 logfmt amount rate by service (grouped) 1h 38 ms 38 ms ⚪ +0% 12 ms 3.17× ✅ same
⚪ U9 logfmt amount rate_counter (folded per series) 1h 213 ms 213 ms ⚪ +0% 42 ms 5.07× ➖ differs (as base)
⚪ U1 json duration_ms sum by service 1h 77 ms 74 ms ⚪ -4% 15 ms 4.93× ➖ differs (as base)
⚪ U10 instant sum_over_time unwrap instant 27 ms 24 ms ⚪ -11% 26 ms 0.92× ✅ same
⚪ U11 instant max_over_time missing label instant 4 ms 4 ms ⚪ +0% 14 ms 0.29× ✅ same
⚪ U12 json duration_ms over every service 1h 213 ms 214 ms ⚪ +0% 41 ms 5.22× ➖ differs (as base)
⚪ U13 logfmt latency_ms over every service 1h 450 ms 445 ms ⚪ -1% 57 ms 7.81× ➖ differs (as base)
⚪ U14 duration() of a unit string 1h 39 ms 39 ms ⚪ +0% 7 ms 5.57× ➖ differs (as base)
⚪ U2 json duration_ms max by service 1h 81 ms 82 ms ⚪ +1% 36 ms 2.28× ➖ differs (as base)
⚪ U3 json duration_ms sliding sum 1h 76 ms 77 ms ⚪ +1% 33 ms 2.33× ➖ differs (as base)
⚪ U4 logfmt size_bytes sum by service 1h 36 ms 34 ms ⚪ -6% 7 ms 4.86× ➖ differs (as base)
⚪ U5 logfmt ttl unit string (no sample) 1h 35 ms 34 ms ⚪ -3% 7 ms 4.86× ✅ same
⚪ U6 missing label, no parser 1h 17 ms 17 ms ⚪ +0% 9 ms 1.89× ✅ same
⚪ U7 missing label, ungrouped sum 1h 15 ms 16 ms ⚪ +7% 9 ms 1.78× ✅ same
⚪ U8 rate of an unwrapped label 1h 79 ms 78 ms ⚪ -1% 14 ms 5.57× ✅ same
⚪ U9 bytes() conversion 1h 46 ms 45 ms ⚪ -2% 12 ms 3.75× ➖ differs (as base)
Cold (first-run) timings — what a user opening a panel sees
shape range base cold PR cold Loki cold PR ÷ Loki cold
bytes_over_time by app 1h 70 ms 84 ms 886 ms 0.09×
drilldown service landing 1h 30 ms 35 ms 711 ms 0.05×
instant json filtered instant 338 ms 340 ms 74 ms 4.59×
instant json volume instant 388 ms 387 ms 97 ms 3.99×
instant ungrouped rate instant 5 ms 9 ms 76 ms 0.12×
json rate by level (no filter, no drop) 1h 400 400 400 —
json volume no filters 1h 187 ms 188 ms 3 ms 62.67×
logfmt count by level, filter, drop 1h 405 ms 399 ms 1.32 s 0.30×
logfmt volume 1h 192 ms 210 ms 1.51 s 0.14×
logs plain 1h 75 ms 53 ms 38 ms 1.39×
logs | json 1h 84 ms 74 ms 59 ms 1.25×
logs | logfmt 1h 123 ms 130 ms 46 ms 2.83×
stream-label count by app 1h 24 ms 25 ms 838 ms 0.03×
topk rate by app 1h 43 ms 44 ms 888 ms 0.05×
ungrouped rate 1h 11 ms 14 ms 816 ms 0.02×
DB1 label breakdown pod 1h 99 ms 102 ms 1.09 s 0.09×
DB2 label breakdown namespace 1h 17 ms 16 ms 754 ms 0.02×
DB3 json field breakdown user_id 1h 51 ms 45 ms 714 ms 0.06×
DB4 json field breakdown batch_id 1h 110 ms 102 ms 800 ms 0.13×
X1 json filter level_extracted, by level 1h 2.94 s 2.89 s 946 ms 3.05×
X2 json group by level_extracted 1h 271 ms 288 ms 920 ms 0.31×
X3 json rate, level_extracted equality 1h 242 ms 251 ms 755 ms 0.33×
X4 json bytes, level_extracted regexp 1h 390 ms 342 ms 808 ms 0.42×
X5 logfmt filter level_extracted, by level 1h 604 ms 622 ms 1.20 s 0.52×
E1 logs, json parser over stream labels 1h 156 ms 316 ms 62 ms 5.10×
E2 logs, logfmt parser over stream labels 1h 70 ms 63 ms 44 ms 1.43×
E3 logs, json, existence filter on level_extracted 1h 128 ms 134 ms 75 ms 1.79×
E4 logs, logfmt, existence filter on level_extracted 1h 78 ms 78 ms 50 ms 1.56×
E5 logs, json, drop level_extracted 1h 124 ms 106 ms 60 ms 1.77×
E6 logfmt count by level_extracted 1h 41 ms 46 ms 52 ms 0.88×
E7 json count by level_extracted 1h 1.18 s 1.17 s 206 ms 5.68×
E8 instant logfmt by level_extracted instant 6 ms 6 ms 9 ms 0.67×
A explore volume, 2 filters, drop 1h 1.25 s 1.19 s 508 ms 2.33×
B grouped sum, filter, no drop 1h 123 ms 181 ms 1.73 s 0.10×
C ungrouped sum, filter, no drop 1h 115 ms 111 ms 1.42 s 0.08×
D volume, no filters 1h 190 ms 189 ms 1.96 s 0.10×
E field breakdown pipeline (explore) 1h 104 ms 103 ms 1.44 s 0.07×
F field breakdown service_version (explore) 1h 1.12 s 1.14 s 807 ms 1.41×
G field breakdown pipeline (drilldown) 1h 46 ms 45 ms 690 ms 0.07×
H field breakdown service_version (drilldown) 1h 111 ms 106 ms 783 ms 0.14×
I grafana volume of B 1h 103 ms 114 ms 806 ms 0.14×
O1 explore logs, underscore structured-metadata filter 1h 339 ms 316 ms 366 ms 0.86×
O2 explore log volume (Grafana form), underscore structured-metadata filter 1h 152 ms 167 ms 681 ms 0.25×
O3 explore logs, dotted structured-metadata filter 1h 400 400 400 —
O4 explore log volume (Grafana form), dotted structured-metadata filter 1h 400 400 400 —
drilldown detected fields 1h 23 ms 25 ms 18 ms 1.39×
drilldown detected labels 1h 18 ms 17 ms 3 ms 5.67×
drilldown landing volume 1h 53 ms 57 ms 23 ms 2.48×
explore label values env 1h 65 ms 62 ms 10 ms 6.20×
explore label values service_name 1h 53 ms 44 ms 14 ms 3.14×
explore labels (label browser) 1h 25 ms 5 ms 3 ms 1.67×
explore labels with selector 1h 46 ms 47 ms 13 ms 3.62×
refresh label values env 1h 3 ms 3 ms 11 ms 0.27×
refresh label values service_name 1h 2 ms 2 ms 15 ms 0.13×
refresh labels (label browser) 1h 2 ms 2 ms 3 ms 0.67×
refresh labels with selector 1h 2 ms 2 ms 13 ms 0.15×
DL1 drilldown field breakdown json-expr, level filter 1h 472 ms 468 ms 700 ms 0.67×
DL2 drilldown field breakdown, level + stream-label filter before json 1h 85 ms 81 ms 33 ms 2.45×
DL3 drilldown logfmt field breakdown, level filter 1h 603 ms 584 ms 1.38 s 0.42×
DL4 instant count, level filter, json+logfmt, 1h instant 485 ms 475 ms 1.35 s 0.35×
DL6 explore volume, level filter before json 1h 951 ms 923 ms 880 ms 1.05×
OR1 logs, or alternatives 1h 68 ms 68 ms 61 ms 1.11×
OR2 logs, negated or alternatives 1h 71 ms 66 ms 38 ms 1.74×
OR3 logs, regexp or alternatives 1h 50 ms 50 ms 64 ms 0.78×
OR4 count by level, or alternatives with parser 1h 124 ms 120 ms 658 ms 0.18×
OR5 rate, negated regexp or alternatives 1h 54 ms 55 ms 781 ms 0.07×
OR6 instant, or alternatives instant 8 ms 7 ms 64 ms 0.11×
PR1 count by level, parenthesised json 1h 199 ms 177 ms 986 ms 0.18×
PR2 rate, parenthesised line filter 1h 46 ms 47 ms 557 ms 0.08×
PR3 bytes by app, parenthesised regexp 1h 51 ms 50 ms 593 ms 0.08×
PR4 count by level, parenthesised parser, offset 1h 178 ms 192 ms 904 ms 0.21×
PR5 rate by app, pipeline after the range 1h 204 ms 217 ms 1.08 s 0.20×
PR6 doubly parenthesised range 1h 45 ms 50 ms 552 ms 0.09×
PR7 instant, parenthesised line filter instant 7 ms 6 ms 62 ms 0.10×
P1 logs, json, level equals 1h 146 ms 110 ms 80 ms 1.38×
P2 logs, logfmt, level not equal 1h 30 ms 27 ms 40 ms 0.67×
P3 logs, json, level existence 1h 116 ms 112 ms 86 ms 1.30×
P4 logfmt count by level 1h 31 ms 28 ms 46 ms 0.61×
P5 json count by level 1h 82 ms 82 ms 196 ms 0.42×
P6 json count, level filter 1h 16 ms 19 ms 186 ms 0.10×
P7 json rate by level, level regexp 1h 15 ms 19 ms 198 ms 0.10×
P8 json count by a name that is no stream label 1h 86 ms 86 ms 159 ms 0.54×
P9 instant logfmt by level instant 5 ms 5 ms 9 ms 0.56×
SF1 logs, json extraction list 1h 311 ms 86 ms 39 ms 2.21×
SF10 volume, structured-metadata filter 1h 14 ms 13 ms 498 ms 0.03×
SF2 logs, logfmt extraction list 1h 136 ms 138 ms 42 ms 3.29×
SF3 logs, json then line_format 1h 108 ms 93 ms 57 ms 1.63×
SF4 logs, line_format without a parser 1h 60 ms 59 ms 39 ms 1.51×
SF5 logs, regexp capture 1h 133 ms 138 ms 45 ms 3.07×
SF6 logs, structured-metadata filter 1h 59 ms 58 ms 119 ms 0.49×
SF7 logs, filter on a line key without a parser 1h 70 ms 68 ms 595 ms 0.11×
SF8 volume, json extraction list 1h 20 ms 28 ms 881 ms 0.03×
SF9 volume, logfmt label_format rename 1h 483 ms 480 ms 1.43 s 0.33×
E1 json duration_ms sum by service 1h 97 ms 74 ms 218 ms 0.34×
E2 json duration_ms p95 by service 1h 312 ms 300 ms 237 ms 1.27×
E3 logfmt latency_ms rate, production 1h 457 ms 451 ms 1.24 s 0.36×
E4 logfmt size_bytes max by service 1h 38 ms 42 ms 203 ms 0.21×
E5 json duration_ms sum, instant instant 28 ms 24 ms 24 ms 1.00×
E6 logfmt ttl unit string (Loki 400) 1h 400 400 400 —
E7 json hexadecimal trace_id (Loki 400) 1h 400 400 400 —
E8 json duration_ms sum by service, namespace prod 1h 170 ms 169 ms 690 ms 0.24×
E9 logfmt latency_ms sum by service, namespace prod 1h 271 ms 266 ms 899 ms 0.30×
U1 logfmt amount rate 1h 51 ms 35 ms 63 ms 0.56×
U2 json duration_ms rate 1h 80 ms 79 ms 180 ms 0.44×
U3 json duration_ms rate, error filter 1h 78 ms 75 ms 209 ms 0.36×
U4 logfmt latency_ms rate, production 1h 316 ms 316 ms 1.23 s 0.26×
U5 logfmt amount rate, instant instant 6 ms 8 ms 12 ms 0.67×
U6 json duration_ms rate, instant instant 11 ms 12 ms 19 ms 0.63×
U7 json duration_ms sum_over_time, instant instant 13 ms 13 ms 19 ms 0.68×
U8 logfmt amount rate by service (grouped) 1h 36 ms 37 ms 67 ms 0.55×
U9 logfmt amount rate_counter (folded per series) 1h 209 ms 215 ms 319 ms 0.67×
U1 json duration_ms sum by service 1h 97 ms 77 ms 217 ms 0.35×
U10 instant sum_over_time unwrap instant 27 ms 24 ms 27 ms 0.89×
U11 instant max_over_time missing label instant 5 ms 5 ms 16 ms 0.31×
U12 json duration_ms over every service 1h 217 ms 210 ms 814 ms 0.26×
U13 logfmt latency_ms over every service 1h 455 ms 453 ms 1.29 s 0.35×
U14 duration() of a unit string 1h 43 ms 38 ms 50 ms 0.76×
U2 json duration_ms max by service 1h 82 ms 80 ms 840 ms 0.10×
U3 json duration_ms sliding sum 1h 77 ms 76 ms 241 ms 0.32×
U4 logfmt size_bytes sum by service 1h 39 ms 35 ms 48 ms 0.73×
U5 logfmt ttl unit string (no sample) 1h 39 ms 35 ms 47 ms 0.74×
U6 missing label, no parser 1h 16 ms 16 ms 98 ms 0.16×
U7 missing label, ungrouped sum 1h 15 ms 14 ms 95 ms 0.15×
U8 rate of an unwrapped label 1h 81 ms 79 ms 280 ms 0.28×
U9 bytes() conversion 1h 47 ms 44 ms 55 ms 0.80×
Why these shapes
  • internal/translator/translator.go → 90: control/bytes_over_time by app, control/instant json filtered, control/instant json volume, control/instant ungrouped rate, control/json rate by level (no filter, no drop), control/json volume no filters, control/logfmt count by level, filter, drop, control/logfmt volume …
  • internal/proxy/http_utils.go → 46: drilldown-breakdown-exact/DB1 label breakdown pod, drilldown-breakdown-exact/DB2 label breakdown namespace, drilldown-breakdown-exact/DB3 json field breakdown user_id, drilldown-breakdown-exact/DB4 json field breakdown batch_id, extracted-suffix/E1 logs, json parser over stream labels, extracted-suffix/E2 logs, logfmt parser over stream labels, extracted-suffix/E3 logs, json, existence filter on level_extracted, extracted-suffix/E4 logs, logfmt, existence filter on level_extracted …
  • internal/logql/parser.go → 31: control/bytes_over_time by app, control/instant json volume, control/instant ungrouped rate, control/json rate by level (no filter, no drop), control/json volume no filters, control/logfmt count by level, filter, drop, control/logfmt volume, control/logs | json …
  • internal/logql/ast.go → 12: control/bytes_over_time by app, control/instant json volume, control/instant ungrouped rate, control/json volume no filters, control/stream-label count by app, control/topk rate by app, control/ungrouped rate, json-filter-pushdown/A explore volume, 2 filters, drop …
  • internal/proxy/vector_matching.go → 10: control/stream-label count by app, unwrap-ungrouped-sum/U1 logfmt amount rate, unwrap-ungrouped-sum/U2 json duration_ms rate, unwrap-ungrouped-sum/U3 json duration_ms rate, error filter, unwrap-ungrouped-sum/U4 logfmt latency_ms rate, production, unwrap-ungrouped-sum/U5 logfmt amount rate, instant, unwrap-ungrouped-sum/U6 json duration_ms rate, instant, unwrap-ungrouped-sum/U7 json duration_ms sum_over_time, instant …
  • control smoke → 8: control/drilldown service landing, control/instant ungrouped rate, control/json rate by level (no filter, no drop), control/json volume no filters, control/logs plain, control/logs | json, control/stream-label count by app, control/topk rate by app
  • internal/proxy/proxy.go (endpoint handler) → 8: control/drilldown service landing, control/instant ungrouped rate, control/json rate by level (no filter, no drop), control/json volume no filters, control/logs plain, control/logs | json, control/stream-label count by app, control/topk rate by app
  • internal/logql/canonical.go → 7: parenthesized-range-or-filters/PR1 count by level, parenthesised json, parenthesized-range-or-filters/PR2 rate, parenthesised line filter, parenthesized-range-or-filters/PR3 bytes by app, parenthesised regexp, parenthesized-range-or-filters/PR4 count by level, parenthesised parser, offset, parenthesized-range-or-filters/PR5 rate by app, pipeline after the range, parenthesized-range-or-filters/PR6 doubly parenthesised range, parenthesized-range-or-filters/PR7 instant, parenthesised line filter

base b20a550f6f → PR b66d2b8f3b · 4 runs (first is cold), warm p50 · ranges 1h · noise ±30% and 100 ms · VictoriaLogs restarts 0→0 · 12.1 min. Same runner, one fresh stack, targets interleaved per request. How to read and reproduce: bench/ab/README.md.

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

PR Quality Report

Compared against base branch main.

Coverage and tests

Signal Base PR Delta
Test count 8648 8821 173
Coverage 87.7% 87.7% 0.0% (stable)

Compatibility

Track Base PR Delta
Loki API 100.0% 11/11 (100.0%) 0.0% (stable)
Logs Drilldown 100.0% 17/17 (100.0%) 0.0% (stable)
VictoriaLogs 100.0% 11/11 (100.0%) 0.0% (stable)

Performance smoke

Lower CPU cost (ns/op) is better. Lower benchmark memory cost (B/op, allocs/op) is better. Higher throughput is better. Lower load-test memory growth is better. Benchmark rows are medians from repeated samples.

Signal Base PR Delta
QueryRange cache-hit CPU cost 3421.0 ns/op 3362.0 ns/op -1.7% (stable)
QueryRange cache-hit memory 1240.0 B/op 1240.0 B/op 0.0% (stable)
QueryRange cache-hit allocations 27.0 allocs/op 27.0 allocs/op 0.0% (stable)
QueryRange cache-bypass CPU cost 4029.0 ns/op 4077.0 ns/op +1.2% (stable)
QueryRange cache-bypass memory 1300.0 B/op 1302.0 B/op +0.2% (stable)
QueryRange cache-bypass allocations 27.0 allocs/op 27.0 allocs/op 0.0% (stable)
Labels cache-hit CPU cost 894.2 ns/op 880.5 ns/op -1.5% (stable)
Labels cache-hit memory 48.0 B/op 48.0 B/op 0.0% (stable)
Labels cache-hit allocations 3.0 allocs/op 3.0 allocs/op 0.0% (stable)
Labels cache-bypass CPU cost 1493.0 ns/op 1501.0 ns/op +0.5% (stable)
Labels cache-bypass memory 399.0 B/op 399.0 B/op 0.0% (stable)
Labels cache-bypass allocations 14.0 allocs/op 14.0 allocs/op 0.0% (stable)

State

  • Coverage, compatibility, and sampled performance are reported here from the same PR workflow.
  • This is a delta report, not a release gate by itself. Required checks still decide merge safety.
  • Performance is a smoke comparison, not a full benchmark lab run.
  • Delta states use the same noise guards as the quality gate (percent + absolute + low-baseline checks), so report labels match merge-gate behavior.

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

bugfix Bug fix scope/docs Documentation scope/proxy Proxy core scope/tests Tests scope/translator LogQL translator size/XL Extra large change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant