Skip to content

Commit 8126d0b

Browse files
committed
Skip redundant prefix pre-check for point reads when whole_key_filtering is enabled
When whole_key_filtering is true (the default), the filter contains full-key hashes. For point reads, the full-key Bloom is strictly more precise than the prefix pre-check — so skip the prefix probe entirely and go straight to the Bloom. This eliminates a redundant filter probe on every point read that was costing ~10% throughput on point-read-heavy workloads. The prefix filter now only activates for range/prefix scans, where it provides table-level pruning that the full-key Bloom cannot. When whole_key_filtering is false, point reads still use the prefix filter as the sole pre-check since no full-key hashes are available. Feed workload (point-read heavy): - Before: -9.5% regression vs no prefix extractor - After: +7.7% improvement vs no prefix extractor, -69.6% disk reads Column store (scan-only): +4.7% improvement, consistent with previous
1 parent c811f8b commit 8126d0b

1 file changed

Lines changed: 22 additions & 20 deletions

File tree

src/tree/mod.rs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -872,32 +872,34 @@ impl Tree {
872872

873873
if allow_filter {
874874
if let Some(ex) = config.prefix_extractor.as_ref() {
875-
// If prefix filtering is allowed and an extractor is configured,
876-
// consult the prefix filter first for a coarse table-level check.
877-
let probe = table.maybe_contains_prefix(key, ex.as_ref())?;
878-
879-
#[cfg(feature = "metrics")]
880-
if probe.is_some() {
881-
use std::sync::atomic::Ordering::Relaxed;
882-
table.metrics.filter_queries.fetch_add(1, Relaxed);
883-
}
875+
if config.whole_key_filtering {
876+
// When whole_key_filtering is enabled, the filter contains
877+
// full-key hashes. The full-key Bloom is strictly more
878+
// precise than the prefix pre-check for point reads, so
879+
// skip the prefix check entirely and go straight to the
880+
// Bloom. This avoids a redundant filter probe on every
881+
// point read.
882+
} else {
883+
// Without whole_key_filtering, the filter only has prefix
884+
// hashes. Use the prefix filter as the sole pre-check.
885+
let probe = table.maybe_contains_prefix(key, ex.as_ref())?;
884886

885-
if probe == Some(false) {
886887
#[cfg(feature = "metrics")]
887-
{
888+
if probe.is_some() {
888889
use std::sync::atomic::Ordering::Relaxed;
889-
table.metrics.io_skipped_by_filter.fetch_add(1, Relaxed);
890+
table.metrics.filter_queries.fetch_add(1, Relaxed);
890891
}
891892

892-
return Ok(None);
893-
}
893+
if probe == Some(false) {
894+
#[cfg(feature = "metrics")]
895+
{
896+
use std::sync::atomic::Ordering::Relaxed;
897+
table.metrics.io_skipped_by_filter.fetch_add(1, Relaxed);
898+
}
899+
900+
return Ok(None);
901+
}
894902

895-
// Prefix is maybe present. When whole_key_filtering is enabled,
896-
// fall through to the full-key Bloom for a precise per-key check
897-
// (the filter contains both prefix and full-key hashes).
898-
// When disabled, the filter only has prefix hashes, so go
899-
// directly to the data blocks.
900-
if !config.whole_key_filtering {
901903
return table.get_without_filter(key, seqno);
902904
}
903905
}

0 commit comments

Comments
 (0)