use yo_common::Small;
use yo_common::lock::{Held, Lock};
use yo_index::Cursor as KeyCursor;
use crate::value::Kind;
use crate::{Clock, Keyspace};
pub const MAX_STRIPES: usize = 1 << yo_index::STRIPE_BITS;
const STRIPE_SHIFT: u32 = 56;
pub struct Db {
stripes: Vec<Lock<Keyspace>>,
mask: u64,
clock: Clock,
spare: Lock<Spare>,
}
#[derive(Default)]
pub(crate) struct Spare {
pub(crate) bytes: Vec<u8>,
pub(crate) rows: Vec<usize>,
pub(crate) setops: crate::setops::Scratch,
}
impl Db {
#[must_use]
pub fn with_clock(clock: Clock, stripes: usize) -> Db {
let n = stripes.clamp(1, MAX_STRIPES).next_power_of_two();
Db {
stripes: (0..n)
.map(|_| Lock::new(Keyspace::with_clock(clock)))
.collect(),
mask: (n - 1) as u64,
clock,
spare: Lock::new(Spare {
bytes: Vec::new(),
rows: Vec::new(),
setops: crate::setops::Scratch::new(),
}),
}
}
#[must_use]
pub fn new() -> Db {
Db::with_clock(Clock::system(), 1)
}
#[must_use]
pub fn width(&self) -> usize {
self.stripes.len()
}
#[inline]
#[must_use]
pub fn stripe_of(&self, key: &[u8]) -> usize {
self.stripe_of_hash(Keyspace::hash_of(key))
}
#[inline]
#[must_use]
pub fn stripe_of_hash(&self, hash: u64) -> usize {
((hash >> STRIPE_SHIFT) & self.mask) as usize
}
#[inline]
#[must_use]
pub fn at(&mut self, key: &[u8]) -> &mut Keyspace {
let i = self.stripe_of(key);
self.stripes[i].get_mut()
}
#[inline]
#[must_use]
pub fn hold(&self, key: &[u8]) -> Held<'_, Keyspace> {
let i = self.stripe_of(key);
self.stripes[i].lock()
}
#[inline]
#[must_use]
pub fn at_hashed(&mut self, hash: u64) -> &mut Keyspace {
let i = self.stripe_of_hash(hash);
self.stripes[i].get_mut()
}
#[inline]
#[must_use]
pub fn hold_hashed(&self, hash: u64) -> Held<'_, Keyspace> {
let i = self.stripe_of_hash(hash);
self.stripes[i].lock()
}
#[inline]
pub fn prefetch_hashed(&self, hash: u64) {
let i = self.stripe_of_hash(hash);
if let Some(stripe) = self.stripes[i].try_lock() {
stripe.prefetch(hash);
}
}
#[must_use]
pub fn one_stripe<'k>(&self, mut keys: impl Iterator<Item = &'k [u8]>) -> Option<usize> {
let first = self.stripe_of(keys.next()?);
keys.all(|key| self.stripe_of(key) == first)
.then_some(first)
}
pub(crate) fn spare(&self) -> Held<'_, Spare> {
self.spare.lock()
}
#[inline]
#[must_use]
pub fn stripe_mut(&mut self, i: usize) -> &mut Keyspace {
self.stripes[i].get_mut()
}
#[inline]
#[must_use]
pub fn hold_stripe(&self, i: usize) -> Held<'_, Keyspace> {
self.stripes[i].lock()
}
#[must_use]
pub fn hold_many(&self, homes: impl Iterator<Item = usize>) -> Holds<'_> {
let mut want: Small<u16, INLINE_HOLDS> = homes.map(|i| i as u16).collect();
want.sort_unstable();
let mut out = Holds::new();
let mut last = u16::MAX;
for &i in want.iter() {
if i == last {
continue;
}
last = i;
out.push(i, self.stripes[usize::from(i)].lock());
}
out
}
#[must_use]
pub fn hold_keys<'k>(&self, keys: impl Iterator<Item = &'k [u8]>) -> Holds<'_> {
self.hold_many(keys.map(|key| self.stripe_of(key)))
}
pub fn stripes_mut(&mut self) -> impl Iterator<Item = &mut Keyspace> {
self.stripes.iter_mut().map(Lock::get_mut)
}
#[must_use]
pub fn now_ms(&self) -> u64 {
self.clock.now_ms()
}
#[must_use]
pub fn len(&self) -> usize {
(0..self.stripes.len())
.map(|i| self.stripes[i].lock().len())
.sum()
}
#[must_use]
pub fn is_empty(&self) -> bool {
(0..self.stripes.len()).all(|i| self.stripes[i].lock().is_empty())
}
#[must_use]
pub fn expires(&self) -> usize {
(0..self.stripes.len())
.map(|i| self.stripes[i].lock().expires())
.sum()
}
pub fn clear(&self) {
for i in 0..self.stripes.len() {
self.hold_stripe(i).clear();
}
}
pub fn set_clock_ms(&mut self, ms: u64) {
self.clock.set(ms);
for stripe in self.stripes_mut() {
stripe.clock_mut().set(ms);
}
}
pub fn track_memory(&mut self, on: bool) {
for stripe in self.stripes_mut() {
stripe.track_memory(on);
}
}
pub fn scan(
&self,
from: KeyCursor,
budget: usize,
ty: Option<Kind>,
mut out: impl FnMut(&[u8]),
) -> KeyCursor {
let mut at = from.stripe();
if at >= self.stripes.len() {
return KeyCursor::START;
}
let mut cursor = from.without_stripe();
let mut seen = 0usize;
while at < self.stripes.len() {
let next = self.hold_stripe(at).scan(cursor, budget, ty, |key| {
seen += 1;
out(key);
});
if !next.is_end() {
return next.with_stripe(at);
}
at += 1;
cursor = KeyCursor::START;
if at < self.stripes.len() && seen >= budget {
return KeyCursor::START.with_stripe(at);
}
}
KeyCursor::START
}
pub fn keys(&self, mut out: impl FnMut(&[u8])) {
for i in 0..self.stripes.len() {
self.hold_stripe(i).keys(&mut out);
}
}
pub fn random_key(&self, f: impl FnOnce(&[u8])) -> bool {
let live: usize = (0..self.stripes.len())
.map(|i| self.hold_stripe(i).len())
.sum();
if live == 0 {
return false;
}
let draw = (self.hold_stripe(0).random() % live as u64) as usize;
let mut running = 0;
let mut first = 0;
for i in 0..self.stripes.len() {
running += self.hold_stripe(i).len();
if draw < running {
first = i;
break;
}
}
let mut f = Some(f);
for step in 0..self.stripes.len() {
let i = (first as u64 + step as u64) & self.mask;
let mut stripe = self.hold_stripe(i as usize);
if let Some(key) = stripe.random_key() {
f.take().expect("the loop stops the first time it fires")(key);
return true;
}
}
false
}
}
impl Default for Db {
fn default() -> Db {
Db::new()
}
}
const INLINE_HOLDS: usize = 8;
pub struct Holds<'a> {
room: [Option<(u16, Held<'a, Keyspace>)>; INLINE_HOLDS],
n: usize,
spill: Vec<(u16, Held<'a, Keyspace>)>,
}
impl<'a> Holds<'a> {
fn new() -> Holds<'a> {
Holds {
room: [const { None }; INLINE_HOLDS],
n: 0,
spill: Vec::new(),
}
}
fn push(&mut self, home: u16, held: Held<'a, Keyspace>) {
if self.n < INLINE_HOLDS {
self.room[self.n] = Some((home, held));
self.n += 1;
} else {
self.spill.push((home, held));
}
}
#[must_use]
pub fn stripe(&self, i: usize) -> &Keyspace {
let home = i as u16;
for slot in self.room[..self.n].iter().flatten() {
if slot.0 == home {
return &slot.1;
}
}
let at = self
.spill
.binary_search_by_key(&home, |&(where_, _)| where_)
.expect("a stripe that was asked for");
&self.spill[at].1
}
#[must_use]
pub fn stripe_mut(&mut self, i: usize) -> &mut Keyspace {
let home = i as u16;
for slot in self.room[..self.n].iter_mut().flatten() {
if slot.0 == home {
return &mut slot.1;
}
}
let at = self
.spill
.binary_search_by_key(&home, |&(where_, _)| where_)
.expect("a stripe that was asked for");
&mut self.spill[at].1
}
#[must_use]
pub fn len(&self) -> usize {
self.n + self.spill.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use yo_index::Cursor as KeyCursor;
use super::{Db, MAX_STRIPES};
use crate::{Clock, Keyspace};
fn filled(stripes: usize, keys: u32) -> Db {
let mut db = Db::with_clock(Clock::fixed(1_000_000), stripes);
for i in 0..keys {
let key = format!("k{i}").into_bytes();
db.at(&key).setnx(&key, b"v").expect("room for a record");
}
db
}
#[test]
fn a_width_is_always_a_power_of_two_and_never_zero() {
for asked in [0, 1, 2, 3, 5, 8, 9, 100] {
let db = Db::with_clock(Clock::system(), asked);
assert!(db.width().is_power_of_two());
assert!(db.width() >= asked.max(1));
}
assert_eq!(Db::with_clock(Clock::system(), 10_000).width(), MAX_STRIPES);
}
#[test]
fn one_stripe_takes_every_key() {
let db = Db::with_clock(Clock::system(), 1);
for i in 0..1000u32 {
assert_eq!(db.stripe_of(&i.to_le_bytes()), 0);
}
}
#[test]
fn a_list_of_keys_is_on_one_stripe_or_it_is_not() {
let names: [&[u8]; 3] = [b"a", b"b", b"c"];
let one = Db::with_clock(Clock::system(), 1);
assert_eq!(one.one_stripe(names.into_iter()), Some(0));
assert_eq!(one.one_stripe(std::iter::empty()), None);
let many = Db::with_clock(Clock::system(), 16);
assert_eq!(many.one_stripe(names.into_iter()), None);
let home = many.stripe_of(b"a");
assert_eq!(many.one_stripe(std::iter::once(&b"a"[..])), Some(home));
assert_eq!(many.one_stripe([&b"a"[..], b"a"].into_iter()), Some(home));
}
#[test]
fn a_key_always_answers_the_same_stripe() {
let db = Db::with_clock(Clock::system(), 16);
for i in 0..1000u32 {
let key = i.to_le_bytes();
let first = db.stripe_of(&key);
assert_eq!(db.stripe_of(&key), first);
assert_eq!(db.stripe_of_hash(Keyspace::hash_of(&key)), first);
}
}
#[test]
fn the_stripe_number_moves_with_the_key() {
let db = Db::with_clock(Clock::system(), 16);
let mut seen = [0usize; 16];
for i in 0..1000u32 {
seen[db.stripe_of(&i.to_le_bytes())] += 1;
}
assert!(
seen.iter().all(|&n| n > 0),
"some stripe took no keys: {seen:?}"
);
}
#[test]
fn a_key_written_to_its_stripe_is_found_on_its_stripe() {
let mut db = Db::with_clock(Clock::system(), 8);
for i in 0..200u32 {
let key = i.to_le_bytes();
assert!(db.at(&key).setnx(&key, b"x").unwrap());
}
assert_eq!(db.len(), 200);
for i in 0..200u32 {
let key = i.to_le_bytes();
assert!(db.at(&key).exists(&key));
}
db.clear();
assert!(db.is_empty());
}
#[test]
fn a_scan_walks_every_stripe_and_answers_every_key_once() {
let db = filled(8, 2_000);
let mut seen: Vec<Vec<u8>> = Vec::new();
let mut at = KeyCursor::START;
let mut calls = 0;
loop {
at = db.scan(at, 10, None, |key| seen.push(key.to_vec()));
calls += 1;
if at.is_end() {
break;
}
assert!(calls < 10_000, "a scan that will not finish");
}
let unique: HashSet<Vec<u8>> = seen.iter().cloned().collect();
assert_eq!(unique.len(), 2_000);
assert_eq!(seen.len(), 2_000, "a quiet scan returned a key twice");
let mut walked = HashSet::new();
db.keys(|key| {
walked.insert(key.to_vec());
});
assert_eq!(unique, walked);
}
#[test]
fn a_scan_carries_the_stripe_in_the_cursor() {
let db = filled(8, 2_000);
let first = db.scan(KeyCursor::START, 10, None, |_| {});
assert!(!first.is_end());
let mut stripes = HashSet::new();
let mut at = KeyCursor::START;
loop {
at = db.scan(at, 10, None, |_| {});
if at.is_end() {
break;
}
stripes.insert(at.stripe());
}
assert_eq!(stripes.len(), 8, "some stripe was never the one in hand");
let beyond = KeyCursor::START.with_stripe(9);
let mut any = false;
assert!(db.scan(beyond, 10, None, |_| any = true).is_end());
assert!(!any);
}
#[test]
fn a_random_key_comes_from_whichever_stripe_still_has_one() {
let mut db = filled(8, 5_000);
let mut all = HashSet::new();
db.keys(|key| {
all.insert(key.to_vec());
});
let mut picked = HashSet::new();
for _ in 0..200 {
let mut key = Vec::new();
assert!(
db.random_key(|k| key.extend_from_slice(k)),
"the database is not empty"
);
assert!(all.contains(&key), "a key that is not there");
picked.insert(key);
}
assert!(picked.len() > 10, "only {} distinct keys", picked.len());
for i in 0..5_000u32 {
if i != 4_242 {
let key = format!("k{i}").into_bytes();
db.at(&key).del(&key);
}
}
for _ in 0..20 {
let mut key = Vec::new();
assert!(db.random_key(|k| key.extend_from_slice(k)));
assert_eq!(key, b"k4242");
}
db.at(b"k4242").del(b"k4242");
assert!(!db.random_key(|_| unreachable!("there are no keys left")));
}
#[test]
fn a_random_key_does_not_allocate() {
let db = filled(8, 500);
assert!(db.random_key(|_| {}), "the database is not empty");
let (_, allocs) = crate::tally::counted(|| {
for _ in 0..200 {
assert!(db.random_key(|_| {}), "the database is not empty");
}
});
assert_eq!(
allocs, 0,
"randomkey allocated {allocs} times in two hundred"
);
}
#[test]
fn holding_several_stripes_takes_each_of_them_once() {
let mut db = filled(16, 400);
let counts: Vec<usize> = (0..db.width()).map(|i| db.stripe_mut(i).len()).collect();
let held = db.hold_many([9, 2, 9, 0, 2, 15].into_iter());
assert_eq!(held.len(), 4, "six names, four stripes");
assert!(!held.is_empty());
for i in [0, 2, 9, 15] {
assert_eq!(held.stripe(i).len(), counts[i], "stripe {i} came back");
}
}
#[test]
fn holding_no_stripes_holds_nothing() {
let db = filled(4, 40);
let held = db.hold_many(std::iter::empty());
assert!(held.is_empty());
assert_eq!(held.len(), 0);
}
#[test]
fn holding_up_to_eight_stripes_does_not_allocate() {
let db = filled(16, 400);
let (_, allocs) = crate::tally::counted(|| {
for _ in 0..50 {
let held = db.hold_many((0..8).rev());
assert_eq!(held.len(), 8);
}
});
assert_eq!(allocs, 0, "holding eight stripes allocated {allocs} times");
}
#[test]
fn holding_more_stripes_than_there_is_room_for_still_holds_them_all() {
let mut db = filled(16, 400);
let counts: Vec<usize> = (0..db.width()).map(|i| db.stripe_mut(i).len()).collect();
let held = db.hold_many((0..16).rev());
assert_eq!(held.len(), 16);
for (i, &was) in counts.iter().enumerate() {
assert_eq!(held.stripe(i).len(), was, "stripe {i} came back");
}
}
}