mini-lsm通关笔记Week2Day6

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

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

Summary

在本章中,您将:

  • 实现WAL日志文件的编解码

  • 系统重启时使用WAL日志恢复memtable

要将测试用例复制到启动器代码中并运行它们,

bash 复制代码
cargo x copy-test --week 2 --day 6
cargo x scheck

Task 1-WAL Encoding

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

复制代码
src/wal.rs

在上一章中,我们已经实现了manifest文件,这样LSM状态就可以持久化了。同时我们实现了close函数,在停止引擎之前将所有的memtable转储到SST。现在,如果系统崩溃(即关机)怎么办?我们可以将memtable的修改记录到WAL(预写日志)中,并在重启数据库时读取WAL日志。只有当self.options.enable_wal = true时才启用WAL日志。

WAL日志的编码只是一个键值对列表。

复制代码
| key_len | key | value_len | value |

您还需要实现recover函数来读取WAL并恢复memtable的状态。

请注意,我们使用BufWriter来写入WAL日志。使用BufWriter可以减少使用操作系统系统调用的次数,从而优化写入路径的耗时。当用户修改key-value键值对时,数据不能保证立刻写入磁盘。相反,引擎只保证在调用sync时持久化数据。要正确地将数据持久化到磁盘,您需要首先通过调用flush()将数据从缓冲区写入器刷盘到文件对象,然后通过使用get_mut().sync_all()对文件执行fsync。请注意,您只需要在调用引擎的sync时进行fsync。不需要每次写入数据都进行fsync。

记录WAL日志

put函数,依次写入key_lenkeyvalue_lenvalue。MemTable在记录WAL日志后,还会调用sync函数强制刷盘。

rust 复制代码
pub fn put(&self, _key: &[u8], _value: &[u8]) -> Result<()> {
    let mut file = self.file.lock();
    let mut buf: Vec<u8> = Vec::new();
    let key_len = _key.len();
    // 写入key_len
    buf.put_u16(key_len as u16);
    // 写入key
    buf.put_slice(_key);
    let value_len = _value.len();
    // 写入value_len
    buf.put_u16(value_len as u16);
    // 写入value
    buf.put_slice(_value);
    file.write_all(&buf)?;
    Ok(())
}

pub fn sync(&self) -> Result<()> {
    let mut file = self.file.lock();
    file.flush()?;
    file.get_mut().sync_all()?;
    Ok(())
}

构造函数

WAL的构造函数有两个:

  1. create:新MenTable,创建与其对应的WAL日志
  2. recover:恢复过程创建WAL
create

可以参考其他的构造函数,一层一层new出来:

rust 复制代码
pub fn create(_path: impl AsRef<Path>) -> Result<Self> {
    Ok(Self {
        file: Arc::new(Mutex::new(
            BufWriter::new(
                OpenOptions::new()
                    .read(true)
                    .create_new(true)
                    .write(true)
                    .open(_path)
                    .context("failed to create wal")?,
            )
        )),
    })
}
recover

需要读取WAL日志,解析出对应的key_value键值对,插入到skiplist中:

rust 复制代码
pub fn recover(path: impl AsRef<Path>, skiplist: &SkipMap<Bytes, Bytes>) -> Result<Self> {
    let path = path.as_ref();
    let mut file = OpenOptions::new()
        .read(true)
        .append(true)
        .open(path)
        .context("failed to recover from WAL")?;
    let mut buf = Vec::new();
    // 将WAL日志内容读取到buf中
    file.read_to_end(&mut buf)?;
    let mut rbuf: &[u8] = buf.as_slice();
    while rbuf.has_remaining() {
        // 读取key_len
        let key_len = rbuf.get_u16() as usize;
        // 读取key
        let key = Bytes::copy_from_slice(&rbuf[..key_len]);
        rbuf.advance(key_len);
        // 读取value_len
        let value_len = rbuf.get_u16() as usize;
        // 读取value
        let value = Bytes::copy_from_slice(&rbuf[..value_len]);
        rbuf.advance(value_len);
        // 插入key_value键值对
        skiplist.insert(key, value);
    }
    Ok(Self {
        file: Arc::new(Mutex::new(BufWriter::new(file))),
    })
}

Task 2-Integrate WALs

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

复制代码
src/mem_table.rs
src/wal.rs
src/lsm_storage.rs

MemTable有一个WAL变量。如果wal变量为Some(wal),则在更新memtable时需要追加到WAL中。在LSM引擎中,如果enable_wal = true,则需要创建WAL。您还需要在创建新的memtable时使用ManifestRecord::NewMemtable记录一条WAL日志。

