mod iter;
#[cfg(test)]
mod tests;
use std::cell::RefCell;
use std::cmp::Ordering;
use tessarikv_format::key_hash;
use tessarikv_sync::{ArenaError, ConcurrentBloom, KeyComparator, SkipList};
use tessarikv_types::{
InternalKeyRef, SequenceNumber, Trailer, ValueKind, compare_internal, encode_internal,
seek_trailer,
};
use crate::tombstones::RangeTombstone;
use crate::{Error, Result};
pub(crate) use iter::{MemtableCursor, MemtableEntry, MemtableIter};
pub(crate) struct InternalKeyComparator;
impl KeyComparator for InternalKeyComparator {
fn compare(&self, a: &[u8], b: &[u8]) -> Ordering {
compare_internal(a, b)
}
}
thread_local! {
static SCRATCH: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Lookup<'a> {
Value(&'a [u8]),
Deleted,
Absent,
}
pub(crate) struct Memtable {
entries: SkipList<InternalKeyComparator>,
range_deletes: SkipList<InternalKeyComparator>,
keys: ConcurrentBloom,
budget: u64,
}
const BYTES_PER_FILTER_BIT: u64 = 8;
impl Memtable {
#[cfg(test)]
pub(crate) fn new() -> Self {
Self::sized(64 * 1024)
}
pub(crate) fn sized(table_bytes: u64) -> Self {
let bits = usize::try_from(table_bytes / BYTES_PER_FILTER_BIT).unwrap_or(usize::MAX);
Self::with(bits, table_bytes)
}
pub(crate) fn successor(&self) -> Self {
Self::with(self.keys.bits(), self.budget)
}
fn with(bits: usize, budget: u64) -> Self {
Self {
entries: SkipList::new(InternalKeyComparator),
range_deletes: SkipList::new(InternalKeyComparator),
keys: ConcurrentBloom::new(bits),
budget,
}
}
pub(crate) fn is_full(&self) -> bool {
self.approximate_bytes() >= self.budget
}
pub(crate) fn insert(
&self,
sequence: SequenceNumber,
kind: ValueKind,
key: &[u8],
value: &[u8],
) -> Result<()> {
let (list, value) = match kind {
ValueKind::Value => (&self.entries, value),
ValueKind::Delete => (&self.entries, &[][..]),
ValueKind::RangeDelete => (&self.range_deletes, value),
};
if kind != ValueKind::RangeDelete {
self.keys.insert(key_hash(key));
}
SCRATCH
.with_borrow_mut(|internal| {
internal.clear();
encode_internal(key, Trailer::new(sequence, kind), internal);
list.insert(internal, value)
})
.map(|_| ())
.map_err(arena_error)
}
pub(crate) fn get(&self, key: &[u8], snapshot: SequenceNumber) -> Lookup<'_> {
if !self.keys.may_contain(key_hash(key)) {
return Lookup::Absent;
}
let mut cursor = self.entries.cursor();
SCRATCH.with_borrow_mut(|internal| {
internal.clear();
encode_internal(key, seek_trailer(snapshot), internal);
cursor.seek(internal);
});
if !cursor.valid() {
return Lookup::Absent;
}
let Ok(found) = InternalKeyRef::parse(cursor.key()) else {
return Lookup::Absent;
};
if found.user_key() != key {
return Lookup::Absent;
}
match found.trailer().kind() {
Ok(ValueKind::Value) => Lookup::Value(cursor.value()),
Ok(ValueKind::Delete) => Lookup::Deleted,
Ok(ValueKind::RangeDelete) | Err(_) => Lookup::Absent,
}
}
pub(crate) fn iter(&self) -> MemtableIter<'_> {
MemtableIter::new(self.entries.cursor())
}
pub(crate) fn tombstones(&self, out: &mut Vec<RangeTombstone>) {
let mut cursor = self.range_deletes.cursor();
cursor.seek_to_first();
while cursor.valid() {
if let Ok(start) = InternalKeyRef::parse(cursor.key()) {
out.push(RangeTombstone {
start: start.user_key().to_vec(),
end: cursor.value().to_vec(),
sequence: start.trailer().sequence(),
});
}
cursor.next();
}
}
pub(crate) fn has_tombstones(&self) -> bool {
let mut cursor = self.range_deletes.cursor();
cursor.seek_to_first();
cursor.valid()
}
pub(crate) fn approximate_bytes(&self) -> u64 {
self.entries
.allocated_bytes()
.saturating_add(self.range_deletes.allocated_bytes())
}
}
fn arena_error(error: ArenaError) -> Error {
Error::Memtable {
reason: match error {
ArenaError::Exhausted => "arena address space exhausted",
ArenaError::TooLarge => "entry larger than a record can describe",
_ => "internal arena error",
},
}
}