feat(querier): extract row-group pruning stats from DataFusion — B1 live (slice 2) - #88
Conversation
…ive (slice 2) Querier::run now builds + executes the physical plan itself (count aggregate, so heavy columns aren't materialised) and walks the executed ExecutionPlan tree for the ParquetExec metrics, populating QueryResult.stats: row_groups_pruned + row_groups_scanned from the row_groups_pruned_statistics PruningMetrics (pruned/matched), and bytes_read from bytes_scanned. Metric names confirmed empirically against datafusion 53. §4.6 held — only plain integers cross the public boundary. RFC0007.1 (B1) is now a LIVE test: a selective template_id query over two files (one per template) prunes the non-matching file's row group via statistics — row_groups_pruned >= 1, with at least one scanned. Proves pushdown skips data rather than scanning it. RFC0007.2 (B2 latency bench) is the next slice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe querier builds and executes a count aggregation physical plan, extracts the resulting row count from the collected RecordBatch, and computes QueryStats by traversing the executed physical plan to sum Parquet row-group pruning and bytes-scanned metrics. A new integration test verifies pruning occurred. ChangesRFC0007.1 row-group pruning metrics collection
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR advances the ourios-querier execution path to slice 2 by surfacing Parquet row-group pruning/IO metrics from DataFusion, and by promoting RFC0007.1 (B1) from an ignored acceptance stub into a live execution test that demonstrates statistics-based pruning.
Changes:
- Update
Querier::runto execute an explicit aggregate physical plan (instead ofdf.count()) so the executedExecutionPlanis retained and its metrics can be read. - Walk the physical plan tree and map DataFusion Parquet metrics into
QueryStats(row groups pruned/scanned, bytes read). - Add a live B1 test that asserts a selective
template_id = 1query prunes at least one row group via Parquet statistics.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/ourios-querier/src/lib.rs | Executes an explicit physical plan and extracts Parquet pruning + IO metrics into QueryStats. |
| crates/ourios-querier/tests/execution.rs | Adds a live RFC0007.1 test proving row-group pruning via Parquet statistics. |
| crates/ourios-querier/tests/acceptance.rs | Removes the ignored RFC0007.1 stub and points to the live execution test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ourios-querier/src/lib.rs`:
- Around line 193-229: Add colocated unit tests in the same file beneath a
#[cfg(test)] mod that exercise scan_stats and accumulate_scan_stats: implement
minimal fake ExecutionPlan(s) (or use a TestPlan helper) that return metrics
with aggregate_by_name entries named "row_groups_pruned_statistics"
(PruningMetrics with pruned() and matched()) and "bytes_scanned" (Count),
include child plans to verify recursive aggregation, and assert the resulting
QueryStats.bytes_read, row_groups_pruned, and row_groups_scanned match expected
totals; keep tests adjacent to the functions scan_stats and
accumulate_scan_stats so metric-name/aggregation behavior is pinned locally and
resilient to DataFusion-version differences.
- Around line 184-190: count_value currently masks malformed or unexpected
aggregate output by returning 0; change it to fail closed by returning a
Result<u64, E> (e.g., Result<u64, DataFusionError> or anyhow::Error) and return
Err with a clear message when the batches array is empty, the first batch has no
rows, the first column cannot be downcast to Int64Array, or the Int64 value
cannot be converted to u64; update all call sites of count_value to handle the
Result (propagate the error or map it into the surrounding execution error) so
unexpected DataFusion shapes surface as execution failures instead of silent
zero counts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4bbad017-c226-4015-8e64-4ba0898309ef
📒 Files selected for processing (3)
crates/ourios-querier/src/lib.rscrates/ourios-querier/tests/acceptance.rscrates/ourios-querier/tests/execution.rs
… — B1 live (slice 2)
… — B1 live (slice 2)
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/ourios-querier/src/lib.rs (1)
193-214:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFail closed on extra aggregate columns in
count_value.
count_valuedocuments thatCOUNT(*)(no grouping) returns “exactly one Int64 row”, but the current check only rejectsnum_columns() == 0and will still accept> 1columns, silently readingcolumn(0). Tighten the guard to require exactly 1 column.Suggested fix
- if batch.num_rows() != 1 || batch.num_columns() == 0 { + if batch.num_rows() != 1 || batch.num_columns() != 1 { return Err(bad(format!( - "expected 1 row × ≥1 column, got {}×{}", + "expected exactly 1 row × 1 column, got {}×{}", batch.num_rows(), batch.num_columns(), ))); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ourios-querier/src/lib.rs` around lines 193 - 214, The code currently allows extra aggregate columns by only rejecting zero columns; tighten the guard to require exactly one column: replace the check using batch.num_rows() != 1 || batch.num_columns() == 0 with batch.num_rows() != 1 || batch.num_columns() != 1 (and update the error text to "expected 1 row × 1 column, got {}×{}"); this ensures the subsequent column(0) downcast to Int64Array and null check for count remain valid when implementing count_value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/ourios-querier/src/lib.rs`:
- Around line 193-214: The code currently allows extra aggregate columns by only
rejecting zero columns; tighten the guard to require exactly one column: replace
the check using batch.num_rows() != 1 || batch.num_columns() == 0 with
batch.num_rows() != 1 || batch.num_columns() != 1 (and update the error text to
"expected 1 row × 1 column, got {}×{}"); this ensures the subsequent column(0)
downcast to Int64Array and null check for count remain valid when implementing
count_value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b5eae36a-ab4a-4c08-ab32-0480fdf5a2d5
📒 Files selected for processing (2)
crates/ourios-querier/src/lib.rscrates/ourios-querier/tests/execution.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/ourios-querier/tests/execution.rs
… — B1 live (slice 2)
What
Slice 2 of the querier execution path — B1 is live.
Querier::runnow reports how much data the query skipped, and a test proves a selective query prunes row groups via Parquet statistics. This is the first measured evidence for the query half of the thesis: pushdown skips data, it doesn't scan it.How
runnow builds + executes the physical plan itself (df.aggregate(count) → create_physical_plan → collect) instead ofdf.count()— same projection-pushdown efficiency (heavy columns never materialised), but the executedExecutionPlanis retained so its metrics can be read.ParquetExecmetrics intoQueryStats:row_groups_pruned/row_groups_scanned← therow_groups_pruned_statisticsPruningMetrics(.pruned()/.matched()).bytes_read←bytes_scanned.MetricValue::PruningMetrics { pruning_metrics }shape were confirmed empirically against datafusion 53 (dumped the live metric set, then matched the typed variant — not guessed).B1 test (RFC0007.1, now live)
rfc0007_1_pushdown_prunes_row_groups: tenant "a" gets two files (one pertemplate_id, in different hours). Atemplate_id = 1query must skip the template-2 file's row group (its min/max can't satisfy= 1) → assertsrow_groups_pruned ≥ 1and ≥ 1 scanned. Flipped from#[ignore]stub to live.Verification
Workspace 333 passed / 0 failed / 47 ignored; fmt/clippy/mdbook green.
Next
RFC0007.2 (B2) — wire the latency-vs-corpus-size measurement into
ourios-benchagainstcorpus/otel-demo-v*: median template-exact latency should track result size, not corpus size. That's the headline query-thesis number.🤖 Generated with Claude Code
Summary by CodeRabbit
Performance Improvements
Documentation
Tests