可以使用create_with_wal函数创建带有WAL的memtable。WAL应该写入存储目录下的<memtable_id>.wal中。如果此memtable作为L0 SST被转储,则memtable id应该与SST id相同。

  1. mem_table.rs文件修改内容,两个构造函数以及在put数据时记录WAL日志:
rust 复制代码
// 创建新的MemTable,并创建与之配套的WAL日志
pub fn create_with_wal(_id: usize, _path: impl AsRef<Path>) -> Result<Self> {
    Ok(
        MemTable {
            id: _id,
            map: Arc::new(SkipMap::new()),
            wal: Some(Wal::create(_path.as_ref())?),
            approximate_size: Arc::new(AtomicUsize::new(0)),
        }
    )
}

// 从WAL日志中恢复MemTable
pub fn recover_from_wal(_id: usize, _path: impl AsRef<Path>) -> Result<Self> {
    let map = Arc::new(SkipMap::new());
    Ok(Self {
        id: _id,
        wal: Some(Wal::recover(_path.as_ref(), &map)?),
        map,
        approximate_size: Arc::new(AtomicUsize::new(0)),
    })
}

pub fn put(&self, _key: &[u8], _value: &[u8]) -> Result<()> {
    ...
    // 记录WAL日志
    if let Some(ref wal) = self.wal {
        wal.put(_key, _value);
    }
    Ok(())
}
  1. wal.rs中的修改内容在上一小节的内容已展示

  2. lsm_storage.rs需要在创建MenTable时,记录Manifest日志。存在两处,一是创建数据库实例时会初始化一个MemTable,二是冻结MemTable时,会生成新的MemTable。同时这两个地方都需要根据enable_wal开关是否开启,创建是否需要记录WAL日志的MemTable

force_freeze_memtable:

rust 复制代码
pub fn force_freeze_memtable(&self, _state_lock_observer: &MutexGuard<'_, ()>) -> Result<()> {
    let memtable_id = self.next_sst_id();
    // 根据enable_wal开关是否开启,创建是否需要记录WAL日志的MemTable
    let new_men_table: Arc<MemTable> = if self.options.enable_wal {
        Arc::new(MemTable::create_with_wal(
            memtable_id,
            self.path_of_wal(memtable_id),
        )?)
    } else {
        Arc::new(MemTable::create(memtable_id))
    };
    {
        // 原始逻辑
        ...
    }

    // 记录Manifest日志
    self.manifest.as_ref().unwrap().add_record(
        _state_lock_observer,
        ManifestRecord::NewMemtable(memtable_id),
    )?;
    self.sync_dir()?;
    Ok(())
}

Task 3-Recover from the WALs

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

复制代码
src/lsm_storage.rs

如果启用了WAL,则在加载数据库时需要根据WAL恢复memtable。您还需要实现数据库的同步功能。sync的基本保障是引擎确信数据持久化到磁盘(并在重启时恢复)。要实现这一点,您可以简单地同步当前memtable对应的WAL。

复制代码
cargo run --bin mini-lsm-cli -- --enable-wal

记得从状态中恢复正确的next_sst_id,它应该是max{memtable id, sst id} + 1。在你的close函数中,如果enable_wal设置为true,你不应该将memtable转储到SST,因为WAL本身提供了持久化。在关闭数据库之前,应该等待所有的compaction和flush线程退出。

如图所示,需要在LsmStorageInner::open中添加如下流程:

同样的可以直接使用cat命令查看wal日志中的内容。

相关推荐
辰海Coding6 小时前
MiniSpring框架学习笔记-解决循环依赖的简化IoC容器
笔记·学习
晓梦林6 小时前
cp520靶场学习笔记
android·笔记·学习
心中有国也有家7 小时前
cann-recipes-infer:昇腾 NPU 推理的“菜谱集合”
经验分享·笔记·学习·算法
玄米乌龙茶1238 小时前
LLM成长笔记(三):API 开发基础
笔记
Upsy-Daisy8 小时前
AI Agent 项目学习笔记(八):Tool Calling 工具调用机制总览
人工智能·笔记·学习
LuminousCPP9 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习
一只机电自动化菜鸟10 小时前
一建机电备考笔记(40) 建筑机电施工—排水管道施工(含考频+题型)
经验分享·笔记·学习·职场和发展·课程设计
你干嘛?哎哟11 小时前
4月工作笔记
笔记
tom021811 小时前
软考中级《嵌入式系统设计师》全套备考资料(真题 + 教材 + 笔记)
笔记·嵌入式·软考·自学·电子技术·电子资料·变成
问心无愧051312 小时前
ctf show web入门156
笔记