Skip to content

Commit 716434d

Browse files
committed
parallel retain files
1 parent a0c8444 commit 716434d

1 file changed

Lines changed: 34 additions & 6 deletions

File tree

src/query/stream_schema_provider.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use datafusion::{
4646
};
4747
use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
4848
use itertools::Itertools;
49+
use rayon::prelude::*;
4950

5051
/// Number of manifest files fetched from the metastore in parallel while
5152
/// planning a query.
@@ -572,19 +573,46 @@ async fn collect_from_snapshot(
572573
.try_collect::<Vec<_>>()
573574
.await?;
574575

575-
let mut manifest_files: Vec<_> = manifest_files
576+
let manifest_files: Vec<_> = manifest_files
576577
.into_iter()
577578
.flatten()
578579
.flat_map(|file| file.files)
579580
.rev()
580581
.collect();
581582
let files_before_prune = manifest_files.len();
582-
let mut pruned_by_filter: Vec<(String, usize)> = Vec::new();
583-
for filter in filters {
584-
let before = manifest_files.len();
585-
manifest_files.retain(|file| !file.can_be_pruned(filter));
586-
pruned_by_filter.push((filter.to_string(), before - manifest_files.len()));
583+
584+
// One parallel pass rather than a `retain` per filter. Every
585+
// `can_be_pruned` call linearly scans the file's column list (hundreds of
586+
// entries on wide streams), so a sequential pass per filter over tens of
587+
// thousands of files is hundreds of milliseconds of single threaded work.
588+
// `position` keeps attribution identical to the sequential chain: a file is
589+
// credited to the first filter that would have dropped it.
590+
let prune_reason: Vec<Option<usize>> = manifest_files
591+
.par_iter()
592+
.map(|file| filters.iter().position(|filter| file.can_be_pruned(filter)))
593+
.collect();
594+
595+
let mut pruned_counts = vec![0usize; filters.len()];
596+
for reason in prune_reason.iter().flatten() {
597+
pruned_counts[*reason] += 1;
587598
}
599+
let pruned_by_filter: Vec<(String, usize)> = filters
600+
.iter()
601+
.zip(pruned_counts)
602+
.map(|(filter, count)| (filter.to_string(), count))
603+
.collect();
604+
605+
// Parallel because of the drops, not the filtering. Pruning discards the
606+
// overwhelming majority of files, and every discarded `File` owns a `Vec`
607+
// of hundreds of `Column`s, each with its own heap allocated name and
608+
// stats. Tens of millions of frees on one thread is hundreds of
609+
// milliseconds; `into_par_iter` spreads them. Order is preserved — the
610+
// `rev()` above establishes the ordering the limit below depends on.
611+
let mut manifest_files: Vec<_> = manifest_files
612+
.into_par_iter()
613+
.zip(prune_reason)
614+
.filter_map(|(file, reason)| reason.is_none().then_some(file))
615+
.collect();
588616
tracing::warn!(
589617
stream = %stream_name,
590618
files_before_prune,

0 commit comments

Comments
 (0)