Skip to content
Merged
114 changes: 110 additions & 4 deletions src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ pub struct Metrics {
/// Number of blocks that were read from block cache
pub(crate) data_block_load_cached: AtomicUsize,

/// Number of range tombstone blocks that were actually read from disk
pub(crate) range_tombstone_block_load_io: AtomicUsize,

/// Number of range tombstone blocks that were read from block cache
pub(crate) range_tombstone_block_load_cached: AtomicUsize,

/// Number of filter queries that were performed
pub(crate) filter_queries: AtomicUsize,

Expand All @@ -48,6 +54,9 @@ pub struct Metrics {

/// Number of filter block bytes that were requested from OS or disk
pub(crate) filter_block_io_requested: AtomicU64,

/// Number of range tombstone block bytes that were requested from OS or disk
pub(crate) range_tombstone_block_io_requested: AtomicU64,
}

#[expect(
Expand Down Expand Up @@ -82,11 +91,17 @@ impl Metrics {
self.filter_block_io_requested.load(Relaxed)
}

/// Number of I/O range tombstone block bytes transferred from disk or OS page cache.
pub fn range_tombstone_block_io(&self) -> u64 {
self.range_tombstone_block_io_requested.load(Relaxed)
}

/// Number of I/O block bytes transferred from disk or OS page cache.
pub fn block_io(&self) -> u64 {
self.data_block_io_requested.load(Relaxed)
+ self.index_block_io_requested.load(Relaxed)
+ self.filter_block_io_requested.load(Relaxed)
+ self.range_tombstone_block_io_requested.load(Relaxed)
}
Comment thread
polaz marked this conversation as resolved.

/// Number of data blocks that were accessed.
Expand All @@ -104,33 +119,46 @@ impl Metrics {
self.filter_block_load_cached.load(Relaxed) + self.filter_block_load_io.load(Relaxed)
}

/// Number of range tombstone blocks that were accessed.
pub fn range_tombstone_block_load_count(&self) -> usize {
self.range_tombstone_block_load_cached.load(Relaxed)
+ self.range_tombstone_block_load_io.load(Relaxed)
}

/// Number of blocks that were loaded from disk or OS page cache.
pub fn block_load_io_count(&self) -> usize {
self.data_block_load_io.load(Relaxed)
+ self.index_block_load_io.load(Relaxed)
+ self.filter_block_load_io.load(Relaxed)
+ self.range_tombstone_block_load_io.load(Relaxed)
}

/// Number of data blocks that were loaded from disk or OS page cache.
/// Number of data blocks that were served from block cache.
pub fn data_block_load_cached_count(&self) -> usize {
self.data_block_load_cached.load(Relaxed)
}

/// Number of index blocks that were loaded from disk or OS page cache.
/// Number of index blocks that were served from block cache.
pub fn index_block_load_cached_count(&self) -> usize {
self.index_block_load_cached.load(Relaxed)
}

/// Number of filter blocks that were loaded from disk or OS page cache.
/// Number of filter blocks that were served from block cache.
pub fn filter_block_load_cached_count(&self) -> usize {
self.filter_block_load_cached.load(Relaxed)
}

/// Number of blocks that were loaded from disk or OS page cache.
/// Number of range tombstone blocks that were served from block cache.
pub fn range_tombstone_block_load_cached_count(&self) -> usize {
self.range_tombstone_block_load_cached.load(Relaxed)
}

/// Number of blocks that were served from block cache.
pub fn block_load_cached_count(&self) -> usize {
self.data_block_load_cached.load(Relaxed)
+ self.index_block_load_cached.load(Relaxed)
+ self.filter_block_load_cached.load(Relaxed)
+ self.range_tombstone_block_load_cached.load(Relaxed)
}

/// Number of blocks that were accessed.
Expand Down Expand Up @@ -174,6 +202,18 @@ impl Metrics {
}
}

