mod checkpoint;
mod flush;
mod lifecycle;
mod replication;
use std::{
fs,
path::{Path, PathBuf},
str,
sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering},
},
};
use parking_lot::RwLock;
use wbase::{
backoff::backoff,
base32::{BASE32_LEN_U64, BASE32_LEN_U128, Base32Buf128, encode_u64, encode_u128},
striped::{CacheAlignedLock as BaseCacheAlignedLock, StripedRwLock},
};
use whasher::{GxPapayaMap, fast_hash, hash128, new_papaya_map};
use crate::{error::Result, service::BfTreeService, stub::RANGE_INDEX_STUB_SIZE};
const PREFIX_SEED_1: u64 = 0x27bb_2ee6_87b0_b0fd;
const PREFIX_SEED_2: u64 = 0x517c_c1b7_2722_0a95;
pub(crate) const HASH_PREFIX_LEN: usize = BASE32_LEN_U128;
const DATA_FILE_SUFFIX: &str = ".data.bftree";
const FLUSH_FILE_SUFFIX: &str = ".flush.bftree";
const TREE_FILE_SUFFIX: &str = ".bftree";
pub(crate) const CPR_MAGIC: &[u8; 16] = b"BF-TREE-V0-BEGIN";
pub const NUM_LOCK_STRIPES: usize = 128;
pub const DEFAULT_MIGRATION_CHUNK_SIZE: usize = 256 * 1024;
pub const INDEX_SIZE_BYTES: usize = RANGE_INDEX_STUB_SIZE;
pub type CacheAlignedLock = BaseCacheAlignedLock<()>;
pub type RangeIndexLocks = StripedRwLock<(), NUM_LOCK_STRIPES>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RangeIndexFileEntry {
pub path: PathBuf,
pub key_hash: String,
pub address: i64,
pub is_flush_file: bool,
}
pub struct TreeEntry {
pub tree: RwLock<Option<Arc<BfTreeService>>>,
pub key_hash: u64,
pub key_id: u128,
pub hash_prefix: Base32Buf128,
pub snapshot_pending: AtomicBool,
pub snapshot_in_progress: AtomicBool,
}
impl TreeEntry {
pub fn new(
tree: Option<Arc<BfTreeService>>,
key_hash: u64,
key_id: u128,
hash_prefix: Base32Buf128,
) -> Self {
Self {
tree: RwLock::new(tree),
key_hash,
key_id,
hash_prefix,
snapshot_pending: AtomicBool::new(false),
snapshot_in_progress: AtomicBool::new(false),
}
}
#[inline]
pub fn try_claim_snapshot(&self) -> bool {
self
.snapshot_in_progress
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
}
#[inline]
pub fn release_snapshot(&self) {
self.snapshot_in_progress.store(false, Ordering::Release);
}
pub fn snapshot_under_claim(&self, tree: &BfTreeService, destination_path: &Path) -> Result<()> {
let mut spins = 0u32;
while !self.try_claim_snapshot() {
backoff(spins);
spins = spins.wrapping_add(1);
}
let _guard = SnapshotClaimGuard(self);
tree.cpr_snapshot(destination_path)
}
}
struct SnapshotClaimGuard<'a>(&'a TreeEntry);
impl Drop for SnapshotClaimGuard<'_> {
fn drop(&mut self) {
self.0.release_snapshot();
}
}
pub struct RangeIndexManager {
pub(crate) ri_log_root: PathBuf,
pub(crate) cpr_dir: PathBuf,
pub(crate) migration_temp_dir: PathBuf,
pub(crate) live_indexes: GxPapayaMap<u128, Arc<TreeEntry>>,
pub(crate) checkpoint_in_progress: AtomicBool,
pub(crate) addr_flush_gen: AtomicU64,
pub(crate) addr_flush_settled_gen: AtomicU64,
pub(crate) locks: RangeIndexLocks,
}
impl RangeIndexManager {
pub fn from_root(ri_log_root: impl Into<PathBuf>) -> Self {
let root = ri_log_root.into();
let cpr = root.join("cpr");
Self::new(root, cpr)
}
pub fn new(ri_log_root: impl Into<PathBuf>, cpr_dir: impl Into<PathBuf>) -> Self {
let ri_log_root = ri_log_root.into();
let cpr_dir = cpr_dir.into();
let _ = fs::create_dir_all(&ri_log_root);
let _ = fs::create_dir_all(&cpr_dir);
let migration_temp_dir = ri_log_root.join("migration-tmp");
if migration_temp_dir.exists() {
let _ = fs::remove_dir_all(&migration_temp_dir);
}
let _ = fs::create_dir_all(&migration_temp_dir);
Self {
ri_log_root,
cpr_dir,
migration_temp_dir,
live_indexes: new_papaya_map(),
checkpoint_in_progress: AtomicBool::new(false),
addr_flush_gen: AtomicU64::new(1),
addr_flush_settled_gen: AtomicU64::new(0),
locks: RangeIndexLocks::new(),
}
}
#[inline]
pub(crate) fn addr_flush_scan_pending(&self) -> bool {
self.addr_flush_gen.load(Ordering::Acquire)
!= self.addr_flush_settled_gen.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn addr_flush_scan_token(&self) -> u64 {
self.addr_flush_gen.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn settle_addr_flush_scan(&self, token: u64) {
if self.addr_flush_gen.load(Ordering::Acquire) == token {
self.addr_flush_settled_gen.store(token, Ordering::Release);
}
}
#[inline]
pub(crate) fn notice_addr_flush_files(&self) {
self.addr_flush_gen.fetch_add(1, Ordering::AcqRel);
}
#[inline]
pub fn derive_temp_migration_path(&self) -> PathBuf {
let rand_id = fastrand::u128(..);
let b32 = encode_u128(rand_id);
let mut s = String::with_capacity(HASH_PREFIX_LEN + TREE_FILE_SUFFIX.len());
s.push_str(&b32);
s.push_str(TREE_FILE_SUFFIX);
self.migration_temp_dir.join(s)
}
pub fn dispose(&self) {
let pin = self.live_indexes.pin();
for entry in pin.values() {
if let Some(tree) = entry.tree.write().take() {
let _ = tree.dispose_quiesced();
}
}
pin.clear();
}
#[inline]
pub fn locks(&self) -> &RangeIndexLocks {
&self.locks
}
#[inline]
pub fn key_id_of(key: &[u8]) -> u128 {
hash128(key, PREFIX_SEED_1, PREFIX_SEED_2)
}
#[inline]
pub fn base32_prefix_of(key: &[u8]) -> Base32Buf128 {
let id = Self::key_id_of(key);
encode_u128(id)
}
#[inline]
pub fn hash_prefix_of(key: &[u8]) -> String {
Self::base32_prefix_of(key).to_string()
}
#[inline]
pub fn key_hash_of(key: &[u8]) -> u64 {
fast_hash(key)
}
#[inline]
pub fn compute_leaf_page_size(max_record_size: usize) -> usize {
if max_record_size <= 2048 {
return 4096;
}
(max_record_size * 5 / 2).min(32768).next_power_of_two()
}
pub fn data_file_path(&self, hash_prefix: &str) -> PathBuf {
let mut file_name = String::with_capacity(hash_prefix.len() + DATA_FILE_SUFFIX.len());
file_name.push_str(hash_prefix);
file_name.push_str(DATA_FILE_SUFFIX);
self.ri_log_root.join(file_name)
}
#[inline]
pub fn data_file_path_for_key(&self, key: &[u8]) -> PathBuf {
let hash_prefix = Self::base32_prefix_of(key);
self.data_file_path(&hash_prefix)
}
pub fn log_flush_path(&self, hash_prefix: &str, logical_address: i64) -> PathBuf {
let b32 = encode_u64(logical_address as u64);
let mut s =
String::with_capacity(hash_prefix.len() + 1 + BASE32_LEN_U64 + FLUSH_FILE_SUFFIX.len());
s.push_str(hash_prefix);
s.push('.');
s.push_str(&b32);
s.push_str(FLUSH_FILE_SUFFIX);
self.ri_log_root.join(s)
}
pub fn bare_flush_path(&self, hash_prefix: &str) -> PathBuf {
let mut flush_name = String::with_capacity(hash_prefix.len() + FLUSH_FILE_SUFFIX.len());
flush_name.push_str(hash_prefix);
flush_name.push_str(FLUSH_FILE_SUFFIX);
self.ri_log_root.join(flush_name)
}
#[inline]
pub fn live_indexes(&self) -> &GxPapayaMap<u128, Arc<TreeEntry>> {
&self.live_indexes
}
pub(crate) fn live_entries(&self) -> Vec<Arc<TreeEntry>> {
let pin = self.live_indexes.pin();
let mut entries = Vec::with_capacity(pin.len());
entries.extend(pin.values().cloned());
entries
}
#[inline]
pub fn get_tree(&self, key: &[u8]) -> Option<Arc<BfTreeService>> {
let key_id = Self::key_id_of(key);
let pin = self.live_indexes.pin();
pin
.get(&key_id)
.and_then(|e| e.tree.read().as_ref().cloned())
}
#[inline]
pub(crate) fn live_tree_of(&self, key_id: u128) -> Option<(Arc<TreeEntry>, Arc<BfTreeService>)> {
let pin = self.live_indexes.pin();
pin
.get(&key_id)
.and_then(|e| e.tree.read().as_ref().cloned().map(|t| (Arc::clone(e), t)))
}
pub fn checkpoint_snapshot_path(&self, token: &str, hash_prefix: &str) -> PathBuf {
let mut file_name = String::with_capacity(hash_prefix.len() + TREE_FILE_SUFFIX.len());
file_name.push_str(hash_prefix);
file_name.push_str(TREE_FILE_SUFFIX);
self.cpr_dir.join(token).join("rangeindex").join(file_name)
}
#[inline]
pub fn live_index_count(&self) -> usize {
self.live_indexes.pin().len()
}
#[inline]
pub fn is_checkpoint_in_progress(&self) -> bool {
self.checkpoint_in_progress.load(Ordering::Acquire)
}
}
impl Drop for RangeIndexManager {
fn drop(&mut self) {
self.dispose();
}
}