use super::{
AsyncProjectionData, INLINE_KV_VALUE_MAX_BYTES, KvData, Namespace, SecondaryIndex,
SecondaryIndexStore, StoredRow, TableData,
};
use crate::catalog::types::{Row, Value};
use crate::storage::encoded_key::EncodedKey;
use crate::storage::kv_segment::KvSegmentMeta;
use crate::storage::value_store::PersistentValueRef;
fn estimate_row_bytes(row: &Row) -> usize {
const ROW_BASE_OVERHEAD_BYTES: usize = 48;
const VALUE_SLOT_OVERHEAD_BYTES: usize = 16;
row.values
.iter()
.map(estimate_value_bytes)
.sum::<usize>()
.saturating_add(ROW_BASE_OVERHEAD_BYTES)
.saturating_add(row.values.len().saturating_mul(VALUE_SLOT_OVERHEAD_BYTES))
}
pub(crate) fn row_mem_cost(row: &Row) -> usize {
const TABLE_ROW_RESIDENT_FACTOR: usize = 2;
estimate_row_bytes(row)
.saturating_add(32)
.saturating_mul(TABLE_ROW_RESIDENT_FACTOR)
}
pub(crate) fn stored_row_mem_cost(stored: &StoredRow) -> usize {
if let Some(row) = stored.resident() {
row_mem_cost(row)
} else if let Some(value_ref) = stored.value_ref() {
persistent_value_ref_cost(value_ref)
} else {
0
}
}
pub(crate) fn projection_row_mem_cost(row: &Row) -> usize {
estimate_row_bytes(row)
}
pub(crate) fn kv_entry_cost(key_len: usize, value_len: usize) -> usize {
key_len.saturating_add(value_len).saturating_add(24)
}
pub(crate) fn kv_inline_entry_cost(key_len: usize, value_len: usize) -> usize {
if value_len <= INLINE_KV_VALUE_MAX_BYTES {
small_kv_entry_cost(key_len, value_len)
} else {
kv_entry_cost(key_len, value_len)
}
}
pub(crate) fn persistent_value_ref_resident_cost() -> usize {
std::mem::size_of::<PersistentValueRef>().saturating_add(8)
}
pub(crate) fn persistent_value_ref_cost(_value_ref: &PersistentValueRef) -> usize {
persistent_value_ref_resident_cost()
}
pub(crate) fn small_kv_entry_cost(key_len: usize, value_len: usize) -> usize {
key_len.saturating_add(value_len).saturating_add(16)
}
pub(crate) fn kv_tombstone_cost(key_len: usize) -> usize {
key_len.saturating_add(16)
}
pub(crate) fn kv_segment_meta_cost(meta: &KvSegmentMeta) -> usize {
let block_cost = meta
.blocks
.iter()
.map(|block| {
block
.first_key
.len()
.saturating_add(block.last_key.len())
.saturating_add(block.sha256_hex.len())
.saturating_add(64)
})
.sum::<usize>();
meta.filename
.len()
.saturating_add(meta.min_key.len())
.saturating_add(meta.max_key.len())
.saturating_add(
meta.bloom_bits
.len()
.saturating_mul(std::mem::size_of::<u64>()),
)
.saturating_add(block_cost)
.saturating_add(96)
}
pub(crate) const INDEX_POSTING_OVERHEAD_BYTES: usize = 32;
pub(crate) const INDEX_VALUE_OVERHEAD_BYTES: usize = 32;
fn index_postings_set_cost(values: &im::OrdSet<EncodedKey>) -> usize {
values
.iter()
.map(|pk| {
pk.as_slice()
.len()
.saturating_add(INDEX_POSTING_OVERHEAD_BYTES)
})
.sum()
}
pub(crate) fn secondary_index_store_cost(index: &SecondaryIndex) -> usize {
match &index.store {
SecondaryIndexStore::BTree(map) => map
.iter()
.map(|(value, pks)| {
value
.as_slice()
.len()
.saturating_add(INDEX_VALUE_OVERHEAD_BYTES)
.saturating_add(index_postings_set_cost(pks))
})
.sum(),
SecondaryIndexStore::Hash(map) => map
.iter()
.map(|(value, pks)| {
value
.as_slice()
.len()
.saturating_add(INDEX_VALUE_OVERHEAD_BYTES)
.saturating_add(index_postings_set_cost(pks))
})
.sum(),
SecondaryIndexStore::UniqueHash(map) => map
.iter()
.map(|(value, pk)| {
value
.as_slice()
.len()
.saturating_add(pk.as_slice().len())
.saturating_add(INDEX_VALUE_OVERHEAD_BYTES)
})
.sum(),
}
}
pub(crate) fn secondary_index_mem_cost(index: &SecondaryIndex) -> usize {
let segment_cost: usize = index.segments.iter().map(kv_segment_meta_cost).sum();
let tombstone_cost: usize = index
.segment_tombstones
.keys()
.map(|key| key.as_slice().len().saturating_add(16))
.sum();
secondary_index_store_cost(index)
.saturating_add(segment_cost)
.saturating_add(tombstone_cost)
}
pub(crate) fn table_data_mem_cost(t: &TableData) -> usize {
let rows = t.rows.values().map(stored_row_mem_cost).sum::<usize>();
let indexes = t
.indexes
.values()
.map(secondary_index_mem_cost)
.sum::<usize>();
rows.saturating_add(indexes)
}
pub(crate) fn kv_data_mem_cost(kv: &KvData) -> usize {
let normal = kv
.entries
.iter()
.map(|(k, v)| kv_entry_cost(k.len(), v.resident_memory_value_len()))
.sum::<usize>();
let small = kv
.small_entries
.iter()
.map(|(k, v)| small_kv_entry_cost(k.len(), v.resident_value_len()))
.sum::<usize>();
let tombstones = kv
.segment_tombstones
.keys()
.map(|key| kv_tombstone_cost(key.len()))
.sum::<usize>();
let segments = kv.segments.iter().map(kv_segment_meta_cost).sum::<usize>();
normal
.saturating_add(small)
.saturating_add(tombstones)
.saturating_add(segments)
}
pub(crate) fn projection_data_mem_cost(p: &AsyncProjectionData) -> usize {
p.rows.values().map(projection_row_mem_cost).sum()
}
pub(crate) fn namespace_mem_cost(ns: &Namespace) -> usize {
ns.tables
.values()
.map(table_data_mem_cost)
.sum::<usize>()
.saturating_add(kv_data_mem_cost(&ns.kv))
}
fn estimate_value_bytes(v: &Value) -> usize {
match v {
Value::Text(s) | Value::Json(s) => s.len(),
Value::U8(_) => 1,
Value::U64(_) => 8,
Value::Integer(_) | Value::Float(_) | Value::Timestamp(_) => 8,
Value::Boolean(_) => 1,
Value::U256(_) | Value::I256(_) => 32,
Value::Blob(b) => b.len(),
Value::Null => 0,
}
}