#![allow(unsafe_code)]
use crate::commutative::{CommutativeLog, CMAG, CTOMB, CHDR};
use crate::dual_buffer::DualBufferEngine;
use crate::index::ShardIndex;
use crate::segment::{Manifest, SegmentMeta, SealedSegment};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
pub use crate::dual_buffer::BlockWriter;
#[derive(Clone, Debug)]
pub struct Config {
pub buffer_size: u64,
pub rotate_threshold: f64,
pub fold_threads: Option<usize>,
}
impl Default for Config {
fn default() -> Self {
Self {
buffer_size: 2 << 30,
rotate_threshold: 0.80,
fold_threads: None,
}
}
}
pub struct DonaDbX {
dbe: DualBufferEngine,
gen: AtomicU64,
sealed: parking_lot::RwLock<Vec<SealedSegment>>,
manifest: parking_lot::Mutex<Manifest>,
manifest_path: PathBuf,
dir: PathBuf,
cfg: Config,
current_height: AtomicU64,
segment_log: arc_swap::ArcSwap<CommutativeLog>,
}
#[must_use = "call .wait() to obtain the state root once the fold completes"]
pub struct CommitAck {
rx: std::sync::mpsc::Receiver<[u8; 32]>,
}
impl CommitAck {
pub fn wait(self) -> [u8; 32] {
self.rx.recv().unwrap_or([0u8; 32])
}
pub fn try_poll(&self) -> Option<[u8; 32]> {
self.rx.try_recv().ok()
}
}
impl DonaDbX {
pub fn open(dir: impl AsRef<Path>, cfg: Config) -> crate::DbResult<Self> {
let dir = dir.as_ref();
std::fs::create_dir_all(dir)?;
let manifest_path = dir.join("manifest.json");
let mut manifest = Manifest::load(&manifest_path)?;
let mut sealed_vec: Vec<SealedSegment> = Vec::new();
for meta in manifest.segments.iter().filter(|m| m.sealed) {
sealed_vec.push(SealedSegment::open(meta.clone())?);
}
let active_path = dir.join("seg_active.log");
let active_log = Arc::new(CommutativeLog::open(&active_path, cfg.buffer_size)?);
let dbe = DualBufferEngine::open(dir, cfg.buffer_size, Arc::clone(&active_log))?;
{
Self::replay_into(&active_log, 0, &dbe.index, &dbe.merkle)?;
}
if !manifest.segments.iter().any(|m| !m.sealed) {
manifest.segments.push(SegmentMeta {
id: manifest.next_id,
path: dir.join("seg_active.log").to_string_lossy().into(),
sealed: false,
used: 0,
min_height: 0,
max_height: u64::MAX,
});
manifest.next_id += 1;
manifest.save(&manifest_path)?;
}
Ok(Self {
dbe,
gen: AtomicU64::new(0),
sealed: parking_lot::RwLock::new(sealed_vec),
manifest: parking_lot::Mutex::new(manifest),
manifest_path,
dir: dir.to_owned(),
cfg,
current_height: AtomicU64::new(0),
segment_log: arc_swap::ArcSwap::from(Arc::clone(&active_log)),
})
}
#[inline(always)]
pub fn put(&self, key: [u8; 32], value: &[u8]) -> crate::DbResult<u64> {
let height = self.current_height.load(Ordering::Acquire);
self.dbe.active.load().put_versioned(key, value, 0, height)
}
pub fn commit(&self, height: u64) -> crate::DbResult<CommitAck> {
self.current_height.store(height, Ordering::Release);
let cur_gen = self.gen.load(Ordering::Acquire);
let sealed_used = self.dbe.active.load().write_offset();
let fold_ack = self.dbe.swap(cur_gen)?;
if sealed_used as f64 / self.cfg.buffer_size as f64 >= self.cfg.rotate_threshold {
self.rotate(height, sealed_used)?;
}
Ok(CommitAck { rx: fold_ack.into_rx() })
}
pub fn get(&self, key: &[u8; 32]) -> crate::DbResult<Vec<u8>> {
if let Some(entry) = self.dbe.index.get_entry(key) {
if entry.deleted { return Err(crate::DbError::NotFound); }
if let Some(cached) = self.dbe.vcache.get(key) {
return Ok(cached.to_vec());
}
}
if let Some(entry) = self.dbe.index.get_entry(key) {
if entry.deleted { return Err(crate::DbError::NotFound); }
if entry.log_id == self.gen.load(Ordering::Acquire) {
let log = self.segment_log.load();
if let Ok(v) = self.read_from_log(&log, entry.offset as usize) {
return Ok(v);
}
}
}
{
let active = self.dbe.active.load();
if let Some(v) = active.scan_unflushed(key) {
return Ok(v);
}
}
for seg in self.sealed.read().iter().rev() {
if let Some(v) = seg.get_at(key, seg.meta.max_height) {
return Ok(v);
}
}
Err(crate::DbError::NotFound)
}
pub fn get_slice(&self, key: &[u8; 32]) -> crate::DbResult<crate::commutative::MmapRef> {
if let Some(entry) = self.dbe.index.get_entry(key) {
if entry.deleted { return Err(crate::DbError::NotFound); }
if entry.log_id == self.gen.load(Ordering::Acquire) {
let committed = self.segment_log.load();
if let Some(r) = committed.value_ref(entry.offset as usize) {
return Ok(r);
}
}
}
{
let active = self.dbe.active.load();
if let Some(v) = active.scan_unflushed(key) {
return Ok(crate::commutative::MmapRef::from_vec(v));
}
}
for seg in self.sealed.read().iter().rev() {
if let Some(v) = seg.get_at(key, seg.meta.max_height) {
return Ok(crate::commutative::MmapRef::from_vec(v));
}
}
Err(crate::DbError::NotFound)
}
pub fn get_at(&self, key: &[u8; 32], height: u64) -> crate::DbResult<Vec<u8>> {
let active_min = self.manifest.lock().segments.iter()
.find(|m| !m.sealed)
.map(|m| m.min_height)
.unwrap_or(0);
if active_min <= height {
if let Some(entry) = self.dbe.index.get_entry(key) {
if entry.log_id == self.gen.load(Ordering::Acquire) {
let log = self.segment_log.load();
if let Ok(v) = self.chain_walk(&log, entry.offset as usize, height) {
return Ok(v);
}
}
}
}
for seg in self.sealed.read().iter().rev() {
if seg.meta.min_height > height {
continue;
}
if let Some(v) = seg.get_at(key, height) {
return Ok(v);
}
}
Err(crate::DbError::NotFound)
}
#[inline(always)]
pub fn delete(&self, key: [u8; 32]) -> crate::DbResult<u64> {
let height = self.current_height.load(Ordering::Acquire);
self.dbe.vcache.remove(&key);
self.dbe.active.load().del_versioned(key, 0, height)
}
pub fn scan_prefix(&self, prefix: &[u8]) -> crate::DbResult<Vec<([u8; 32], Vec<u8>)>> {
let mut results: Vec<([u8; 32], Vec<u8>)> = Vec::new();
for entry in self.dbe.index.all_entries() {
if entry.deleted { continue; }
if !entry.key.starts_with(prefix) { continue; }
if let Ok(v) = self.get(&entry.key) {
results.push((entry.key, v));
}
}
let active = self.dbe.active.load();
for (key, val) in active.scan_all_unflushed() {
if !key.starts_with(prefix) { continue; }
results.retain(|(k, _)| k != &key);
if !val.is_empty() {
results.push((key, val));
}
}
results.sort_unstable_by_key(|(k, _)| *k);
Ok(results)
}
pub fn scan_from_reverse(&self, start: &[u8; 32]) -> crate::DbResult<Vec<([u8; 32], Vec<u8>)>> {
let mut results: Vec<([u8; 32], Vec<u8>)> = Vec::new();
for entry in self.dbe.index.all_entries() {
if entry.deleted { continue; }
if entry.key.as_ref() > start.as_ref() { continue; }
if let Ok(v) = self.get(&entry.key) {
results.push((entry.key, v));
}
}
results.sort_unstable_by(|(a, _), (b, _)| b.cmp(a));
Ok(results)
}
pub fn state_root(&self) -> [u8; 32] {
self.dbe.state_root()
}
pub fn len(&self) -> usize {
self.dbe.index.len()
}
pub fn is_empty(&self) -> bool {
self.dbe.index.is_empty()
}
pub fn num_shards(&self) -> usize {
self.dbe.num_shards()
}
pub fn writer(&self, shard_id: usize) -> BlockWriter {
let height = self.current_height.load(Ordering::Acquire);
self.dbe.writer(shard_id, height)
}
pub fn begin_frame(&self) -> ExecutionFrame<'_> {
ExecutionFrame {
db: self,
start: self.dbe.active.load().write_offset(),
log_id: self.gen.load(Ordering::Acquire),
}
}
#[cfg(test)]
fn read_committed(&self, off: usize) -> crate::DbResult<Vec<u8>> {
self.read_from_log(&self.segment_log.load(), off)
}
fn read_from_log(&self, log: &CommutativeLog, off: usize) -> crate::DbResult<Vec<u8>> {
let cap = log.capacity as usize;
if off + CHDR + 32 > cap {
return Err(crate::DbError::Corrupt);
}
let ptr = log.mmap_ptr();
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off), 4)
}.try_into().unwrap());
if magic != CMAG {
return Err(crate::DbError::Corrupt);
}
let vlen = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off + 4), 4)
}.try_into().unwrap()) as usize;
let vs = off + CHDR + 32;
if vs + vlen > cap {
return Err(crate::DbError::Corrupt);
}
Ok(unsafe { std::slice::from_raw_parts(ptr.add(vs), vlen).to_vec() })
}
fn chain_walk(
&self,
log: &CommutativeLog,
start: usize,
target: u64,
) -> crate::DbResult<Vec<u8>> {
let cap = log.capacity as usize;
let ptr = log.mmap_ptr();
let mut off = start;
loop {
if off + CHDR + 32 > cap {
return Err(crate::DbError::Corrupt);
}
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off), 4)
}.try_into().unwrap());
if magic != CMAG {
return Err(crate::DbError::Corrupt);
}
let vlen = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off + 4), 4)
}.try_into().unwrap()) as usize;
let prev = u64::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off + 8), 8)
}.try_into().unwrap());
let rec_h = u64::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off + 16), 8)
}.try_into().unwrap());
if rec_h <= target {
let vs = off + CHDR + 32;
if vs + vlen > cap {
return Err(crate::DbError::Corrupt);
}
return Ok(unsafe {
std::slice::from_raw_parts(ptr.add(vs), vlen).to_vec()
});
}
if prev == 0 {
return Err(crate::DbError::NotFound);
}
off = prev as usize;
}
}
fn rotate(&self, height: u64, actual: u64) -> crate::DbResult<()> {
let next_id = { self.manifest.lock().next_id };
let seal_path = self.dir.join(format!("seg_{:04}.log", next_id));
let seal_min = self.manifest.lock().segments.iter()
.find(|m| !m.sealed)
.map(|m| m.min_height)
.unwrap_or(0);
std::fs::rename(self.dir.join("seg_active.log"), &seal_path)
.map_err(crate::DbError::Io)?;
{
let mut m = self.manifest.lock();
m.segments.retain(|s| s.sealed);
m.segments.push(SegmentMeta {
id: next_id,
path: seal_path.to_string_lossy().into(),
sealed: true,
used: actual,
min_height: seal_min,
max_height: height,
});
m.next_id += 1;
m.save(&self.manifest_path)?;
let new_id = m.next_id;
m.segments.push(SegmentMeta {
id: new_id,
path: self.dir.join("seg_active.log").to_string_lossy().into(),
sealed: false,
used: 0,
min_height: height + 1,
max_height: u64::MAX,
});
m.next_id += 1;
m.save(&self.manifest_path)?;
}
self.gen.fetch_add(1, Ordering::AcqRel);
let new_log = Arc::new(CommutativeLog::open(
&self.dir.join("seg_active.log"),
self.cfg.buffer_size,
)?);
self.dbe.replace_shard_log(0, Arc::clone(&new_log));
self.segment_log.store(new_log);
self.dbe.vcache.clear();
let new_meta = self.manifest.lock().segments.iter()
.find(|s| s.id == next_id)
.ok_or_else(|| crate::DbError::Io(
std::io::Error::new(std::io::ErrorKind::NotFound, "sealed meta missing"),
))?
.clone();
self.sealed.write().push(SealedSegment::open(new_meta)?);
Ok(())
}
fn replay_into(
log: &Arc<CommutativeLog>,
log_id: u64,
index: &Arc<ShardIndex>,
merkle: &Arc<parking_lot::Mutex<[u8; 32]>>,
) -> crate::DbResult<()> {
let committed = log.committed_offset() as usize;
if committed <= 8 {
return Ok(());
}
let ptr = log.mmap_ptr();
let mut cur = 8usize;
let mut acc = [0u8; 32];
while cur + CHDR + 32 <= committed {
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(cur), 4)
}.try_into().unwrap());
if magic == CTOMB {
let key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap();
if let Some(entry) = index.get_entry(&key) {
if !entry.deleted {
for i in 0..32 { acc[i] ^= key[i] ^ entry.value_hash[i]; }
}
}
let wc = index.count(&key) + 1;
index.upsert_with_deleted(key, cur as u64, wc, log_id, true);
cur += CHDR + 32;
continue;
}
if magic != CMAG {
break;
}
let vlen = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(cur + 4), 4)
}.try_into().unwrap()) as usize;
let total = CHDR + 32 + vlen;
if cur + total > committed || vlen > committed {
break;
}
let key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap();
let val_end = cur + CHDR + 32 + vlen;
if val_end > committed {
break;
}
let val = unsafe { std::slice::from_raw_parts(ptr.add(cur + CHDR + 32), vlen) };
let vh = *blake3::hash(val).as_bytes();
if let Some(entry) = index.get_entry(&key) {
if !entry.deleted {
for i in 0..32 { acc[i] ^= key[i] ^ entry.value_hash[i]; }
}
}
for i in 0..32 {
acc[i] ^= key[i] ^ vh[i];
}
let wc = index.count(&key) + 1;
index.upsert_full(key, cur as u64, wc, log_id, false, vh);
cur += total;
}
log.set_committed(cur as u64);
*merkle.lock() = acc;
Ok(())
}
}
pub struct ExecutionFrame<'a> {
db: &'a DonaDbX,
start: u64,
log_id: u64,
}
impl<'a> ExecutionFrame<'a> {
#[inline(always)]
pub fn put(&self, key: [u8; 32], value: &[u8]) -> crate::DbResult<u64> {
self.db.put(key, value)
}
pub fn commit(self, height: u64) -> crate::DbResult<CommitAck> {
self.db.commit(height)
}
pub fn abort(self) {
let log_arc = self.db.dbe.active.load_full();
let cur_gen = self.db.gen.load(Ordering::Acquire);
if cur_gen != self.log_id {
return;
}
let _guard = log_arc.commit_lock.lock();
while log_arc.inflight.load(Ordering::Acquire) > 0 {
std::hint::spin_loop();
}
let current = log_arc.write_offset() as usize;
let start = self.start as usize;
if current > start {
unsafe {
std::ptr::write_bytes(
log_arc.mmap_ptr().add(start) as *mut u8,
0,
current - start,
);
}
}
log_arc.write_offset.store(self.start, Ordering::Release);
}
}
#[cfg(test)]
mod engine_tests {
use super::*;
use tempfile::tempdir;
fn cfg() -> Config { Config { buffer_size: 256 << 20, ..Default::default() } }
#[test]
fn scan_prefix_debug() {
let d = tempdir().unwrap();
let db = DonaDbX::open(d.path(), cfg()).unwrap();
let mut k1 = [0u8; 32]; k1[0] = 0x01; k1[1] = 0x00;
let mut k2 = [0u8; 32]; k2[0] = 0x01; k2[1] = 0x01;
let mut k3 = [0u8; 32]; k3[0] = 0x02;
db.put(k1, b"a").unwrap();
db.put(k2, b"b").unwrap();
db.put(k3, b"c").unwrap();
db.commit(1).unwrap().wait();
let cur_gen = db.gen.load(Ordering::Acquire);
eprintln!("cur_gen = {cur_gen}");
for e in db.dbe.index.all_entries() {
let rc = db.read_committed(e.offset as usize);
let sw = e.key.starts_with(&[0x01u8]);
eprintln!(" key[0]={:#04x} log_id={} offset={} deleted={} read_committed={:?} starts_with_01={}",
e.key[0], e.log_id, e.offset, e.deleted, rc.as_ref().map(|v| v.len()), sw);
}
let result = db.scan_prefix(&[0x01u8]).unwrap();
eprintln!("scan_prefix results: {}", result.len());
assert_eq!(result.len(), 2);
}
#[test]
fn cache_populated_and_hit() {
let d = tempdir().unwrap();
let db = DonaDbX::open(d.path(), cfg()).unwrap();
let mut k = [0u8; 32]; k[0] = 0x42;
db.put(k, b"cached_value").unwrap();
db.commit(1).unwrap().wait();
let cache_len = db.dbe.vcache.len();
eprintln!("vcache entries after commit: {}", cache_len);
assert!(cache_len > 0, "fold thread must populate cache");
let hit = db.dbe.vcache.get(&k);
assert!(hit.is_some(), "key must be in cache after fold");
assert_eq!(hit.unwrap().as_ref(), b"cached_value");
assert_eq!(db.get(&k).unwrap(), b"cached_value");
}
#[test]
fn optimization_benchmark() {
use std::time::Instant;
use std::sync::Arc;
let d = tempdir().unwrap();
let db = Arc::new(DonaDbX::open(d.path(), cfg()).unwrap());
eprintln!("\n╔════════════════════════════════════════════════════════════╗");
eprintln!("║ DonaDbX Optimization Benchmark ║");
eprintln!("╚════════════════════════════════════════════════════════════╝");
let n = 50_000u64;
let val = vec![42u8; 128];
eprintln!("\n[1] Write {} keys (128 bytes each)", n);
let write_start = Instant::now();
for i in 0..n {
let mut key = [0u8; 32];
key[..8].copy_from_slice(&i.to_le_bytes());
db.put(key, &val).unwrap();
}
let write_time = write_start.elapsed();
eprintln!(" Write time: {:?} ({:.0} ops/s)",
write_time, n as f64 / write_time.as_secs_f64());
eprintln!("\n[2] Commit + parallel fold");
let commit_start = Instant::now();
let root = db.commit(1).unwrap().wait();
let commit_time = commit_start.elapsed();
eprintln!(" Commit time: {:?}", commit_time);
eprintln!(" State root: {}", hex::encode(&root[..8]));
eprintln!("\n[3] Read performance (cache effectiveness)");
let cache_len = db.dbe.vcache.len();
eprintln!(" Cache entries: {}", cache_len);
let read_start = Instant::now();
let mut hits = 0;
for i in 0..10_000u64 {
let mut key = [0u8; 32];
key[..8].copy_from_slice(&(i * 5).to_le_bytes());
if db.get(&key).is_ok() {
hits += 1;
}
}
let read_time = read_start.elapsed();
eprintln!(" 10K reads: {:?} ({:.0} reads/s)",
read_time, 10_000.0 / read_time.as_secs_f64());
eprintln!(" Hit rate: {:.1}%", hits as f64 / 100.0);
eprintln!("\n[4] Concurrent writes (multi-shard)");
let threads = 4;
let per_thread = 10_000u64;
let val_arc = Arc::new(val);
let concurrent_start = Instant::now();
let handles: Vec<_> = (0..threads).map(|shard_id| {
let db = Arc::clone(&db);
let val = Arc::clone(&val_arc);
std::thread::spawn(move || {
let writer = db.writer(shard_id);
for i in 0..per_thread {
let mut key = [0u8; 32];
key[0] = shard_id as u8;
key[8..16].copy_from_slice(&i.to_le_bytes());
writer.put(key, &val).unwrap();
}
})
}).collect();
for h in handles { h.join().unwrap(); }
let concurrent_time = concurrent_start.elapsed();
let total_ops = threads as u64 * per_thread;
eprintln!(" {} threads × {} ops: {:?} ({:.0} ops/s)",
threads, per_thread, concurrent_time,
total_ops as f64 / concurrent_time.as_secs_f64());
let root2 = db.commit(2).unwrap().wait();
eprintln!(" Final index size: {} keys", db.len());
eprintln!(" State root: {}", hex::encode(&root2[..8]));
eprintln!("\n✓ All optimization benchmarks passed");
}
}