Skip to content

Commit 4ed7f92

Browse files
add parquet footer cache
1 parent 32fbda2 commit 4ed7f92

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

quickwit/quickwit-datafusion/src/sources/metrics/table_provider.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use datafusion::physical_plan::ExecutionPlan;
3636
use datafusion_datasource::PartitionedFile;
3737
use datafusion_datasource::file_groups::FileGroup;
3838
use datafusion_datasource::file_scan_config::FileScanConfigBuilder;
39+
use datafusion_datasource_parquet::CachedParquetFileReaderFactory;
3940
use datafusion_datasource_parquet::source::ParquetSource;
4041
use datafusion_physical_plan::expressions::Column;
4142
use quickwit_common::uri::Uri;
@@ -164,7 +165,7 @@ impl TableProvider for MetricsTableProvider {
164165

165166
async fn scan(
166167
&self,
167-
_state: &dyn Session,
168+
state: &dyn Session,
168169
projection: Option<&Vec<usize>>,
169170
filters: &[Expr],
170171
limit: Option<usize>,
@@ -200,11 +201,20 @@ impl TableProvider for MetricsTableProvider {
200201

201202
// Configure ParquetSource with bloom filters + pushdown enabled
202203
let table_schema: datafusion_datasource::TableSchema = self.schema.clone().into();
204+
let metadata_cache = state.runtime_env().cache_manager.get_file_metadata_cache();
205+
let object_store = state
206+
.runtime_env()
207+
.object_store(self.object_store_url.clone())?;
208+
let reader_factory = Arc::new(CachedParquetFileReaderFactory::new(
209+
object_store,
210+
metadata_cache,
211+
));
203212
let parquet_source = ParquetSource::new(table_schema)
204213
.with_bloom_filter_on_read(true)
205214
.with_pushdown_filters(true)
206215
.with_reorder_filters(true)
207-
.with_enable_page_index(true);
216+
.with_enable_page_index(true)
217+
.with_parquet_file_reader_factory(reader_factory);
208218

209219
// Build the FileScanConfig
210220
let mut builder =
@@ -348,8 +358,11 @@ fn splits_have_default_metrics_sort(splits: &[ParquetSplitMetadata]) -> bool {
348358
#[cfg(test)]
349359
mod tests {
350360
use arrow::datatypes::{DataType, Field, Schema};
361+
use datafusion::prelude::SessionContext;
351362
use quickwit_parquet_engine::split::TimeRange;
352363

364+
use crate::sources::metrics::test_utils::TestSplitProvider;
365+
353366
use super::*;
354367

355368
fn schema_with_columns(columns: &[&str]) -> SchemaRef {
@@ -390,6 +403,31 @@ mod tests {
390403
.build()
391404
}
392405

406+
#[tokio::test]
407+
async fn scan_installs_cached_parquet_reader_factory() {
408+
let schema = schema_with_columns(&["metric_name", "timestamp_secs", "value"]);
409+
let split_provider = Arc::new(TestSplitProvider::new(Vec::new()));
410+
let provider =
411+
MetricsTableProvider::new(schema, split_provider, Uri::for_test("file:///metrics"))
412+
.unwrap();
413+
let ctx = SessionContext::new();
414+
let state = ctx.state();
415+
416+
let plan = provider.scan(&state, None, &[], None).await.unwrap();
417+
let data_source_exec = plan
418+
.as_any()
419+
.downcast_ref::<DataSourceExec>()
420+
.expect("metrics scan should produce DataSourceExec");
421+
let (_file_scan, parquet_source) = data_source_exec
422+
.downcast_to_file_source::<ParquetSource>()
423+
.expect("metrics scan should use ParquetSource");
424+
425+
assert!(
426+
parquet_source.parquet_file_reader_factory().is_some(),
427+
"metrics scans should use DataFusion's metadata-caching parquet reader"
428+
);
429+
}
430+
393431
#[test]
394432
fn metrics_output_ordering_stops_at_first_missing_sort_key() {
395433
let schema = schema_with_columns(&["metric_name", "service", "timestamp_secs"]);

quickwit/quickwit-df-core/src/session.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ use crate::task_estimator::DataSourceExecPartitionEstimator;
5656
type CatalogProviderFactory = Arc<dyn Fn() -> Arc<dyn CatalogProvider> + Send + Sync>;
5757
type SchemaProviderFactory = Arc<dyn Fn() -> Arc<dyn SchemaProvider> + Send + Sync>;
5858

59+
/// Default per-node DataFusion cache budget for file-embedded metadata such
60+
/// as Parquet footers and page indexes.
61+
pub const DEFAULT_FILE_METADATA_CACHE_LIMIT_BYTES: usize = 4usize * 1024 * 1024 * 1024;
62+
5963
#[derive(Clone)]
6064
pub(crate) struct CatalogRegistration {
6165
name: String,
@@ -80,6 +84,7 @@ pub struct DataFusionSessionBuilder {
8084
task_estimator: Arc<dyn TaskEstimator + Send + Sync>,
8185
memory_pool: Option<Arc<dyn MemoryPool>>,
8286
object_store_registry: Option<Arc<dyn ObjectStoreRegistry>>,
87+
file_metadata_cache_limit_bytes: usize,
8388
runtime: Arc<RuntimeEnv>,
8489
}
8590

@@ -91,6 +96,10 @@ impl std::fmt::Debug for DataFusionSessionBuilder {
9196
.field("num_catalogs", &self.catalog_registrations.len())
9297
.field("num_schemas", &self.schema_registrations.len())
9398
.field("distributed", &self.worker_resolver.is_some())
99+
.field(
100+
"file_metadata_cache_limit_bytes",
101+
&self.file_metadata_cache_limit_bytes,
102+
)
94103
.finish()
95104
}
96105
}
@@ -103,6 +112,10 @@ impl Default for DataFusionSessionBuilder {
103112

104113
impl DataFusionSessionBuilder {
105114
pub fn new() -> Self {
115+
let runtime = RuntimeEnvBuilder::new()
116+
.with_metadata_cache_limit(DEFAULT_FILE_METADATA_CACHE_LIMIT_BYTES)
117+
.build_arc()
118+
.expect("default DataFusion runtime should build");
106119
Self {
107120
runtime_plugins: Vec::new(),
108121
substrait_extensions: Vec::new(),
@@ -112,12 +125,14 @@ impl DataFusionSessionBuilder {
112125
task_estimator: Arc::new(DataSourceExecPartitionEstimator),
113126
memory_pool: None,
114127
object_store_registry: None,
115-
runtime: Arc::new(RuntimeEnv::default()),
128+
file_metadata_cache_limit_bytes: DEFAULT_FILE_METADATA_CACHE_LIMIT_BYTES,
129+
runtime,
116130
}
117131
}
118132

119133
fn rebuild_runtime(&mut self) -> DFResult<()> {
120-
let mut builder = RuntimeEnvBuilder::new();
134+
let mut builder = RuntimeEnvBuilder::new()
135+
.with_metadata_cache_limit(self.file_metadata_cache_limit_bytes);
121136
if let Some(memory_pool) = &self.memory_pool {
122137
builder = builder.with_memory_pool(Arc::clone(memory_pool));
123138
}
@@ -176,6 +191,12 @@ impl DataFusionSessionBuilder {
176191
Ok(self)
177192
}
178193

194+
pub fn with_file_metadata_cache_limit(mut self, bytes: usize) -> DFResult<Self> {
195+
self.file_metadata_cache_limit_bytes = bytes;
196+
self.rebuild_runtime()?;
197+
Ok(self)
198+
}
199+
179200
pub fn with_object_store_registry(
180201
mut self,
181202
registry: Arc<dyn ObjectStoreRegistry>,
@@ -411,6 +432,21 @@ mod tests {
411432
}
412433
}
413434

435+
#[test]
436+
fn file_metadata_cache_limit_defaults_to_four_gib_and_can_be_overridden() {
437+
let builder = DataFusionSessionBuilder::new();
438+
assert_eq!(
439+
builder.runtime().cache_manager.get_metadata_cache_limit(),
440+
DEFAULT_FILE_METADATA_CACHE_LIMIT_BYTES
441+
);
442+
443+
let builder = builder.with_file_metadata_cache_limit(8 * 1024).unwrap();
444+
assert_eq!(
445+
builder.runtime().cache_manager.get_metadata_cache_limit(),
446+
8 * 1024
447+
);
448+
}
449+
414450
#[test]
415451
fn runtime_settings_compose_and_reinitialize_sources() {
416452
let source_url = Url::parse("test://source").unwrap();
@@ -442,6 +478,10 @@ mod tests {
442478
.and_then(|entry| entry.value),
443479
Some("2K".to_string())
444480
);
481+
assert_eq!(
482+
builder.runtime().cache_manager.get_metadata_cache_limit(),
483+
DEFAULT_FILE_METADATA_CACHE_LIMIT_BYTES
484+
);
445485
assert!(
446486
builder
447487
.runtime()

0 commit comments

Comments
 (0)