mini-lsm通关笔记Week3Day5

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

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

在本章中,您将实现Transaction的所有接口。您的实现将为事务内的修改维护一个私有工作区,并批量提交它们,以便事务内的所有修改在提交之前仅对事务本身可见。我们只在提交时检查冲突(即可序列化冲突),这就是乐观并发控制(OCC)。

要运行测试用例,请执行以下操作:

cargo x copy-test --week 3 --day 5
cargo x scheck

Task 1-Local Workspace + Put and Delete

在此任务中,您需要修改:

src/txn.rs

现在可以通过向local_storage中插入相应的key/value来实现putdeletelocal_storage是一个存储没有时间戳的键(key)的memtable。请注意,对于删除,您仍然需要将其实现为插入空值,而不是从跳表中删除值。

实现类似MemTableput插入函数:

pub fn put(&self, key: &[u8], value: &[u8]) {
    self.local_storage
        .insert(Bytes::copy_from_slice(key), Bytes::copy_from_slice(value));
}

delete删除函数:

pub fn delete(&self, key: &[u8]) {
    self.local_storage
        .insert(Bytes::copy_from_slice(key), Bytes::new());
}

Task 2-Get and Scan

在此任务中,您需要修改:

src/txn.rs

对于get,您应该首先在local_storage中查找。如果找到值,则根据是否为删除标记,返回该值或None。对于scan,当您为没有时间戳的键(key)的memtable实现迭代器时,您需要为skiplist实现一个TxnLocalIterator,如第1.1章所述。你需要在TxnIterator中存储一个TwoMergeIterator<TxnLocalIterator, FusedIterator<LsmIterator>>。最后,鉴于TwoMergeIterator将保留子迭代器中的删除标记,您需要修改TxnIterator实现以正确处理删除的记录。

TxnLocalIterator

这个迭代器,用于遍历local_storage。该部分的实现完全可以参照MemTableIterator

impl TxnLocalIterator {
    fn entry_to_item(entry: Option<Entry<'_, Bytes, Bytes>>) -> (Bytes, Bytes) {
        entry
            .map(|x| (x.key().clone(), x.value().clone()))
            .unwrap_or_else(|| (Bytes::new(), Bytes::new()))
    }
}

impl StorageIterator for TxnLocalIterator {
    type KeyType<'a> = &'a [u8];

    fn value(&self) -> &[u8] {
        &self.borrow_item().1[..]
    }

    fn key(&self) -> &[u8] {
        &self.borrow_item().0[..]
    }

    fn is_valid(&self) -> bool {
        !self.borrow_item().0.is_empty()
    }

    fn next(&mut self) -> Result<()> {
        let entry = self.with_iter_mut(|iter| TxnLocalIterator::entry_to_item(iter.next()));
        self.with_mut(|x| *x.item = entry);
        Ok(())
    }
}

pub fn scan(self: &Arc<Self>, lower: Bound<&[u8]>, upper: Bound<&[u8]>) -> Result<TxnIterator> {
    let mut local_iter = TxnLocalIteratorBuilder {
        map: self.local_storage.clone(),
        iter_builder: |map| map.range((map_bound(lower), map_bound(upper))),
        item: (Bytes::new(), Bytes::new()),
    }
        .build();
    let entry = local_iter.with_iter_mut(|iter| TxnLocalIterator::entry_to_item(iter.next()));
    local_iter.with_mut(|x| *x.item = entry);

    ...
}

TxnIterator

内部有一个TwoMergeIterator<TxnLocalIterator, FusedIterator<LsmIterator>>类型的迭代器,需要在此基础上,实现跳过local_storage中被删除记录的操作。

impl TxnIterator {
    pub fn create(
        txn: Arc<Transaction>,
        iter: TwoMergeIterator<TxnLocalIterator, FusedIterator<LsmIterator>>,
    ) -> Result<Self> {
        let mut iter = Self { _txn: txn, iter };
        iter.skip_deletes()?;  // 【新增】跳过被删除记录
        Ok(iter)
    }

     // 【新增】跳过被删除记录
    fn skip_deletes(&mut self) -> Result<()> {
        while self.iter.is_valid() && self.iter.value().is_empty() {
            self.iter.next()?;
        }
        Ok(())
    }
}

impl StorageIterator for TxnIterator {
    ...
    fn next(&mut self) -> Result<()> {
        self.iter.next()?;
        self.skip_deletes()?; // 【新增】跳过被删除记录
        Ok(())
    }
    ...
}

Task 3-Commit

在此任务中,您需要修改:

src/txn.rs

我们假设事务只能在单个线程上使用。一旦你的事务进入提交阶段,你应该将self.commited设置为true,这样用户就不能对事务做任何其他操作了。如果事务已经提交,则putdeletescanget实现应该出错。

您的提交实现应该简单地从本地存储收集所有键值对,并向存储引擎提交一个写入批处理。

先使用原子操作修改committed变量,在生成批量操作,再执行。

pub fn commit(&self) -> Result<()> {
    self.committed
        .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
        .expect("cannot operate on committed txn!");
    let batch = self
        .local_storage
        .iter()
        .map(|entry| {
            if entry.value().is_empty() {
                WriteBatchRecord::Del(entry.key().clone())
            } else {
                WriteBatchRecord::Put(entry.key().clone(), entry.value().clone())
            }
        })
        .collect::<Vec<_>>();
    self.inner.write_batch(&batch)
}

同时在putdeletescanget函数的开始添加:

if self.committed.load(Ordering::SeqCst) {
    panic!("cannot operate on committed txn!");
}

Task 4-Atomic WAL

在此任务中,您需要修改:

src/wal.rs
src/mem_table.rs

请注意,提交涉及到产生一个写批次,目前,写批次并不保证原子性。您需要更改WAL实现以生成写入批处理的页眉和页脚。

新的WAL编码如下:

|   HEADER   |                          BODY                                      |  FOOTER  |
|     u32    |   u16   | var | u64 |    u16    |  var  |           ...            |    u32   |
| batch_size | key_len | key | ts  | value_len | value | more key-value pairs ... | checksum |

batch_sizeBODY部分的大小,checksum是BODY部分的校验和。

没有测试用例来验证您的实现。只要你通过了所有现有的测试用例,并实现了上面的WAL格式,一切都应该没问题。

您应该实现Wal::put_batch和MemTable::put_batch。原始的put函数应该将单个键值对视为一个批处理。也就是说,此时,你的put函数应该调用put_batch。

一个批处理应该在同一个mem表和同一个WAL中处理,即使它超过mem表大小限制。

跳过,查看skyzh的实现也没有相关实现。

https://github.com/skyzh/mini-lsm-solution-checkpoint/commit/e02fffb3eb941564cb5117a1e75ce94f40b037ab

posted @   余为民同志  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示