use anyhow::{Context, Result};
use rocksdb::{BlockBasedOptions, Cache, Options, WriteBatch, DB};
use std::path::Path;
pub struct BucketIndex {
db: DB,
}
impl BucketIndex {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let mut opts = Options::default();
opts.create_if_missing(true);
opts.set_compression_type(rocksdb::DBCompressionType::Zstd);
opts.optimize_level_style_compaction(64 * 1024 * 1024);
let cache = Cache::new_lru_cache(64 * 1024 * 1024);
let mut block_opts = BlockBasedOptions::default();
block_opts.set_block_cache(&cache);
opts.set_block_based_table_factory(&block_opts);
opts.set_write_buffer_size(16 * 1024 * 1024);
opts.set_max_write_buffer_number(2);
let db = DB::open(&opts, path).context("open bucket index")?;
Ok(Self { db })
}
pub fn put_batch(&self, bucket_id: u64, ids: &[u64]) -> Result<()> {
if ids.is_empty() {
return Ok(());
}
let mut batch = WriteBatch::default();
for id in ids {
batch.put(id.to_le_bytes(), bucket_id.to_le_bytes());
}
self.db.write(batch).context("write bucket index batch")
}
#[allow(dead_code)]
pub fn delete_batch(&self, ids: &[u64]) -> Result<()> {
if ids.is_empty() {
return Ok(());
}
let mut batch = WriteBatch::default();
for id in ids {
batch.delete(id.to_le_bytes());
}
self.db.write(batch).context("delete bucket index batch")
}
pub fn get_many(&self, ids: &[u64]) -> Result<Vec<(u64, u64)>> {
let mut out = Vec::with_capacity(ids.len());
for id in ids {
if let Some(raw) = self
.db
.get(id.to_le_bytes())
.with_context(|| format!("read id {} from bucket index", id))?
{
if raw.len() == 8 {
let mut buf = [0u8; 8];
buf.copy_from_slice(&raw);
let bucket = u64::from_le_bytes(buf);
out.push((*id, bucket));
}
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bucket_index_round_trip() {
let dir = tempfile::tempdir().expect("tempdir");
let index = BucketIndex::open(dir.path()).expect("open index");
let ids = vec![1u64, 2u64, 3u64];
index.put_batch(7, &ids).expect("put");
let got = index
.get_many(&[1, 3, 4])
.expect("get")
.into_iter()
.collect::<Vec<_>>();
assert_eq!(got.len(), 2);
assert!(got.contains(&(1, 7)));
assert!(got.contains(&(3, 7)));
}
}