Skip to content

Commit 04b4cd3

Browse files
add parquet footer cache
1 parent b5c6d72 commit 04b4cd3

2 files changed

Lines changed: 80 additions & 4 deletions

File tree

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use datafusion::physical_plan::ExecutionPlan;
3737
use datafusion_datasource::PartitionedFile;
3838
use datafusion_datasource::file_groups::FileGroup;
3939
use datafusion_datasource::file_scan_config::FileScanConfigBuilder;
40+
use datafusion_datasource_parquet::CachedParquetFileReaderFactory;
4041
use datafusion_datasource_parquet::source::ParquetSource;
4142
use datafusion_physical_plan::expressions::Column;
4243
use mini_moka::sync::Cache;
@@ -230,7 +231,7 @@ impl TableProvider for MetricsTableProvider {
230231

231232
async fn scan(
232233
&self,
233-
_state: &dyn Session,
234+
state: &dyn Session,
234235
projection: Option<&Vec<usize>>,
235236
filters: &[Expr],
236237
limit: Option<usize>,
@@ -273,11 +274,20 @@ impl TableProvider for MetricsTableProvider {
273274

274275
// Configure ParquetSource with bloom filters + pushdown enabled
275276
let table_schema: datafusion_datasource::TableSchema = self.schema.clone().into();
277+
let metadata_cache = state.runtime_env().cache_manager.get_file_metadata_cache();
278+
let object_store = state
279+
.runtime_env()
280+
.object_store(self.object_store_url.clone())?;
281+
let reader_factory = Arc::new(CachedParquetFileReaderFactory::new(
282+
object_store,
283+
metadata_cache,
284+
));
276285
let parquet_source = ParquetSource::new(table_schema)
277286
.with_bloom_filter_on_read(true)
278287
.with_pushdown_filters(true)
279288
.with_reorder_filters(true)
280-
.with_enable_page_index(true);
289+
.with_enable_page_index(true)
290+
.with_parquet_file_reader_factory(reader_factory);
281291

282292
// Build the FileScanConfig
283293
let mut builder =
@@ -654,6 +664,7 @@ mod tests {
654664
};
655665

656666
use super::*;
667+
use crate::sources::metrics::test_utils::TestSplitProvider;
657668

658669
fn schema_with_columns(columns: &[&str]) -> SchemaRef {
659670
let fields = columns
@@ -757,6 +768,31 @@ mod tests {
757768
assert_metadata_pruned_split_ids(splits, filters, &[]);
758769
}
759770

771+
#[tokio::test]
772+
async fn scan_installs_cached_parquet_reader_factory() {
773+
let schema = schema_with_columns(&["metric_name", "timestamp_secs", "value"]);
774+
let split_provider = Arc::new(TestSplitProvider::new(Vec::new()));
775+
let provider =
776+
MetricsTableProvider::new(schema, split_provider, Uri::for_test("file:///metrics"))
777+
.unwrap();
778+
let ctx = SessionContext::new();
779+
let state = ctx.state();
780+
781+
let plan = provider.scan(&state, None, &[], None).await.unwrap();
782+
let data_source_exec = plan
783+
.as_any()
784+
.downcast_ref::<DataSourceExec>()
785+
.expect("metrics scan should produce DataSourceExec");
786+
let (_file_scan, parquet_source) = data_source_exec
787+
.downcast_to_file_source::<ParquetSource>()
788+
.expect("metrics scan should use ParquetSource");
789+
790+
assert!(
791+
parquet_source.parquet_file_reader_factory().is_some(),
792+
"metrics scans should use DataFusion's metadata-caching parquet reader"
793+
);
794+
}
795+
760796
#[test]
761797
fn metrics_output_ordering_stops_at_first_missing_sort_key() {
762798
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)