mini-lsm通关笔记Week3Day4

项目地址:https://github.com/skyzh/mini-lsm

个人实现地址:https://gitee.com/cnyuyang/mini-lsm

在本章中,您将实现必要的结构来跟踪用户正在使用的最小读取时间戳,并在执行合并时从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);
    }
}
posted @   余为民同志  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示