use std::{
num::NonZero,
sync::{
Arc, LazyLock, OnceLock,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
use crate::{
data_file::usize_as_u64,
error::{Error, Result},
telemetry::nanoseconds,
};
const INDEX_ENCODING_CONCURRENCY_BOUNDS: (usize, usize) = (4, 32);
pub(super) fn index_encoding_concurrency() -> usize {
static CONCURRENCY: OnceLock<usize> = OnceLock::new();
*CONCURRENCY.get_or_init(|| {
let (floor, ceiling) = INDEX_ENCODING_CONCURRENCY_BOUNDS;
std::thread::available_parallelism()
.map_or(floor, NonZero::get)
.clamp(floor, ceiling)
})
}
static INDEX_ENCODING_PERMITS: LazyLock<Arc<tokio::sync::Semaphore>> =
LazyLock::new(|| Arc::new(tokio::sync::Semaphore::new(index_encoding_concurrency())));
#[derive(Debug, Default)]
pub(crate) struct DataStoreCounters {
gets: AtomicU64,
bytes: AtomicU64,
}
impl DataStoreCounters {
pub(crate) fn gets(&self) -> u64 {
self.gets.load(Ordering::Relaxed)
}
pub(crate) fn bytes(&self) -> u64 {
self.bytes.load(Ordering::Relaxed)
}
}
#[derive(Default)]
pub(crate) struct ScopedReadMetrics {
upstream: Option<Arc<DataStoreCounters>>,
metadata_hits: AtomicU64,
metadata_misses: AtomicU64,
range_fetches: AtomicU64,
ranges: AtomicU64,
range_bytes: AtomicU64,
range_nanoseconds: AtomicU64,
encode_nanoseconds: AtomicU64,
parquet_files: AtomicU64,
inline_chunks: AtomicU64,
arrow_batches: AtomicU64,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct ScopedReadTally {
pub(crate) metadata_hits: u64,
pub(crate) metadata_misses: u64,
pub(crate) range_fetches: u64,
pub(crate) ranges: u64,
pub(crate) range_bytes: u64,
pub(crate) range_duration: Duration,
pub(crate) encode_duration: Duration,
pub(crate) parquet_files: u64,
pub(crate) inline_chunks: u64,
pub(crate) arrow_batches: u64,
}
impl ScopedReadMetrics {
pub(crate) fn reporting_to(upstream: Arc<DataStoreCounters>) -> Self {
Self {
upstream: Some(upstream),
..Self::default()
}
}
pub(super) fn metadata_hit(&self) {
self.metadata_hits.fetch_add(1, Ordering::Relaxed);
}
pub(super) fn metadata_miss(&self) {
self.metadata_misses.fetch_add(1, Ordering::Relaxed);
}
pub(super) fn range_read(&self, ranges: usize, bytes: u64, duration: Duration) {
self.range_fetches.fetch_add(1, Ordering::Relaxed);
self.ranges
.fetch_add(usize_as_u64(ranges), Ordering::Relaxed);
self.range_bytes.fetch_add(bytes, Ordering::Relaxed);
self.range_nanoseconds
.fetch_add(nanoseconds(duration), Ordering::Relaxed);
if let Some(upstream) = &self.upstream {
upstream
.gets
.fetch_add(usize_as_u64(ranges), Ordering::Relaxed);
upstream.bytes.fetch_add(bytes, Ordering::Relaxed);
}
}
pub(crate) fn encoded(&self, duration: Duration) {
self.encode_nanoseconds
.fetch_add(nanoseconds(duration), Ordering::Relaxed);
}
pub(super) fn parquet_file(&self) {
self.parquet_files.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn inline_chunk(&self) {
self.inline_chunks.fetch_add(1, Ordering::Relaxed);
}
pub(super) fn arrow_batch(&self) {
self.arrow_batches.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn tally(&self) -> ScopedReadTally {
ScopedReadTally {
metadata_hits: self.metadata_hits.load(Ordering::Relaxed),
metadata_misses: self.metadata_misses.load(Ordering::Relaxed),
range_fetches: self.range_fetches.load(Ordering::Relaxed),
ranges: self.ranges.load(Ordering::Relaxed),
range_bytes: self.range_bytes.load(Ordering::Relaxed),
range_duration: Duration::from_nanos(self.range_nanoseconds.load(Ordering::Relaxed)),
encode_duration: Duration::from_nanos(self.encode_nanoseconds.load(Ordering::Relaxed)),
parquet_files: self.parquet_files.load(Ordering::Relaxed),
inline_chunks: self.inline_chunks.load(Ordering::Relaxed),
arrow_batches: self.arrow_batches.load(Ordering::Relaxed),
}
}
}
pub(crate) async fn run_bounded_index_encoding<T, F>(work: F) -> Result<T>
where
T: Send + 'static,
F: FnOnce() -> Result<T> + Send + 'static,
{
let permit = Arc::clone(&INDEX_ENCODING_PERMITS)
.acquire_owned()
.await
.map_err(|_| {
Error::Interrupted("index encoding limiter stopped before work began".to_owned())
})?;
tokio::task::spawn_blocking(move || {
let _permit = permit;
work()
})
.await
.map_err(|error| Error::Interrupted(format!("index encoding worker stopped: {error}")))?
}