mini-lsm通关笔记Week3Day4
在本章中,您将实现必要的结构来跟踪用户正在使用的最小读取时间戳,并在执行合并时从SST中清理不会再使用的版本。
要运行测试用例,请执行以下操作:
cargo x copy-test --week 3 --day 4
cargo x scheck
Task 1-Implement Watermark
在此任务中,您需要修改:
src/mvcc/watermark.rs
Watermark是用来跟踪系统中最小的
read_ts
的结构。当一个新事务被创建时,它应该调用add_reader
来添加它的读取时间戳以便跟踪。当一个事务中止或提交时,它应该将自己从watermark中移除。当调用watermark()
时,watermark结构返回系统中最小的read_ts
。如果没有正在进行的事务,它将简单地返回None
。可以使用
BTreeMap
实现Watermark。它维护一个计数器,显示每个read_ts
有多少快照正在使用此读取时间戳。在b-tree表中不应该有读取器为0的条目。
就是对BTreeMap
使用得封装:
add_reader,已存在就计数值加1,不存在就新插入:
pub fn add_reader(&mut self, ts: u64) {
let mut count: usize = 1;
if self.readers.contains_key(&ts) {
count += self.readers.get(&ts).unwrap();
}
self.readers.insert(ts, count);
}
remove_reader,计数值先减1,若减为0则移除该元素:
pub fn remove_reader(&mut self, ts: u64) {
let cnt = self.readers.get_mut(&ts).unwrap();
*cnt -= 1;
if *cnt == 0 {
self.readers.remove(&ts);
}
}
watermark,若空返回None
,否则返回最小时间戳,因为BTreeMap
是B树,是一种自平衡的多路搜索树,可以使用first_key_value
获取最小值:
pub fn watermark(&self) -> Option<u64> {
if self.readers.is_empty() {
None
} else {
Some(*self.readers.first_key_value().unwrap().0)
}
}
测试用例中,还调用了num_retained_snapshots这个函数,这个函数不在模板中,需要自己新增,返回系统中有多少在运行的时间戳:
pub fn num_retained_snapshots(&self) -> usize {
self.readers.len()
}
Task 2-Maintain Watermark in Transactions
在此任务中,您需要修改:
src/mvcc/txn.rs src/mvcc.rs
您需要在事务开始时将
read_ts
添加到watermark中,并在为事务调用drop
时将其移除。
事务开始new_txn
函数,调用add_reader
函数:
pub fn new_txn(&self, inner: Arc<LsmStorageInner>, serializable: bool) -> Arc<Transaction> {
let mut ts = self.ts.lock(); // 【修改】添加mut关键字
let read_ts = ts.0;
ts.1.add_reader(read_ts); // 【新增】事务开始时将read_ts添加到watermark中
Arc::new(Transaction {
read_ts,
inner,
local_storage: Arc::new(SkipMap::new()),
committed: Arc::new(AtomicBool::new(false)),
key_hashes: None,
})
}
drop
移除函数,调用remove_reader
函数:
impl Drop for Transaction {
fn drop(&mut self) {
self.inner.mvcc().ts.lock().1.remove_reader(self.read_ts);
}
}
Task 3-Garbage Collection in Compaction
在此任务中,您需要修改:
src/compact.rs
现在我们有了watermark系统,我们可以在合并过程中清理不会被使用的版本。
- 如果某个键的版本高于watermark保存的最小读取时间戳,则保留该密钥。
- 对于小于或等于watermar中所有的时间戳,保留最新版本。
例如,如果我们有watermark=3和以下数据:
a@4=del <- above watermark a@3=3 <- latest version below or equal to watermark a@2=2 <- can be removed, no one will read it a@1=1 <- can be removed, no one will read it b@1=1 <- latest version below or equal to watermark c@4=4 <- above watermark d@3=del <- can be removed if compacting to bottom-most level d@2=2 <- can be removed
如果我们对这些键执行合并,将得到:
a@4=del a@3=3 b@1=1 c@4=4 d@3=del (can be removed if compacting to bottom-most level)
假设这些都是引擎中的密钥。如果我们在ts=3处进行扫描,我们将在合并之前/之后得到
a=3、b=1、c=4
。如果我们在ts=4处执行扫描,我们将在合并之前/之后得到b=1、c=4
。合并不会也不应该影响读取时间戳>=watermark的事务。
因为测试用例中只有ForceFullCompaction
,所以只修改该合并代码。
let watermark = self.mvcc().watermark(); // 【新增】获取watermark
let mut first_key_below_watermark = false; // 【新增】小于或等于watermar中所有的时间戳,保留最新版本
let mut last_key = Vec::<u8>::new(); //【新增】上一个键,用于和当前键比较
while iter.is_valid() {
let key = iter.key();
let value = iter.value();
let same_as_last_key = iter.key().key_ref() == last_key; // 【新增】和上一个键是否一样
last_key = iter.key().key_ref().to_vec(); // 【新增】更新上一个键
if same_as_last_key {
if !first_key_below_watermark && key.ts() <= watermark { // 【新增】小于或等于watermar中所有的时间戳,的最新版本
first_key_below_watermark = true;
} else if key.ts() <= watermark {
iter.next().unwrap(); // 【新增】和上个键一样,不是最新版本,删除该记录
continue;
}
// 【新增】大于watermar中最小的时间戳,需保留,往下走
} else if key.ts() <= watermark {
first_key_below_watermark = true; // 【新增】和上个键不一样,小于或等于watermar中所有的时间戳,保留最新版本
} else {
first_key_below_watermark = false; // 【新增】和上个键不一样,大于watermar中最小的时间戳,需保留,设置标志位
}
if key.ts() <= watermark && !first_key_below_watermark { // 【新增】小于或等于watermar中所有的时间戳,不是最新版本,删除!
iter.next().unwrap();
continue;
}
if key.ts() <= watermark && value.is_empty() && first_key_below_watermark { // 【新增】小于或等于watermar中所有的时间戳,是需要保留的最新版本,但是合并后放在最低一层,可以删除
iter.next().unwrap();
continue;
}
builder.add(key, value);
iter.next().unwrap();
if builder.estimated_size() >= self.options.target_sst_size {
let id = self.next_sst_id();
let sst = builder.build(id, None, self.path_of_sst(id))?;
result.push(Arc::new(sst));
builder = SsTableBuilder::new(self.options.block_size);
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战