Skip to content

Commit f38096a

Browse files
authored
[datafusion] Support runtime filters for Paimon scans (#549)
1 parent 8265068 commit f38096a

26 files changed

Lines changed: 1324 additions & 73 deletions

benchmarks/tpcds/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ target/release/paimon-tpcds-bench run \
164164
--warmup 1 \
165165
--iterations 3 \
166166
--target-partitions 64 \
167+
--parquet-pushdown-filters \
167168
--memory-limit-gib 192 \
168169
--spill-dir /nvme/datafusion-spill \
169170
--max-spill-gib 1024
@@ -173,6 +174,10 @@ This is an end-to-end source comparison. Loading the data into Paimon rewrites
173174
the physical files, so it is not a pure measurement of catalog or manifest
174175
overhead.
175176

177+
`--parquet-pushdown-filters` only controls DataFusion's Parquet reader. Paimon
178+
always receives supported predicates for conservative pruning, while exact row
179+
filtering stays in the parent DataFusion operator for benchmark runs.
180+
176181
## Cache Protocol
177182

178183
Run and label cold and warm experiments separately:

benchmarks/tpcds/src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ pub struct RuntimeArgs {
113113
/// DataFusion execution partitions. Defaults to available CPUs.
114114
#[arg(long)]
115115
pub target_partitions: Option<usize>,
116+
/// Evaluate pushed filters during Parquet scans, in addition to statistics pruning.
117+
#[arg(long)]
118+
pub parquet_pushdown_filters: bool,
116119
/// DataFusion memory limit in GiB. Omit for an unbounded pool.
117120
#[arg(long)]
118121
pub memory_limit_gib: Option<u64>,
@@ -132,6 +135,7 @@ impl RuntimeArgs {
132135
.target_partitions
133136
.unwrap_or(defaults.target_partitions)
134137
.max(1),
138+
parquet_pushdown_filters: self.parquet_pushdown_filters,
135139
memory_limit_bytes: self.memory_limit_gib.map(gib_to_usize).transpose()?,
136140
spill_dir: self.spill_dir.clone(),
137141
max_spill_bytes: self.max_spill_gib.map(gib_to_u64).transpose()?,

benchmarks/tpcds/src/context.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use serde::{Deserialize, Serialize};
2828
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2929
pub struct BenchmarkRuntimeConfig {
3030
pub target_partitions: usize,
31+
#[serde(default)]
32+
pub parquet_pushdown_filters: bool,
3133
pub memory_limit_bytes: Option<usize>,
3234
pub spill_dir: Option<PathBuf>,
3335
pub max_spill_bytes: Option<u64>,
@@ -39,6 +41,7 @@ impl Default for BenchmarkRuntimeConfig {
3941
target_partitions: std::thread::available_parallelism()
4042
.map(usize::from)
4143
.unwrap_or(1),
44+
parquet_pushdown_filters: false,
4245
memory_limit_bytes: None,
4346
spill_dir: None,
4447
max_spill_bytes: None,
@@ -60,10 +63,15 @@ pub fn build_sql_context(config: &BenchmarkRuntimeConfig) -> DataFusionResult<SQ
6063
let sql = SQLContext::new();
6164
let state_ref = sql.ctx().state_ref();
6265
let current_state = state_ref.read().clone();
63-
let session_config = current_state
66+
let mut session_config = current_state
6467
.config()
6568
.clone()
6669
.with_target_partitions(config.target_partitions.max(1));
70+
session_config
71+
.options_mut()
72+
.execution
73+
.parquet
74+
.pushdown_filters = config.parquet_pushdown_filters;
6775
let state = SessionStateBuilder::from(current_state)
6876
.with_config(session_config)
6977
.with_runtime_env(Arc::new(runtime.build()?))

benchmarks/tpcds/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ mod tests {
183183
let spill_dir = TempDir::new().unwrap();
184184
let ctx = build_sql_context(&BenchmarkRuntimeConfig {
185185
target_partitions: 3,
186+
parquet_pushdown_filters: true,
186187
memory_limit_bytes: Some(32 * 1024 * 1024),
187188
spill_dir: Some(spill_dir.path().to_path_buf()),
188189
max_spill_bytes: Some(64 * 1024 * 1024),
@@ -197,6 +198,14 @@ mod tests {
197198
.target_partitions,
198199
3
199200
);
201+
assert!(
202+
ctx.ctx()
203+
.state()
204+
.config_options()
205+
.execution
206+
.parquet
207+
.pushdown_filters
208+
);
200209
assert!(matches!(
201210
ctx.ctx().runtime_env().memory_pool.memory_limit(),
202211
MemoryLimit::Finite(size) if size == 32 * 1024 * 1024

benchmarks/tpcds/tests/smoke.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ async fn command_orchestration_loads_and_writes_a_run_report() {
225225
.unwrap();
226226
let runtime = RuntimeArgs {
227227
target_partitions: Some(2),
228+
parquet_pushdown_filters: false,
228229
memory_limit_gib: None,
229230
spill_dir: None,
230231
max_spill_gib: None,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use datafusion::common::config::ConfigExtension;
19+
use datafusion::common::{config_namespace, extensions_options};
20+
21+
pub const PAIMON_ROW_FILTER: &str = "paimon.read.row_filter";
22+
23+
config_namespace! {
24+
/// Paimon read options.
25+
pub struct PaimonReadOptions {
26+
/// Apply pushed predicates as row filters inside Paimon readers.
27+
pub row_filter: bool, default = false
28+
}
29+
}
30+
31+
extensions_options! {
32+
/// Paimon-specific DataFusion session options.
33+
pub struct PaimonConfig {
34+
/// Options that control Paimon reads.
35+
pub read: PaimonReadOptions, default = PaimonReadOptions::default()
36+
}
37+
}
38+
39+
impl ConfigExtension for PaimonConfig {
40+
const PREFIX: &'static str = "paimon";
41+
}
42+
43+
#[cfg(test)]
44+
mod tests {
45+
use datafusion::config::ConfigOptions;
46+
47+
use super::*;
48+
49+
#[test]
50+
fn paimon_row_filter_defaults_to_false_and_can_be_set() {
51+
let mut options = ConfigOptions::default();
52+
options.extensions.insert(PaimonConfig::default());
53+
54+
let config = options.extensions.get::<PaimonConfig>().unwrap();
55+
assert!(!config.read.row_filter);
56+
57+
options.set("paimon.read.row_filter", "true").unwrap();
58+
let config = options.extensions.get::<PaimonConfig>().unwrap();
59+
assert!(config.read.row_filter);
60+
}
61+
}

crates/integrations/datafusion/src/filter_pushdown.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ fn reverse_comparison_operator(op: Operator) -> Option<Operator> {
410410
}
411411
}
412412

413-
fn scalar_to_datum(scalar: &ScalarValue, data_type: &DataType) -> Option<Datum> {
413+
pub(crate) fn scalar_to_datum(scalar: &ScalarValue, data_type: &DataType) -> Option<Datum> {
414414
match data_type {
415415
DataType::Boolean(_) => match scalar {
416416
ScalarValue::Boolean(Some(value)) => Some(Datum::Bool(*value)),

crates/integrations/datafusion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ mod blob_descriptor_functions;
4040
mod blob_reader;
4141
mod blob_view;
4242
mod catalog;
43+
pub mod config;
4344
mod delete;
4445
mod error;
4546
mod filter_pushdown;

0 commit comments

Comments
 (0)