Repository navigation
Conversation
…esultType A query handleQuery post-processes (without, label_replace, label_join) was buffered and a failure of it was written with the status dropped, so an error reached the client as a 200 with no data and no resultType; query_range already sent the failing status, the instant path now does too. A label_replace or label_join around a binary expression sent the translator's internal binary marker to VictoriaLogs as a query; the expression under the transform now routes like any binary expression. Also on the instant endpoint: a scalar literal answers a scalar as Loki's evalLiteral does, vector() stamps its sample in seconds, and a log query with every tenant filtered out is rejected with Loki's 400 instead of an empty streams answer.
…the proxy alone The instant result-type table runs as a bare client, Grafana Explore and Logs Drilldown, which take their own failure and series-limit branches. The multi-tenant e2e checks the proxy against the type Loki gives the expression, as the compose Loki is not configured for multi-tenant queries.
… instant constants follow Loki label_replace and label_join split the expression they wrap at the first comma outside strings and brackets, in the translator and in the instant routing, so a parenthesis or comma in a line filter or a matcher no longer sends the query to the log path or the binary marker to VictoriaLogs. vector(n) with a scalar, and a scalar with vector(n), are evaluated as constants through one path, a literal division by zero is NaN as in Loki's MergeBinOp, and with every tenant filtered out an unparseable query gets the parse error. Cluster f2066991 is no longer claimed by the instant result-type row; a stub row owns it.
⚪ Performance A/B: no change beyond noise101 shape×range — 101 ⚪ same · results vs Loki: 84 same, 0 new difference(s), 17 pre-existing Every shape is within noise of the base build (±30% and 100 ms). 101 unchanged shape×range (within noise)
Cold (first-run) timings — what a user opening a panel sees
Why these shapes
base |
✅ Visual smoke: passed with warningsBase Fix proofCaptures of the exact cases this pull request adds or fixes (registry case, base | PR | Loki). The verdict compares the PR with Loki where the base did not match. The gate fails a case unless one capture is fixed or improved, or, beyond the Loki data window, the base failed where Loki and the PR both answered with data (the label does not excuse it; only
Regression check
Gate: a base-vs-PR data difference passes when the PR is closer to Loki, and fails when the PR diverges from Loki or Loki cannot decide it (at a range Loki does not hold, a difference passes when the same page proved it closer to Loki at a shorter range; otherwise label the pull request explore-sum-by-over-binary-6h: no Loki data at this range; the same differences match Loki at a shorter range (1): query ('sum by (service_name) (rate(
Core set montages (5): base | PR | Lokidd-fields-1h dd-landing-1h dd-service-logs-1h explore-logs-plain-1h explore-metric-plain-1h All montages of this run: |
PR Quality ReportCompared against base branch Coverage and tests
Compatibility
Performance smokeLower CPU cost (
State
|
0257129 to
6df85f0
Compare
…pression label_replace(a, ...) / b is a binary expression, not a transform of a: the instant routing stripped the call by its prefix and answered a alone. The wrapper is now removed only when its closing parenthesis ends the expression.
…reads escaped regexps The transform ran on range matrices only, so an instant vector came back without the new label. It now rewrites the labels of a vector and of a matrix alike, keeps the rest of the answer, expands the replacement as Loki does and files the points of series that end with one label set under one series on a range query, as JoinSampleVector does. The string arguments are unquoted like the LogQL lexer does, a call without the four string arguments is a 400 parse error, topk/sort/stddev over a label_replace no longer send the translator's marker to VictoriaLogs, a label_replace around a binary expression on query_range routes by the expression under it, and the without marker no longer swallows the markers that follow it.
… the expression under it, and reports Loki's argument errors The series of a label_replace answer are ordered by label set after the transform (a range result, and an instant vector unless the query sorts by value). The request is cloned once, above the routing chain, when a label transform wraps the expression. A malformed call reports the token names and the string start column of Loki's parser. label_join is no longer claimed as a feature of the transform. Ranking over duplicate label sets that a label_replace produced is registered as an open row.
6df85f0 to
be827a4
Compare
…n query, so each operand keeps its parser
…ary, like Loki A sum, avg, min, max or count over a binary expression answered only the first operand: the translator's outer-aggregation extraction dropped the operator and the second operand, so `sum by (job) (A / B)`, `sum(A / 2)`, a comparison and on()/ignoring() under an aggregation returned wrong values with a 200, and an instant query with a literal or a nested binary first answered 400 for a log query. Loki joins the operands per series and folds the joined series per step (newBinOpStepEvaluator, vectorBinop, VectorAggEvaluator.Next). The query and query_range routing now send such an aggregation to the binary evaluator: each operand stays a native VictoriaLogs stats query, the proxy joins the aggregated series and folds them with the aggregation's operator. The expression is read from the request so an operand keeps its own parser, a one-sided offset is accepted as for a bare binary, and folded series come in Loki's label order. Registers semantics/aggregation-over-binary-dropped (fixed) and moves the discovered cluster f2066991 to it.
…ary evaluator Only an aggregation routed from under a label_replace reads the expression inside it; a binary whose first operand is a label_replace is read whole again, as before. The differential also draws one-to-one ignoring() over a label one side lacks, and new tests pin a uniform offset on both sides.
… the folded answer label_replace and label_join around `sum by (job) (a / b)` (the discovered shape of cluster f2066991 on /query) route by the expression inside them on query_range as on query, and the transform applies to the folded answer; the transforms now apply to an instant vector as well as a matrix. A transform is stripped only when its call is the whole expression: `label_replace(a, ...) / b` is a binary whose operand applies its own. The series a fold makes keep every label set apart (escaped keys: a value holding `,` or `=` no longer folds with another set) and come in Loki's order, labels.Compare: name then value, label by label. The differential also starts half its range scenarios off the step grid. Registers semantics/binary-division-by-zero-nan (open): `x / 0` answers ±Inf where Loki answers NaN.
… differential pins start+k*step Under a label_replace or label_join the request is cloned to the expression inside it from the request's own query, not from the handler's rewritten one, which may have lost the parser stage an operand needs (a query naming both `| json` and `| logfmt`). The aggregation differential's reference evaluates at start+k*step, as a default Loki does; a join of two vectors off the step grid answers on the aligned grid instead and is registered apart as semantics/binary-step-grid (open). A transform's warnings and statistics are pinned.
be827a4 to
5158a0a
Compare








Stacked on #735, and to be rebased onto #744. Merge order: #735 → #744 → this PR. #744 owns the vector support in
applyLabelReplace; on that rebase this branch drops its own version of that change. The label transform around an aggregated binary (cluster f2066991's wrapped /query) is now routed and applied after the fold on both endpoints. The fold keys series by the escaped label key and orders them by Loki's pairwise label comparison. A new open row covers the binary step grid off the start (semantics/binary-step-grid), and another covers x/0 (semantics/binary-division-by-zero-nan). The folded-output series limit (after #739) and NaN min/max (after #734) are deferred.Summary
New gap
semantics/aggregation-over-binary-dropped, which owns parity cluster f2066991.A vector aggregation over a binary expression silently returned wrong values. Examples:
sum by (job) (rate(A[1m]) / count_over_time(A[1m]))sum(rate({app="x"}[1m]) / 2)sum by (cluster) (A + ignoring(machine) B)The proxy sent only a top-level binary to the binary evaluator. An aggregation over a binary went to the translator instead, and the translator kept the first operand's stats chain. It dropped the operator and the second operand (
/ B,/ 2,> 0.5,on/group_left) and answered 200 with that operand's values. On instant queries, a literal or nested binary as the first operand was rejected with 400.Loki 3.7.7 joins per series and then folds per step. Sources:
pkg/logql/evaluator.go:791newBinOpStepEvaluator:955vectorBinop:1096resultMetric:400-630VectorAggEvaluatorpkg/logql/syntax/ast.go:1944MergeBinOpFix.
internal/proxy/binary_aggregation.gorecognises sum, avg, min, max and count (with by, without or no grouping, and nested) over a binary expression. These queries now route to the existing binary evaluator on query_range and query.applyWithoutGrouping, innermost aggregation first. No raw rows.Tests
TestAggregationOverBinaryEdgeCases: 25 cases, handler-level against a VictoriaLogs emulator on v1.52 and v1.40.bool.on/ignoringwithgroup_left.label_replaceoperand staying on the binary evaluator.TestAggregationOverBinaryDifferential: an independent Loki-semantics reference. It passed 3,200 scenarios locally and 40 iterations are committed. It includes thesum by (job)(A) + ignoring(machine) sum by (job, machine)(B)shape.TestCompat_AggregationOverBinary,TestCompat_AggregationOverBinaryMixedParsers.This also fixes the outer-sum symptom of
semantics/binary-ignoring-matching, whose join fixes are on a separate branch.Pending before ready