/// Range tombstone block cache efficiency in percent (0.0 - 1.0).
pub fn range_tombstone_block_cache_hit_rate(&self) -> f64 {
let queries = self.range_tombstone_block_load_count() as f64;
let hits = self.range_tombstone_block_load_cached_count() as f64;

if queries == 0.0 {
1.0
} else {
hits / queries
}
}

/// Block cache efficiency in percent (0.0 - 1.0).
pub fn block_cache_hit_rate(&self) -> f64 {
let queries = self.block_loads() as f64;
Expand Down Expand Up @@ -210,3 +250,69 @@ impl Metrics {
self.io_skipped_by_filter.load(Relaxed)
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering::Relaxed;

#[test]
fn range_tombstone_counters_default_zero() {
let m = Metrics::default();
assert_eq!(0, m.range_tombstone_block_load_count());
assert_eq!(0, m.range_tombstone_block_load_cached_count());
assert_eq!(0, m.range_tombstone_block_io());
}

#[test]
fn range_tombstone_block_load_count_sums_cached_and_io() {
let m = Metrics::default();
m.range_tombstone_block_load_cached.store(3, Relaxed);
m.range_tombstone_block_load_io.store(7, Relaxed);
assert_eq!(10, m.range_tombstone_block_load_count());
}

#[test]
fn range_tombstone_cache_hit_rate_no_loads_returns_one() {
let m = Metrics::default();
assert!((m.range_tombstone_block_cache_hit_rate() - 1.0).abs() < f64::EPSILON);
}

#[test]
fn range_tombstone_cache_hit_rate_mixed_loads() {
let m = Metrics::default();
m.range_tombstone_block_load_cached.store(3, Relaxed);
m.range_tombstone_block_load_io.store(1, Relaxed);
assert!((m.range_tombstone_block_cache_hit_rate() - 0.75).abs() < f64::EPSILON);
}

#[test]
fn block_io_includes_range_tombstone() {
let m = Metrics::default();
m.data_block_io_requested.store(10, Relaxed);
m.index_block_io_requested.store(20, Relaxed);
m.filter_block_io_requested.store(30, Relaxed);
m.range_tombstone_block_io_requested.store(40, Relaxed);
assert_eq!(100, m.block_io());
}

#[test]
fn block_load_io_count_includes_range_tombstone() {
let m = Metrics::default();
m.data_block_load_io.store(1, Relaxed);
m.index_block_load_io.store(2, Relaxed);
m.filter_block_load_io.store(3, Relaxed);
m.range_tombstone_block_load_io.store(4, Relaxed);
assert_eq!(10, m.block_load_io_count());
}

#[test]
fn block_load_cached_count_includes_range_tombstone() {
let m = Metrics::default();
m.data_block_load_cached.store(5, Relaxed);
m.index_block_load_cached.store(6, Relaxed);
m.filter_block_load_cached.store(7, Relaxed);
m.range_tombstone_block_load_cached.store(8, Relaxed);
assert_eq!(26, m.block_load_cached_count());
}
}
108 changes: 108 additions & 0 deletions src/table/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,3 +1429,111 @@ fn table_global_seqno() -> crate::Result<()> {

Ok(())
}

/// Exercises the `load_block` cache-miss and cache-hit paths for
/// `BlockType::RangeTombstone`, verifying that the dedicated RT metrics
/// counters are incremented instead of the data-block counters.
#[test]
#[cfg(feature = "metrics")]
fn load_block_range_tombstone_metrics() -> crate::Result<()> {
use crate::{
cache::Cache,
descriptor_table::DescriptorTable,
range_tombstone::RangeTombstone,
table::{block::BlockType, util::load_block},
CompressionType,
};
use std::sync::atomic::Ordering::Relaxed;

let dir = tempdir()?;
let file = dir.path().join("table");

// Build a table that contains a range tombstone block.
let mut writer = Writer::new(file.clone(), 0, 0)?;
writer.write(InternalValue::from_components(
b"a",
b"v1",
1,
crate::ValueType::Value,
))?;
writer.write(InternalValue::from_components(
b"z",
b"v2",
2,
crate::ValueType::Value,
))?;
writer.write_range_tombstone(RangeTombstone::new(b"b".into(), b"y".into(), 3));
#[expect(
clippy::unwrap_used,
reason = "finish() returns Some after writing data items"
)]
let (_, checksum) = writer.finish()?.unwrap();
Comment thread
polaz marked this conversation as resolved.

