use slatedb::{
WriteBatch,
config::{CompactorOptions, FlushOptions, FlushType},
};
use super::*;
async fn read_rows(db: &Db) {
for key in (0_u64..8192).step_by(43) {
assert_eq!(
db.get(key.to_be_bytes()).await.unwrap().unwrap().as_ref(),
&[2; 128]
);
}
assert!(db.get(8192_u64.to_be_bytes()).await.unwrap().is_none());
}
async fn compacted_fixture(
admission: bool,
cache: &cache::TestCache,
) -> (Db, Arc<cache::CacheCounters>) {
let objects: Arc<dyn ObjectStore> = Arc::new(object_store::memory::InMemory::new());
let options = StoreBuilder::new("compaction-cache", objects.clone())
.cache_puts(false)
.cache_compaction_puts(admission);
let settings = Settings {
compactor_options: Some(CompactorOptions {
poll_interval: Duration::from_millis(20),
scheduler_options: [("min_compaction_sources".into(), "2".into())].into(),
..CompactorOptions::default()
}),
..options.settings()
};
let counters = Arc::clone(&cache.counters);
let db = Db::builder("compaction-cache", objects.clone())
.with_settings(settings)
.with_sst_block_size(SST_BLOCK_SIZE)
.with_block_cache_policy(options.block_cache_policy())
.with_db_cache(cache.handle.clone())
.with_metrics_recorder(cache::recorder(counters.clone()))
.build()
.await
.unwrap();
for version in [1_u8, 2] {
let mut batch = WriteBatch::new();
for key in 0_u64..8193 {
if version == 2 && key == 8192 {
batch.delete(key.to_be_bytes());
} else {
batch.put(key.to_be_bytes(), [version; 128]);
}
}
db.write(batch).await.unwrap();
db.flush_with_options(FlushOptions {
flush_type: FlushType::MemTable,
})
.await
.unwrap();
}
let admin = AdminBuilder::new("compaction-cache", objects).build();
tokio::time::timeout(Duration::from_secs(20), async {
loop {
if let Some(compactions) = admin.read_compactions(None).await.unwrap()
&& compactions
.recent_compactions()
.any(|job| !job.output_ssts().is_empty() && !job.active())
{
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
})
.await
.unwrap();
db.refresh_manifest().await.unwrap();
(db, counters)
}
#[tokio::test]
async fn compaction_admission_and_eviction() {
for disk in [false, true] {
let directory =
std::env::temp_dir().join(format!("moraine-compaction-cache-{}", Uuid::new_v4()));
for admission in [false, true] {
let path = directory.join(admission.to_string());
let cache =
cache::TestCache::new(16 * 1024 * 1024, disk.then_some(path.as_path())).await;
let (db, counters) = compacted_fixture(admission, &cache).await;
let before = counters.object_store_tally();
let before_cache = counters.tally();
read_rows(&db).await;
let reads = counters.object_store_tally().since(before).main_gets;
let cache_reads = counters.tally().since(before_cache);
eprintln!(
"compaction disk={disk} admission={admission}: first-touch {reads} GETs, {cache_reads:?}"
);
assert_eq!(
cache_reads.metadata_misses + cache_reads.block_misses == 0,
admission
);
cache.resize(512 * 1024);
assert!(
cache.usage() <= 512 * 1024,
"resize did not evict resident entries: {}",
cache.usage()
);
let before = counters.object_store_tally();
let before_cache = counters.tally();
read_rows(&db).await;
let reads = counters.object_store_tally().since(before).main_gets;
assert!(
cache.usage() <= 512 * 1024,
"memory admission exceeded its resized budget: {}",
cache.usage()
);
let after_eviction = counters.tally().since(before_cache);
if disk {
assert!(
after_eviction.block_disk_hits > 0,
"evicted blocks read back from disk count as disk hits: {after_eviction:?}"
);
assert!(
after_eviction.block_disk_hits <= after_eviction.block_hits,
"a disk hit is one of the hits: {after_eviction:?}"
);
} else {
assert!(
after_eviction.block_misses > 0,
"admitted blocks never evicted"
);
assert_eq!(
after_eviction.block_disk_hits, 0,
"no disk tier, no disk hits"
);
}
eprintln!(
"after eviction disk={disk} admission={admission}: {reads} GETs, {} resident bytes",
cache.usage()
);
db.close().await.unwrap();
}
if disk {
std::fs::remove_dir_all(directory).unwrap();
}
}
}