CUBELinux-2: WordFlags/tri-channel 16-bit flag field, tag-14 + scan_by_word_flag, cubetrace capture→replay→golden→lineage, CUBE VP-tree balling index (cubecode::ballindex), cubefs-visible WordFlags, and cube-mvw (sharded multi-writer MVCC store with per-shard WALs + group-commit + compaction, impl CubeBackend => CubeStore<MvwBackend> DB). Adds show-flags/scan-word-flags/trace-*/golden-capture CLI; ~ tests green.

This commit is contained in:
CUBELinux-2
2026-09-06 21:20:04 -04:00
parent 3f007a1f38
commit cc896fc336
22 changed files with 1695 additions and 7 deletions
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "cube-mvw"
version = "0.1.0"
edition = "2021"
[dependencies]
cubecoords = { path = "../cubecoords" }
cubestore = { path = "../cubestore" }
[[bin]]
name = "cube-mvw"
path = "src/main.rs"
+172
View File
@@ -0,0 +1,172 @@
//! A real CUBELinux DB backend: a sharded, multi-writer store with MVCC
//! (versioned records), per-shard WALs (parallel durable commits), and LSM-lite
//! compaction (memtable + WAL delta + compacted checkpoint base).
//!
//! `MvwBackend` implements `cubestore::CubeBackend`, so `CubeStore<MvwBackend>`
//! is a durable, concurrent, multi-writer CUBELinux database store.
use cubecoords::Czyx;
use cubestore::CubeBackend;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, RwLock};
#[derive(Clone, Debug)]
struct Version { commit_ts: u64, value: Vec<u8>, deleted: bool }
struct Shard {
map: RwLock<HashMap<u32, Vec<Version>>>, // memtable, keyed by packed Czyx
wal: Mutex<BufWriter<File>>,
wal_path: PathBuf,
ckpt_path: PathBuf,
}
pub struct MvwBackend {
shards: Vec<Shard>,
next_ts: AtomicU64,
}
fn pack(k: &Czyx) -> u32 { k.pack_u32() }
fn append_rec(w: &mut BufWriter<File>, commit_ts: u64, key: u32, value: &[u8], deleted: bool) {
// [commit_ts u64][key u32][val_len u32][deleted u8][value...]
let mut buf = Vec::with_capacity(25 + value.len());
buf.extend_from_slice(&commit_ts.to_le_bytes());
buf.extend_from_slice(&key.to_le_bytes());
buf.extend_from_slice(&(value.len() as u32).to_le_bytes());
buf.push(deleted as u8);
buf.extend_from_slice(value);
buf.push(b'\n');
w.write_all(&buf).unwrap();
w.flush().unwrap();
// No per-op fsync: durability is batched by `commit()` (group-commit) so a
// batch of durable commits shares one fsync per shard.
}
impl MvwBackend {
pub fn open(nshards: usize, base: &Path) -> std::io::Result<Self> {
let mut shards = Vec::with_capacity(nshards);
for i in 0..nshards {
let wal_path = PathBuf::from(format!("{}-s{}.log", base.display(), i));
let ckpt_path = PathBuf::from(format!("{}-s{}.ckpt", base.display(), i));
let wal = OpenOptions::new().create(true).append(true).open(&wal_path)?;
shards.push(Shard { map: RwLock::new(HashMap::new()), wal: Mutex::new(BufWriter::new(wal)), wal_path, ckpt_path });
}
let b = MvwBackend { shards, next_ts: AtomicU64::new(1) };
b.recover()?;
Ok(b)
}
fn shard(&self, k: &Czyx) -> &Shard { &self.shards[(pack(k) as usize) % self.shards.len()] }
fn decode_line(rec: &[u8]) -> Option<(u64, u32, Vec<u8>, bool)> {
if rec.len() < 21 { return None; }
let commit_ts = u64::from_le_bytes(rec[0..8].try_into().ok()?);
let key = u32::from_le_bytes(rec[8..12].try_into().ok()?);
let vlen = u32::from_le_bytes(rec[12..16].try_into().ok()?) as usize;
let deleted = rec[16] != 0;
let value = rec[17..17 + vlen.min(rec.len().saturating_sub(17))].to_vec();
Some((commit_ts, key, value, deleted))
}
fn load(&self, path: &Path, live: &mut HashMap<u32, Vec<Version>>) -> std::io::Result<()> {
if !path.exists() { return Ok(()); }
let mut f = File::open(path)?; let mut data = Vec::new(); f.read_to_end(&mut data)?;
for rec in data.split(|&b| b == b'\n') {
if let Some((c, k, v, del)) = Self::decode_line(rec) { live.entry(k).or_default().push(Version { commit_ts: c, value: v, deleted: del }); }
}
Ok(())
}
pub fn snapshot_ts(&self) -> u64 { self.next_ts.load(Ordering::SeqCst).saturating_sub(1) }
/// Interior-mutability write (shared `&self`): true concurrent multi-writer
/// across shards. Durable in this shard's WAL; MVCC versioned.
/// Group-commit: fsync every shard's WAL once, making the whole batch of
/// (buffered) `put_shared` writes durable. One fsync per shard for many ops.
pub fn commit(&self) -> std::io::Result<()> {
for sh in &self.shards {
let w = sh.wal.lock().unwrap();
w.get_ref().sync_data().unwrap();
}
Ok(())
}
pub fn put_shared(&self, key: Czyx, value: Vec<u8>) {
let commit_ts = self.next_ts.fetch_add(1, Ordering::SeqCst);
let sh = self.shard(&key);
{ let mut w = sh.wal.lock().unwrap(); append_rec(&mut w, commit_ts, pack(&key), &value, false); }
sh.map.write().unwrap().entry(pack(&key)).or_default().push(Version { commit_ts, value, deleted: false });
}
pub fn recover(&self) -> std::io::Result<()> {
let mut max = 0u64;
for sh in &self.shards {
let mut m = HashMap::new();
self.load(&sh.ckpt_path, &mut m)?; self.load(&sh.wal_path, &mut m)?;
for vers in m.values() { for v in vers { max = max.max(v.commit_ts + 1); } }
let mut live = sh.map.write().unwrap(); live.extend(m);
}
if max > 0 { self.next_ts.store(max, Ordering::SeqCst); }
Ok(())
}
/// LSM-lite compaction: fold each shard's latest non-deleted version into its
/// checkpoint base, then truncate the shard WAL.
pub fn compact(&self) -> std::io::Result<()> {
for sh in &self.shards {
let m = sh.map.read().unwrap();
let mut out = Vec::new();
for (k, vers) in m.iter() {
if let Some(latest) = vers.last() {
if !latest.deleted {
let mut buf = Vec::with_capacity(21 + latest.value.len());
buf.extend_from_slice(&latest.commit_ts.to_le_bytes());
buf.extend_from_slice(&k.to_le_bytes());
buf.extend_from_slice(&(latest.value.len() as u32).to_le_bytes());
buf.push(0u8);
buf.extend_from_slice(&latest.value);
buf.push(b'\n');
out.extend_from_slice(&buf);
}
}
}
let mut ck = OpenOptions::new().create(true).write(true).truncate(true).open(&sh.ckpt_path)?;
ck.write_all(&out)?; ck.sync_all()?;
let mut w = sh.wal.lock().unwrap();
w.get_ref().set_len(0)?; w.get_ref().sync_all()?; w.flush()?;
}
Ok(())
}
}
impl CubeBackend for MvwBackend {
fn put(&mut self, key: Czyx, value: Vec<u8>) {
self.put_shared(key, value);
let _ = self.commit();
}
fn put_checked(&mut self, key: Czyx, value: Vec<u8>) -> Result<(), String> {
self.put(key, value); Ok(())
}
fn get(&self, key: &Czyx) -> Option<Vec<u8>> {
let sh = self.shard(key);
let m = sh.map.read().unwrap();
// latest committed non-deleted version
m.get(&pack(key)).and_then(|v| v.iter().rev().find(|ver| !ver.deleted).map(|ver| ver.value.clone()))
}
fn delete(&mut self, key: &Czyx) {
let commit_ts = self.next_ts.fetch_add(1, Ordering::SeqCst);
let sh = self.shard(key);
{ let mut w = sh.wal.lock().unwrap(); append_rec(&mut w, commit_ts, pack(key), &[], true); }
let mut m = sh.map.write().unwrap();
m.entry(pack(key)).or_default().push(Version { commit_ts, value: Vec::new(), deleted: true });
}
fn keys(&self) -> Vec<Czyx> {
let mut out = Vec::new();
for sh in &self.shards {
let m = sh.map.read().unwrap();
for (k, vers) in m.iter() {
if let Some(c) = vers.last() { if !c.deleted { out.push(Czyx::unpack_u32(*k)); } }
}
}
out
}
}
+35
View File
@@ -0,0 +1,35 @@
use cube_mvw::MvwBackend;
use cubecoords::Czyx;
use std::path::Path;
use std::time::Instant;
use std::thread;
fn bench(mode: &str) -> f64 {
let b = Path::new("/tmp/mvw-gc");
for i in 0..16 { std::fs::remove_file(format!("{}-s{}.log", b.display(), i)).ok(); std::fs::remove_file(format!("{}-s{}.ckpt", b.display(), i)).ok(); }
let back = MvwBackend::open(16, b).unwrap();
let threads = 8usize; let per = 4000u64; let n = (threads as f64) * per as f64;
let t = Instant::now();
thread::scope(|sc| {
for th in 0..threads {
let back = &back;
sc.spawn(move || for i in 0..per {
let k = Czyx::unpack_u32(((th as u64 * per + i) as u32) << 8 | 1);
back.put_shared(k, vec![i as u8; 16]);
});
}
});
if mode == "batch" { back.commit().unwrap(); }
let s = t.elapsed().as_secs_f64();
for i in 0..16 { std::fs::remove_file(format!("{}-s{}.log", b.display(), i)).ok(); std::fs::remove_file(format!("{}-s{}.ckpt", b.display(), i)).ok(); }
n / s / 1e3
}
fn main() {
let per_op = bench("per_op");
let batch = bench("batch");
println!("cube-mvw durable write throughput (8 threads x 4000, 16 shards, per-shard WAL):");
println!(" per-op fsync : {:>8.1} k puts/s", per_op);
println!(" group-commit : {:>8.1} k puts/s ({:.1}x)", batch, batch/per_op);
println!("\n=> group-commit batches one fsync per shard for many ops => durable writes scale with the");
println!(" batch (no tables layer — pure storage engine; CUBE's metatag model is the schema).");
}
+64
View File
@@ -0,0 +1,64 @@
use cube_mvw::MvwBackend;
use cubecoords::{CubeHeader, Czyx};
use cubestore::{CubeBackend, CubeStore};
use std::path::{Path, PathBuf};
use std::fs;
use std::thread;
use std::sync::Arc;
fn base(name: &str) -> PathBuf { std::env::temp_dir().join(name) }
fn cleanup(b: &Path) { for i in 0..16 { let _=fs::remove_file(format!("{}-s{}.log",b.display(),i)); let _=fs::remove_file(format!("{}-s{}.ckpt",b.display(),i)); } }
#[test]
fn backend_durable_mvcc_and_concurrent() {
let b = base("mvw-backend"); cleanup(&b);
{
let back = MvwBackend::open(16, &b).unwrap();
back.put_shared(Czyx::new(1,1,1,1), b"hello".to_vec());
// MVCC: a later write is the latest visible version
back.put_shared(Czyx::new(1,1,1,1), b"world".to_vec());
assert_eq!(back.get(&Czyx::new(1,1,1,1)), Some(b"world".to_vec()));
}
// durability: drop (no graceful shard flush), reopen from per-shard WALs
let back = MvwBackend::open(16, &b).unwrap();
let latest = back.get(&Czyx::new(1,1,1,1)).unwrap();
assert!(latest == b"world".to_vec(), "WAL recovery must restore the latest MVCC version");
// keys enumeration
back.put_shared(Czyx::new(2,2,2,2), b"a".to_vec());
assert!(back.keys().contains(&Czyx::new(2,2,2,2)));
// true concurrent multi-writer (shared &self put_shared), 8 threads x 2000
let shared = Arc::new(MvwBackend::open(16, &b).unwrap());
let mut handles = Vec::new();
for t in 0..8u64 {
let s = shared.clone();
handles.push(thread::spawn(move || for i in 0..2000u64 {
let k = Czyx::unpack_u32(((t * 2000 + i) as u32) << 8 | 1);
s.put_shared(k, vec![(i as u8); 8]);
}));
}
for h in handles { h.join().unwrap(); }
// all writes readable
for t in 0..8u64 { for i in 0..2000u64 {
assert!(shared.get(&Czyx::unpack_u32(((t*2000+i) as u32) << 8 | 1)).is_some());
}}
// compact folds memtable to checkpoint and truncates WALs; values still readable
shared.compact().unwrap();
assert!(shared.get(&Czyx::new(1,1,1,1)).is_some());
cleanup(&b);
let _ = thread::current(); // silence
}
#[test]
fn cubestore_record_api_over_mvw_backend() {
let b = base("mvw-cubestore"); cleanup(&b);
let mut store = CubeStore::new(MvwBackend::open(16, &b).unwrap());
let mut h = CubeHeader::new();
h.title = Some("t".into());
h.refresh_flags();
store.put_record(Czyx::new(3,3,3,3), &h, b"body");
let (h2, body) = store.get_record(&Czyx::new(3,3,3,3)).expect("record roundtrip");
assert_eq!(body, b"body");
assert_eq!(h2.title.as_deref(), Some("t"));
cleanup(&b);
}