let metrics = Arc::new(crate::metrics::Metrics::default());

let table = Table::recover(
file,
checksum,
0,
0,
// Recovery bypasses load_block() (reads via Block::from_file() directly),
// so it intentionally does NOT increment block-load metrics — consistent
// with how filter and index recovery reads are handled.
Arc::new(Cache::with_capacity_bytes(10_000_000)),
Some(Arc::new(DescriptorTable::new(10))),
false,
false,
#[cfg(feature = "metrics")]
metrics.clone(),
)?;

let rt_handle = table
.regions
.range_tombstones
.expect("table should have range tombstone block");

let table_id = table.global_id();

// Recovery does NOT increment block-load counters (bypasses load_block).
assert_eq!(0, metrics.range_tombstone_block_load_io.load(Relaxed));

// Use a fresh cache so the first load_block() call is a cache miss.
let fresh_cache = Arc::new(Cache::with_capacity_bytes(10_000_000));

// load_block cache miss → IO path
let _block = load_block(
table_id,
&table.path,
&table.file_accessor,
&fresh_cache,
&rt_handle,
BlockType::RangeTombstone,
CompressionType::None,
#[cfg(feature = "metrics")]
&metrics,
)?;

assert_eq!(1, metrics.range_tombstone_block_load_io.load(Relaxed));
assert_eq!(0, metrics.range_tombstone_block_load_cached.load(Relaxed));
assert!(metrics.range_tombstone_block_io_requested.load(Relaxed) > 0);
assert_eq!(0, metrics.data_block_load_io.load(Relaxed));

// load_block cache hit (block was inserted into fresh_cache by previous call)
let _block = load_block(
table_id,
&table.path,
&table.file_accessor,
&fresh_cache,
&rt_handle,
BlockType::RangeTombstone,
CompressionType::None,
#[cfg(feature = "metrics")]
&metrics,
)?;

assert_eq!(1, metrics.range_tombstone_block_load_io.load(Relaxed));
assert_eq!(1, metrics.range_tombstone_block_load_cached.load(Relaxed));
assert_eq!(0, metrics.data_block_load_cached.load(Relaxed));

Ok(())
}
19 changes: 14 additions & 5 deletions src/table/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ pub fn load_block(
BlockType::Index => {
metrics.index_block_load_cached.fetch_add(1, Relaxed);
}
// TODO(#34): RangeTombstone counted under data_block metrics — add
// dedicated range_tombstone_block_load_cached/miss counters
BlockType::Data | BlockType::Meta | BlockType::RangeTombstone => {
BlockType::RangeTombstone => {
metrics
.range_tombstone_block_load_cached
.fetch_add(1, Relaxed);
}
BlockType::Data | BlockType::Meta => {
metrics.data_block_load_cached.fetch_add(1, Relaxed);
}
}
Expand Down Expand Up @@ -109,8 +112,14 @@ pub fn load_block(
.index_block_io_requested
.fetch_add(handle.size().into(), Relaxed);
}
// TODO(#34): same as above — RangeTombstone uses data_block IO counters
BlockType::Data | BlockType::Meta | BlockType::RangeTombstone => {
BlockType::RangeTombstone => {
metrics.range_tombstone_block_load_io.fetch_add(1, Relaxed);

metrics
.range_tombstone_block_io_requested
.fetch_add(handle.size().into(), Relaxed);
}
BlockType::Data | BlockType::Meta => {
metrics.data_block_load_io.fetch_add(1, Relaxed);

metrics
Expand Down
Loading