Repository navigation
test(parquet): add I/O access tests for parquet pruning, filter pushdown and TopK #27022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+880
−65
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c0a70fb
test(parquet): add SQL-level I/O snapshot test for Bloom filter loadi…
mohitgurav20 73ba64e
test(parquet): use inline snapshot for Bloom filter I/O test
alamb 37110ee
test(parquet): move I/O tracing setup into parquet/io module
alamb a6a9426
Rework tests
alamb 61f0e9d
Merge remote-tracking branch 'apache/main' into alamb/pr-26897-io-ref…
alamb 1522d2c
test(parquet): complete comment on pruned Bloom filter I/O test
alamb f1517f6
comments
alamb e85a89f
test(parquet): add I/O tracking tests for row group pruning, filter p…
alamb 6b5ec0b
Merge remote-tracking branch 'apache/main' into alamb/parquet-io-prun…
alamb e92655f
Comments
alamb 5da1a02
Update tests
alamb 9a1bfd1
show requests
alamb e04f166
ft
alamb 5ebd5be
Merge remote-tracking branch 'apache/main' into alamb/parquet-io-prun…
alamb 86d96a0
Record HEAD requests, set metadata footer hint
alamb 7a51b70
tweaks
alamb dcc8a64
fmt
alamb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,24 @@ | |
| // SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements | ||
|
|
||
| //! Tests tracking storage I/O events during Parquet Bloom filter evaluation. | ||
| //! | ||
| //! The file has a single row group with Bloom filters on both columns and no | ||
| //! statistics, so only the Bloom filters are used to prune. | ||
| //! | ||
| //! ```text | ||
| //! row group 0 | ||
| //! ┌──────┬───────┐ | ||
| //! │ a │ b │ | ||
| //! ├──────┼───────┤ | ||
| //! │ 10 │ 100 │ | ||
| //! │ 20 │ 200 │ | ||
| //! │ 30 │ 300 │ | ||
| //! └──────┴───────┘ | ||
| //! Bloom filter (a) Bloom filter (b) no min/max statistics | ||
| //! | ||
| //! a = 10 AND b = 100 -> Bloom filters match, row group read | ||
| //! a = 999 AND b = 100 -> Bloom filter on `a` misses, row group pruned | ||
| //! ``` | ||
|
|
||
| use std::sync::Arc; | ||
|
|
||
|
|
@@ -21,14 +39,25 @@ async fn test_parquet_bloom_filter_io_matching() { | |
| let test = bloom_filter_test().await; | ||
| // Loads the Bloom filters, finds a match, then reads the data pages and | ||
| // produces a batch. | ||
| // | ||
| // Note: in practice, the default 512 KiB metadata size hint often covers | ||
| // the OffsetIndex and Bloom filters for small files, so they wouldn't | ||
| // need separate requests. | ||
| assert_snapshot!(test.query("SELECT * FROM t WHERE a = 10 AND b = 100").await, @r" | ||
| --- Query: SELECT * FROM t WHERE a = 10 AND b = 100 (1 rows) --- | ||
| Read Parquet footer (offsets 0..678) | ||
| Read OffsetIndex for column 'a' (rg 0) (offsets 192..213) | ||
| Read Bloom filter for column 'a' (rg 0) (offsets 98..145) | ||
| Read Bloom filter for column 'b' (rg 0) (offsets 145..192) | ||
| Read data page for column 'a' (rg 0) (offsets 4..51) | ||
| Read data page for column 'b' (rg 0) (offsets 51..98) | ||
| Request 1 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think showing the distinct requests is what is important as that is the actual IO that is generated |
||
| Head (object size and metadata) | ||
| Request 2 | ||
| Read Parquet footer (offsets 213..678) | ||
| Request 3 | ||
| Read OffsetIndex for columns 'a', 'b' (rg 0) (offsets 192..213) | ||
| Request 4 | ||
| Read Bloom filter for column 'a' (rg 0) (offsets 98..145) | ||
| Request 5 | ||
| Read Bloom filter for column 'b' (rg 0) (offsets 145..192) | ||
| Request 6 | ||
| Read dictionary page and data page 0 for column 'a' (rg 0) (offsets 4..51) | ||
| Read dictionary page and data page 0 for column 'b' (rg 0) (offsets 51..98) | ||
| Produce batch (1 rows) | ||
| "); | ||
| } | ||
|
|
@@ -43,10 +72,16 @@ async fn test_parquet_bloom_filter_io_pruned() { | |
| // evaluated first and the Bloom filter for `b` would not be loaded. | ||
| assert_snapshot!(test.query("SELECT * FROM t WHERE a = 999 AND b = 100").await, @r" | ||
| --- Query: SELECT * FROM t WHERE a = 999 AND b = 100 (0 rows) --- | ||
| Read Parquet footer (offsets 0..678) | ||
| Read OffsetIndex for column 'a' (rg 0) (offsets 192..213) | ||
| Read Bloom filter for column 'a' (rg 0) (offsets 98..145) | ||
| Read Bloom filter for column 'b' (rg 0) (offsets 145..192) | ||
| Request 1 | ||
| Head (object size and metadata) | ||
| Request 2 | ||
| Read Parquet footer (offsets 213..678) | ||
| Request 3 | ||
| Read OffsetIndex for columns 'a', 'b' (rg 0) (offsets 192..213) | ||
| Request 4 | ||
| Read Bloom filter for column 'a' (rg 0) (offsets 98..145) | ||
| Request 5 | ||
| Read Bloom filter for column 'b' (rg 0) (offsets 145..192) | ||
| "); | ||
| } | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership | ||
| // SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements | ||
|
|
||
| //! Tests tracking I/O events with and without Parquet filter pushdown | ||
| //! (`datafusion.execution.parquet.pushdown_filters`, see | ||
| //! [`ParquetOptions::pushdown_filters`]). | ||
| //! | ||
| //! Both tests run `val = 7`, which statistics cannot prune, but which only | ||
| //! matches a row in row group 1: | ||
| //! | ||
| //! ```text | ||
| //! row group 0 row group 1 row group 2 | ||
| //! ┌──────┬───────┐ ┌──────┬───────┐ ┌──────┬───────┐ | ||
| //! │ id │ val │ │ id │ val │ │ id │ val │ | ||
| //! ├──────┼───────┤ ├──────┼───────┤ ├──────┼───────┤ | ||
| //! │ 1 │ 0 │ │ 4 │ 0 │ │ 7 │ 0 │ | ||
| //! │ 2 │ 5 │ │ 5 │ 7 * │ │ 8 │ 5 │ | ||
| //! │ 3 │ 10 │ │ 6 │ 10 │ │ 9 │ 10 │ | ||
| //! └──────┴───────┘ └──────┴───────┘ └──────┴───────┘ | ||
| //! val: min 0 max 10 val: min 0 max 10 val: min 0 max 10 | ||
| //! | ||
| //! * the only row matching `val = 7` | ||
| //! ``` | ||
| //! | ||
| //! [`ParquetOptions::pushdown_filters`]: datafusion::config::ParquetOptions::pushdown_filters | ||
|
|
||
| use std::sync::Arc; | ||
|
|
||
| use arrow::array::{ArrayRef, Int32Array, RecordBatch}; | ||
| use bytes::Bytes; | ||
| use datafusion::prelude::SessionConfig; | ||
| use insta::assert_snapshot; | ||
| use parquet::arrow::ArrowWriter; | ||
| use parquet::file::properties::WriterProperties; | ||
|
|
||
| use super::ParquetIoTest; | ||
|
|
||
| /// Without filter pushdown, the scan fetches every column of every row group. | ||
| #[tokio::test] | ||
| async fn test_filter_pushdown_io_disabled() { | ||
| let test = filter_pushdown_test(false).await; | ||
| // Note: in practice, the default 512 KiB metadata size hint often covers | ||
| // the OffsetIndex and Bloom filters for small files, so they wouldn't | ||
| // need separate requests. | ||
| assert_snapshot!(test.query("SELECT * FROM t WHERE val = 7").await, @r" | ||
| --- Query: SELECT * FROM t WHERE val = 7 (1 rows) --- | ||
| Request 1 | ||
| Head (object size and metadata) | ||
| Request 2 | ||
| Read Parquet footer (offsets 489..1402) | ||
| Request 3 | ||
| Read ColumnIndex and OffsetIndex for columns 'id', 'val' (rg 0, 1, 2) (offsets 286..489) | ||
| Request 4 | ||
| Read dictionary page and data page 0 for column 'id' (rg 0) (offsets 4..51) | ||
| Read dictionary page and data page 0 for column 'val' (rg 0) (offsets 51..98) | ||
| Request 5 | ||
| Read dictionary page and data page 0 for column 'id' (rg 1) (offsets 98..145) | ||
| Read dictionary page and data page 0 for column 'val' (rg 1) (offsets 145..192) | ||
| Request 6 | ||
| Read dictionary page and data page 0 for column 'id' (rg 2) (offsets 192..239) | ||
| Read dictionary page and data page 0 for column 'val' (rg 2) (offsets 239..286) | ||
| Produce batch (1 rows) | ||
| "); | ||
| } | ||
|
|
||
| /// **With** filter pushdown, the scan first fetches and evaluates the filter | ||
| /// column (`val`) for each row group, and then fetches the remaining columns | ||
| /// (`id`) only for the row groups with matching rows (row group 1). | ||
| #[tokio::test] | ||
| async fn test_filter_pushdown_io_enabled() { | ||
| let test = filter_pushdown_test(true).await; | ||
| assert_snapshot!(test.query("SELECT * FROM t WHERE val = 7").await, @r" | ||
| --- Query: SELECT * FROM t WHERE val = 7 (1 rows) --- | ||
| Request 1 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do love seeing this difference |
||
| Head (object size and metadata) | ||
| Request 2 | ||
| Read Parquet footer (offsets 489..1402) | ||
| Request 3 | ||
| Read ColumnIndex and OffsetIndex for columns 'id', 'val' (rg 0, 1, 2) (offsets 286..489) | ||
| Request 4 | ||
| Read dictionary page and data page 0 for column 'val' (rg 0) (offsets 51..98) | ||
| Request 5 | ||
| Read dictionary page and data page 0 for column 'val' (rg 1) (offsets 145..192) | ||
| Request 6 | ||
| Read dictionary page for column 'id' (rg 1) (offsets 98..124) | ||
| Read data page 0 for column 'id' (rg 1) (offsets 124..145) | ||
| Produce batch (1 rows) | ||
| Request 7 | ||
| Read dictionary page and data page 0 for column 'val' (rg 2) (offsets 239..286) | ||
| "); | ||
| } | ||
|
|
||
| /// Returns a [`ParquetIoTest`] for the file described in the module docs | ||
| async fn filter_pushdown_test(pushdown_filters: bool) -> ParquetIoTest { | ||
| let mut config = SessionConfig::new().with_target_partitions(1); | ||
| config.options_mut().execution.parquet.pushdown_filters = pushdown_filters; | ||
| ParquetIoTest::new(three_row_group_file(), config).await | ||
| } | ||
|
|
||
| /// Returns the Parquet file described in the module docs: three row groups of | ||
| /// three rows each, with `id` sorted across row groups. | ||
| fn three_row_group_file() -> Bytes { | ||
| let id: ArrayRef = Arc::new(Int32Array::from_iter_values(1..=9)); | ||
| let val: ArrayRef = Arc::new(Int32Array::from(vec![0, 5, 10, 0, 7, 10, 0, 5, 10])); | ||
| let batch = RecordBatch::try_from_iter([("id", id), ("val", val)]).unwrap(); | ||
|
|
||
| let props = WriterProperties::builder() | ||
| .set_max_row_group_row_count(Some(3)) | ||
| .build(); | ||
|
|
||
| let mut buffer = Vec::new(); | ||
| let mut writer = | ||
| ArrowWriter::try_new(&mut buffer, batch.schema(), Some(props)).unwrap(); | ||
| writer.write(&batch).unwrap(); | ||
| writer.close().unwrap(); | ||
| Bytes::from(buffer) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like ascii art