fix: correctness failure for aggregations with a drop - #20782
Conversation
V1 Engine Behavior AnalysisThis fix correctly aligns v2 with v1's approach to handling error labels in aggregations. How V1 Treats Error Columns in AggregationsKey principle: V1 allows samples with Error Handling Flow for
|
There was a problem hiding this comment.
Pull request overview
This PR fixes a correctness bug in the v2 query engine where automatic error filtering was incorrectly applied to all range aggregations, including count operations. The fix ensures error filtering only occurs for unwrap operations (sum, avg, max, min with unwrap), where invalid numeric values would corrupt results. Count operations now correctly count all rows regardless of parse errors, matching v1 behavior.
Changes:
- Added a test query to the LogQL correctness suite that exposes the bug (count_over_time with json parsing and drop error)
- Moved error filtering logic to only apply after unwrap operations, not for all range aggregations
- Updated test expectations to remove incorrect error filters from count operations without unwrap
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| pkg/logql/bench/generator_query.go | Adds a new test query `count_over_time({service_name="loki"} |
| pkg/engine/internal/planner/logical/planner.go | Moves error filtering from after all range aggregations to only after unwrap operations |
| pkg/engine/internal/planner/logical/planner_test.go | Updates test expectations to remove error filters from count operations without unwrap, and adds clarifying comment for unwrap error filtering |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| addMetricQuery(fmt.Sprintf(`sum by (detected_level) (avg_over_time({service_name="loki"} | json | logfmt | duration != "" | drop __error__, __error_details__ | unwrap duration_seconds(duration) [%s]))`, rangeInterval), start, end, step) | ||
| addMetricQuery(fmt.Sprintf(`sum by (detected_level) (count_over_time({service_name=~"(?i)loki"} | detected_level="debug" or detected_level="info" or detected_level="warn" |~ "(?i)(?i)duration" | json | logfmt | drop __error__, __error_details__ | level=~"(?i)INFO" [%s]))`, rangeInterval), start, end, step) | ||
|
|
||
| // Bugfix test for observed mismatch: V2 added automatic error filter causing it to count only rows without parse errors, resulting in fewer results than V1 |
There was a problem hiding this comment.
The comment could be more precise. Consider revising to: "Bugfix test for observed mismatch: V2 incorrectly added an automatic error filter, causing count operations to exclude rows with parse errors and resulting in fewer results than V1."
| // Bugfix test for observed mismatch: V2 added automatic error filter causing it to count only rows without parse errors, resulting in fewer results than V1 | |
| // Bugfix test for observed mismatch: V2 incorrectly added an automatic error filter, causing count operations to exclude rows with parse errors and resulting in fewer results than V1. |
… removal The previous commit removed automatic error filtering before range aggregation to fix a correctness bug. This commit updates the tree-formatted test expectations in TestFullQueryPlanning to reflect that change. Three test cases updated: - metric: multiple parse stages - math expression - parse logfmt with grouping All three no longer expect the Filter node checking for empty __error__ fields.
| %11 = EQ generated.__error__ "" | ||
| %12 = EQ generated.__error_details__ "" | ||
| %13 = AND %11 %12 | ||
| %14 = SELECT %10 [predicate=%13] |
There was a problem hiding this comment.
Just to make sure I understand: the issue is that we only want this filtering when you're specifically doing an unwrap?
There was a problem hiding this comment.
that's my understanding of how the v1 engine works. when doing an unwrap, we expect the row to have a valid value to unwrap. if there is a parsing error, we can't rely on that value being there, so we want to filter in that case.
for something like a count_over_time, however, we need to count all rows regardless of error
There was a problem hiding this comment.
Nice, this will help performance a little too then since we're removing an unnecessary filter
What this PR does / why we need it:
Add the query
count_over_time({service_name="loki"} | json | drop __error__ [%s])to the logql correctness suite. This exposes a bug where v2 was dropping rows which had errors, which is not consistent with v1. We should only drop rows with errors when doing an unwrap operation as those rows will have invalid data. When doing a count over time operation, we count all the rows, regardless of error, unless there is a specific filter on the error column.Special notes for your reviewer:
Checklist
CONTRIBUTING.mdguide (required)featPRs are unlikely to be accepted unless a case can be made for the feature actually being a bug fix to existing behavior.docs/sources/setup/upgrade/_index.mddeprecated-config.yamlanddeleted-config.yamlfiles respectively in thetools/deprecated-config-checkerdirectory. Example PR