use std::sync::Arc;
use aok::{OK, Void};
use compio::runtime::Runtime;
use log::info;
use tempfile::tempdir;
use wdev::{Device, SegmentedDevice};
use wepoch::LightEpoch;
use whlog::{HybridLog, HybridLogConfig, RecordOutput, SECTOR_ALIGNMENT};
use wrecord::HEADER_SIZE;
use super::support::CountingDevice;
#[test]
fn test_disk_read_page_cache() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let db_path = dir.path().join("hlog_page_cache.db");
let device = Arc::new(CountingDevice::new(SegmentedDevice::single_file(&db_path)?));
let epoch = Arc::new(LightEpoch::new(16));
let page_size = SECTOR_ALIGNMENT;
let config = HybridLogConfig::new(page_size, 16, 0.5)?;
let hlog = HybridLog::new(config, device.clone(), epoch)?;
let mut recs: Vec<(u64, Vec<u8>, Vec<u8>)> = Vec::new();
for i in 0..7u8 {
let key = format!("k:{i:06}").into_bytes();
let val = vec![b'a' + i; 488];
let addr = hlog.append(&key, &val, 0, false)?;
recs.push((addr, key, val));
}
let tail_key = b"tail:000".to_vec();
let tail_val = vec![b'T'; 424]; let tail_addr = hlog.append(&tail_key, &tail_val, 0, false)?;
assert_eq!(tail_addr + 448, page_size as u64, "记录必须恰好抵达页尾");
recs.push((tail_addr, tail_key.clone(), tail_val.clone()));
let page1_end = 2 * page_size as u64;
let c_key = b"c:first1".to_vec();
let c_val = vec![b'c'; 100];
let c_addr = hlog.append(&c_key, &c_val, 0, false)?;
assert_eq!(c_addr, page_size as u64, "页尾满后新记录必须落在下一页开头");
let d_val = vec![b'd'; page_size - 148];
let d_addr = hlog.append(b"d:fill00", &d_val, 0, false)?;
assert_eq!(d_addr + 3972, page1_end, "第 1 页必须被记录精确填满");
let page2_end = 3 * page_size as u64;
let e_key = b"e:first2".to_vec();
let e_val = vec![b'e'; 100];
let e_addr = hlog.append(&e_key, &e_val, 0, false)?;
assert_eq!(e_addr, page2_end - page_size as u64);
let f_val = vec![b'f'; page_size - 148];
let f_addr = hlog.append(b"f:fill00", &f_val, 0, false)?;
assert_eq!(f_addr + 3972, page2_end);
for p in 0..3u64 {
hlog.flush_page(p).await?;
}
hlog.shift_read_only_address(page2_end);
hlog.shift_head_address(page2_end);
assert!(hlog.is_on_disk(tail_addr));
async fn assert_disk_read<D: Device>(
hlog: &HybridLog<D>,
addr: u64,
key: &[u8],
val: &[u8],
) -> Void {
let out = hlog.read_disk_record(addr).await?;
assert!(matches!(out, RecordOutput::Disk(_)));
assert_eq!(out.key()?, key);
assert_eq!(out.value()?, val);
OK
}
assert_disk_read(&hlog, e_addr, &e_key, &e_val).await?;
assert_disk_read(&hlog, f_addr, b"f:fill00", &f_val).await?;
for (addr, key, val) in &recs {
assert_disk_read(&hlog, *addr, key, val).await?;
}
assert_disk_read(&hlog, c_addr, &c_key, &c_val).await?;
assert_disk_read(&hlog, d_addr, b"d:fill00", &d_val).await?;
let reads_filled = device.reads();
assert_eq!(
reads_filled, 6,
"三页 12 条点读应恰好产生 6 次设备 I/O(每页 1 次 probe + 1 次整页装载)"
);
assert_disk_read(&hlog, tail_addr, &tail_key, &tail_val).await?;
assert_disk_read(&hlog, c_addr, &c_key, &c_val).await?;
assert_eq!(device.reads(), reads_filled, "二次回验必须全部命中缓存");
assert_disk_read(&hlog, e_addr, &e_key, &e_val).await?;
assert_disk_read(&hlog, f_addr, b"f:fill00", &f_val).await?;
assert_disk_read(&hlog, e_addr, &e_key, &e_val).await?;
assert_eq!(
device.reads(),
reads_filled + 2,
"页 2 重读应恰好产生 1 次 probe + 1 次整页重装载"
);
assert_disk_read(&hlog, tail_addr, &tail_key, &tail_val).await?;
assert_disk_read(&hlog, recs[0].0, &recs[0].1, &recs[0].2).await?;
assert_eq!(
device.reads(),
reads_filled + 4,
"槽位冲突驱逐后同页重读应恰好产生 1 次 probe + 1 次整页重装载"
);
info!("点读冷路径整页磁盘读缓存测试通过");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn test_disk_read_adaptive_install() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let db_path = dir.path().join("hlog_adaptive_install.db");
let device = Arc::new(CountingDevice::new(SegmentedDevice::single_file(&db_path)?));
let epoch = Arc::new(LightEpoch::new(16));
let page_size = 64 * 1024;
const RECS_PER_PAGE: u64 = 16;
let val_len = 4092 - HEADER_SIZE - 8;
let config = HybridLogConfig::new(page_size, 16, 0.5)?;
let hlog = HybridLog::new(config, device.clone(), epoch)?;
let mut recs = Vec::new();
for page in 0..5u64 {
for i in 0..RECS_PER_PAGE {
let key = format!("k:{page:02}:{i:02}").into_bytes();
let val = vec![b'a' + (i as u8 % 26); val_len];
let addr = hlog.append(&key, &val, 0, false)?;
recs.push((addr, key, val));
}
}
let tail_key = b"tail:0000".to_vec();
let tail_addr = hlog.append(&tail_key, &vec![b'T'; val_len], 0, false)?;
assert_eq!(tail_addr / page_size as u64, 5, "收尾记录必须落在第 5 页");
for p in 0..=5u64 {
hlog.flush_page(p).await?;
}
let tail = hlog.tail_address();
hlog.shift_read_only_address(tail);
hlog.shift_head_address(tail);
let rec = |page: u64, i: u64| &recs[(page * RECS_PER_PAGE + i) as usize];
async fn assert_disk_read<D: Device>(
hlog: &HybridLog<D>,
addr: u64,
key: &[u8],
val: &[u8],
) -> Void {
let out = hlog.read_disk_record(addr).await?;
assert!(matches!(out, RecordOutput::Disk(_)));
assert_eq!(out.key()?, key);
assert_eq!(out.value()?, val);
OK
}
let reads_before = device.reads();
let bytes_before = device.read_bytes();
for i in 0..3u64 {
let (addr, key, val) = rec(3, i);
assert_disk_read(&hlog, *addr, key, val).await?;
}
assert_eq!(
device.reads() - reads_before,
2,
"同页 3 条点读应恰好产生 2 次 I/O:1 次 probe + 1 次整页装载,第三条命中零 I/O"
);
let seq_bytes = device.read_bytes() - bytes_before;
assert!(
seq_bytes >= (page_size + 4096) as u64 && seq_bytes < (page_size + 8192) as u64,
"顺序读应恰好产生 1 次整页装载({page_size} 字节)+ 1 次 probe(4KB 级),实际 {seq_bytes} 字节"
);
let (addr1, key1, val1) = rec(3, 1);
assert_disk_read(&hlog, *addr1, key1, val1).await?;
assert_eq!(
device.reads() - reads_before,
2,
"已装载页重读必须全部命中缓存"
);
let reads_before = device.reads();
let bytes_before = device.read_bytes();
for page in [0u64, 2, 4, 0, 2, 4, 0] {
let (addr, key, val) = rec(page, 0);
assert_disk_read(&hlog, *addr, key, val).await?;
}
assert_eq!(
device.reads() - reads_before,
7,
"均匀随机读每次未命中应恰好一次 probe 级设备 I/O"
);
let rand_bytes = device.read_bytes() - bytes_before;
assert!(
rand_bytes < 7 * 8192,
"均匀随机读总 I/O 必须为 probe 级小读之和,不得出现任何整页装载(单次整页即 {page_size} 字节): {rand_bytes}"
);
assert_disk_read(&hlog, *addr1, key1, val1).await?;
assert_eq!(
device.reads() - reads_before,
7,
"顺序装载页不得被随机 probe 驱逐"
);
let (addr2, key2, val2) = rec(2, 0);
assert_disk_read(&hlog, *addr2, key2, val2).await?;
assert_eq!(
device.reads() - reads_before,
8,
"随机页重读仍应仅 probe,不得整页装载"
);
info!("磁盘读缓存连续性装载门槛测试通过");
aok::Result::<()>::Ok(())
})?;
OK
}