mod iterator;
pub use iterator::TableIterator;
use std::{
fs::File,
io::{Read, Seek, SeekFrom},
path::Path,
sync::{Arc, OnceLock},
};
use parking_lot::{Mutex, MutexGuard};
use crate::cache::block_cache::BlockCache;
use crate::error::{Error, Result, ResultExt};
use crate::iterator::range_del::FragmentedRangeTombstoneList;
use crate::sst::block::Block;
use crate::sst::filter::BloomFilter;
use crate::sst::format::{
BLOCK_TRAILER_SIZE, BlockHandle, CompressionType, FOOTER_SIZE, PREFIX_FILTER_LEN_NAME,
RANGE_DEL_BLOCK_NAME, decode_footer, decode_index_value_with_props,
};
use crate::stats::DbStats;
use crate::types::{InternalKeyRef, SequenceNumber, ValueType, compare_internal_key};
type RangeTombstoneEntry = (Vec<u8>, Vec<u8>, SequenceNumber);
pub(crate) const MAX_DECOMPRESSED_BLOCK_SIZE: usize = 64 * 1024 * 1024;
pub struct IndexEntry {
pub separator_key: Vec<u8>,
pub handle: BlockHandle,
pub first_key: Option<Vec<u8>>,
pub properties: Vec<(Vec<u8>, Vec<u8>)>,
}
type IndexEntries = Arc<Vec<IndexEntry>>;
struct MetaIndexData {
bloom: Option<Vec<u8>>,
prefix: Option<Vec<u8>>,
prefix_len: Option<usize>,
range_del_handle: Option<BlockHandle>,
}
pub struct TableReader {
file_number: u64,
index_block: Block,
filter_data: Option<Vec<u8>>,
prefix_filter_data: Option<Vec<u8>>,
prefix_filter_len: Option<usize>,
file: Mutex<File>,
block_cache: Option<Arc<BlockCache>>,
stats: Option<Arc<DbStats>>,
index_entry_cache: OnceLock<IndexEntries>,
range_tombstone_cache: OnceLock<Arc<FragmentedRangeTombstoneList>>,
range_del_handle: Option<BlockHandle>,
}
impl TableReader {
pub fn open(path: &Path) -> Result<Self> {
Self::open_with_all(path, 0, None, None)
}
pub fn open_with_all(
path: &Path,
file_number: u64,
block_cache: Option<Arc<BlockCache>>,
stats: Option<Arc<DbStats>>,
) -> Result<Self> {
let mut file = File::open(path).ctx()?;
let file_size = file.metadata().ctx()?.len();
if file_size < FOOTER_SIZE as u64 {
return Err(Error::corruption(format!(
"SST file too small: {} bytes",
file_size
)));
}
file.seek(SeekFrom::End(-(FOOTER_SIZE as i64))).ctx()?;
let mut footer_buf = [0u8; FOOTER_SIZE];
file.read_exact(&mut footer_buf).ctx()?;
let (metaindex_handle, index_handle) = decode_footer(&footer_buf).ctx()?;
let index_data =
Self::read_block_data_with_size(&mut file, &index_handle, file_size).ctx()?;
let index_block = Block::from_vec(index_data).ctx()?;
let meta = Self::read_metaindex(&mut file, &metaindex_handle, file_size).ctx()?;
Ok(Self {
file_number,
index_block,
filter_data: meta.bloom,
prefix_filter_data: meta.prefix,
prefix_filter_len: meta.prefix_len,
file: Mutex::new(file),
block_cache,
stats,
index_entry_cache: OnceLock::new(),
range_tombstone_cache: OnceLock::new(),
range_del_handle: meta.range_del_handle,
})
}
pub fn cached_index_entries(&self) -> Result<IndexEntries> {
if let Some(cached) = self.index_entry_cache.get() {
return Ok(cached.clone());
}
let entries = Arc::new(Self::parse_index_entries(&self.index_block)?);
let _ = self.index_entry_cache.set(entries.clone());
Ok(entries)
}
fn parse_index_entries(index_block: &Block) -> Result<Vec<IndexEntry>> {
index_block
.iter()
.map(|(k, v)| {
let d = decode_index_value_with_props(&v).ctx()?;
Ok(IndexEntry {
separator_key: k,
handle: d.handle,
first_key: d.first_key.map(|fk| fk.to_vec()),
properties: d
.properties
.into_iter()
.map(|(n, p)| (n.to_vec(), p.to_vec()))
.collect(),
})
})
.collect()
}
#[cfg(test)]
pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
if let Some(ref filter) = self.filter_data
&& !BloomFilter::key_may_match(key, filter)
{
return Ok(None);
}
let block_handle = match self.find_data_block(key).ctx()? {
Some(h) => h,
None => return Ok(None),
};
let block_data = self.read_block_cached(&block_handle).ctx()?;
let block = Block::new(block_data).ctx()?;
match block.seek(key) {
Some((found_key, value)) if found_key.as_slice() == key => Ok(Some(value)),
_ => Ok(None),
}
}
#[cfg(test)]
pub fn get_internal(
&self,
user_key: &[u8],
sequence: SequenceNumber,
) -> Result<Option<Option<Vec<u8>>>> {
use crate::types::InternalKey;
if let Some(ref filter) = self.filter_data
&& !BloomFilter::key_may_match(user_key, filter)
{
return Ok(None);
}
let seek_key = InternalKey::new(user_key, sequence, ValueType::Value);
let handle = match self
.index_block
.seek_by(seek_key.as_bytes(), compare_internal_key)
{
Some((_idx_key, handle_bytes)) => BlockHandle::decode(&handle_bytes).ctx()?,
None => return Ok(None),
};
let block_data = self.read_block_cached(&handle).ctx()?;
let block = Block::new(block_data).ctx()?;
match block.seek_by(seek_key.as_bytes(), compare_internal_key) {
Some((encoded_ikey, value)) if encoded_ikey.len() >= 8 => {
let ik = InternalKeyRef::new(&encoded_ikey);
if ik.user_key() == user_key {
return Ok(Some(match ik.value_type() {
ValueType::Value => Some(value),
ValueType::Deletion | ValueType::RangeDeletion => None,
}));
}
Ok(None)
}
_ => Ok(None),
}
}
pub fn get_internal_with_seq(
&self,
user_key: &[u8],
sequence: SequenceNumber,
fill_cache: bool,
) -> Result<Option<(Option<Vec<u8>>, SequenceNumber)>> {
use crate::types::InternalKey;
if let Some(ref filter) = self.filter_data
&& !BloomFilter::key_may_match(user_key, filter)
{
return Ok(None);
}
let seek_key = InternalKey::new(user_key, sequence, ValueType::Value);
let handle = match self
.index_block
.seek_by(seek_key.as_bytes(), compare_internal_key)
{
Some((_idx_key, handle_bytes)) => BlockHandle::decode(&handle_bytes).ctx()?,
None => return Ok(None),
};
let block_data = self.read_block_cached_opt(&handle, fill_cache).ctx()?;
let block = Block::new(block_data).ctx()?;
match block.seek_by(seek_key.as_bytes(), compare_internal_key) {
Some((encoded_ikey, value)) if encoded_ikey.len() >= 8 => {
let ik = InternalKeyRef::new(&encoded_ikey);
if ik.user_key() == user_key {
let entry_seq = ik.sequence();
return Ok(Some((
match ik.value_type() {
ValueType::Value => Some(value),
ValueType::Deletion | ValueType::RangeDeletion => None,
},
entry_seq,
)));
}
Ok(None)
}
_ => Ok(None),
}
}
pub fn max_covering_tombstone_seq(
&self,
user_key: &[u8],
read_seq: SequenceNumber,
) -> Result<SequenceNumber> {
let tombstones = self.cached_range_tombstones().ctx()?;
Ok(tombstones.max_covering_tombstone_seq(user_key, read_seq))
}
pub fn get_range_tombstones(&self) -> Result<Vec<RangeTombstoneEntry>> {
let cached = self.cached_range_tombstones().ctx()?;
Ok(cached.tombstones())
}
fn cached_range_tombstones(&self) -> Result<Arc<FragmentedRangeTombstoneList>> {
if let Some(cached) = self.range_tombstone_cache.get() {
return Ok(cached.clone());
}
let mut triples = Vec::new();
if let Some(ref handle) = self.range_del_handle {
let block_data = self.read_block_cached(handle).ctx()?;
let block = Block::new(block_data).ctx()?;
for (k, v) in block.iter() {
if k.len() < 8 {
continue;
}
let ikr = InternalKeyRef::new(&k);
if ikr.value_type() == ValueType::RangeDeletion {
triples.push((ikr.user_key().to_vec(), v, ikr.sequence()));
}
}
} else {
for (_, handle_bytes) in self.index_block.iter() {
let handle = BlockHandle::decode(&handle_bytes).ctx()?;
let block_data = self.read_block_cached(&handle).ctx()?;
let block = Block::new(block_data).ctx()?;
for (k, v) in block.iter() {
if k.len() < 8 {
continue;
}
let ikr = InternalKeyRef::new(&k);
if ikr.value_type() != ValueType::RangeDeletion {
continue;
}
triples.push((ikr.user_key().to_vec(), v, ikr.sequence()));
}
}
}
let cached = Arc::new(FragmentedRangeTombstoneList::new(triples));
let _ = self.range_tombstone_cache.set(cached.clone());
Ok(cached)
}
#[cfg(test)]
pub fn iter(&self) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
let mut result = Vec::new();
for (_, handle_bytes) in self.index_block.iter() {
let handle = BlockHandle::decode(&handle_bytes).ctx()?;
let block_data = self.read_block_cached(&handle).ctx()?;
let block = Block::new(block_data).ctx()?;
for entry in block.iter() {
result.push(entry);
}
}
Ok(result)
}
#[cfg(test)]
fn find_data_block(&self, key: &[u8]) -> Result<Option<BlockHandle>> {
match self.index_block.seek(key) {
Some((_idx_key, handle_bytes)) => {
let handle = BlockHandle::decode(&handle_bytes).ctx()?;
Ok(Some(handle))
}
None => Ok(None),
}
}
fn read_block_data(file: &mut File, handle: &BlockHandle) -> Result<Vec<u8>> {
let file_size = file.metadata().ctx()?.len();
Self::read_block_data_with_size(file, handle, file_size)
}
fn read_block_data_with_size(
file: &mut File,
handle: &BlockHandle,
file_size: u64,
) -> Result<Vec<u8>> {
const MAX_COMPRESSED_BLOCK_SIZE: u64 = 64 * 1024 * 1024;
let end = handle
.offset
.checked_add(handle.size)
.and_then(|n| n.checked_add(BLOCK_TRAILER_SIZE as u64))
.ok_or_else(|| Error::corruption("block handle range overflow"))
.ctx()?;
if end > file_size {
return Err(Error::corruption(format!(
"block handle out of bounds: offset={}, size={}, file_size={}",
handle.offset, handle.size, file_size
)));
}
if handle.size > MAX_COMPRESSED_BLOCK_SIZE {
return Err(Error::corruption(format!(
"compressed block size {} exceeds limit {}",
handle.size, MAX_COMPRESSED_BLOCK_SIZE
)));
}
file.seek(SeekFrom::Start(handle.offset)).ctx()?;
let mut data = vec![0u8; handle.size as usize];
file.read_exact(&mut data).ctx()?;
let mut trailer = [0u8; BLOCK_TRAILER_SIZE];
file.read_exact(&mut trailer).ctx()?;
let compression_type = CompressionType::from_u8(trailer[0])
.ok_or_else(|| Error::corruption("unknown compression type"))
.ctx()?;
let stored_crc = u32::from_le_bytes(trailer[1..5].try_into().unwrap());
let mut hasher = crc32fast::Hasher::new();
hasher.update(&data);
hasher.update(&[trailer[0]]);
let computed_crc = hasher.finalize();
if stored_crc != computed_crc {
return Err(Error::corruption(format!(
"block CRC mismatch: stored {:#x}, computed {:#x}",
stored_crc, computed_crc
)));
}
let data = match compression_type {
CompressionType::Lz4 => {
if data.len() < 4 {
return Err(Error::corruption(
"LZ4 block too small for size header".to_string(),
));
}
let uncompressed_size =
u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
if uncompressed_size > MAX_DECOMPRESSED_BLOCK_SIZE {
return Err(Error::corruption(format!(
"LZ4 decompressed size {} exceeds limit {}",
uncompressed_size, MAX_DECOMPRESSED_BLOCK_SIZE
)));
}
lz4_flex::decompress_size_prepended(&data)
.map_err(|e| Error::corruption(format!("LZ4 decompression error: {}", e)))
.ctx()?
}
CompressionType::Zstd => {
zstd::bulk::decompress(data.as_slice(), MAX_DECOMPRESSED_BLOCK_SIZE)
.map_err(|e| Error::corruption(format!("Zstd decompression error: {}", e)))
.ctx()?
}
CompressionType::None => data,
};
Ok(data)
}
fn read_metaindex(
file: &mut File,
metaindex_handle: &BlockHandle,
file_size: u64,
) -> Result<MetaIndexData> {
if metaindex_handle.size == 0 {
return Ok(MetaIndexData {
bloom: None,
prefix: None,
prefix_len: None,
range_del_handle: None,
});
}
let metaindex_data =
Self::read_block_data_with_size(file, metaindex_handle, file_size).ctx()?;
let metaindex = Block::from_vec(metaindex_data).ctx()?;
let mut bloom = None;
let mut prefix = None;
let mut prefix_len = None;
let mut range_del_handle = None;
for (key, value) in metaindex.iter() {
if key == b"filter.bloom" {
let handle = BlockHandle::decode(&value).ctx()?;
bloom = Some(Self::read_block_data_with_size(file, &handle, file_size).ctx()?);
} else if key == b"filter.prefix" {
let handle = BlockHandle::decode(&value).ctx()?;
prefix = Some(Self::read_block_data_with_size(file, &handle, file_size).ctx()?);
} else if key == PREFIX_FILTER_LEN_NAME.as_bytes() {
if value.len() != 8 {
return Err(Error::corruption(
"bad prefix filter length metadata".to_string(),
));
}
let len = u64::from_le_bytes(value.as_slice().try_into().unwrap());
prefix_len = Some(usize::try_from(len).map_err(|_| {
Error::corruption("prefix filter length overflows usize".to_string())
})?);
} else if key == RANGE_DEL_BLOCK_NAME.as_bytes() {
range_del_handle = Some(BlockHandle::decode(&value).ctx()?);
}
}
Ok(MetaIndexData {
bloom,
prefix,
prefix_len,
range_del_handle,
})
}
pub fn prefix_may_match(&self, prefix: &[u8]) -> bool {
match (self.prefix_filter_data.as_ref(), self.prefix_filter_len) {
(Some(filter), Some(prefix_len)) if prefix.len() >= prefix_len => {
BloomFilter::key_may_match(&prefix[..prefix_len], filter)
}
_ => true, }
}
fn read_block_cached(&self, handle: &BlockHandle) -> Result<Arc<Vec<u8>>> {
self.read_block_cached_opt(handle, true)
}
fn read_block_cached_opt(
&self,
handle: &BlockHandle,
fill_cache: bool,
) -> Result<Arc<Vec<u8>>> {
if let Some(ref cache) = self.block_cache
&& let Some(cached) = cache.get(self.file_number, handle.offset)
{
if let Some(ref s) = self.stats {
s.record_cache_hit();
}
return Ok(cached);
}
if let Some(ref s) = self.stats
&& self.block_cache.is_some()
{
s.record_cache_miss();
}
let mut file = self.open_file().ctx()?;
let data = Self::read_block_data(&mut file, handle).ctx()?;
if fill_cache && let Some(ref cache) = self.block_cache {
return Ok(cache.insert(self.file_number, handle.offset, data));
}
Ok(Arc::new(data))
}
pub fn pin_metadata_in_cache(&self) {
let entries = match self.cached_index_entries() {
Ok(e) => e,
Err(e) => {
tracing::warn!("pin_metadata_in_cache: index decode error: {}", e);
return;
}
};
if let Some(entry) = entries.first()
&& let Some(ref cache) = self.block_cache
&& cache.get(self.file_number, entry.handle.offset).is_none()
&& let Ok(mut file) = self.open_file()
&& let Ok(data) = Self::read_block_data(&mut file, &entry.handle)
{
cache.insert_pinned(self.file_number, entry.handle.offset, data);
}
}
fn advise_willneed(&self, offset: u64, len: u64) {
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
if let Ok(file) = self.open_file() {
unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
offset as i64,
len as i64,
libc::POSIX_FADV_WILLNEED,
);
}
}
}
#[cfg(not(target_os = "linux"))]
{
let _ = (offset, len);
}
}
fn open_file(&self) -> Result<MutexGuard<'_, File>> {
Ok(self.file.lock())
}
}