mini-lsm通关笔记Week2Day6

news/2025/2/6 11:21:28/

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

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

Summary

在本章中,您将:

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

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

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

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函数强制刷盘。

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_lenbuf.put_u16(key_len as u16);// 写入keybuf.put_slice(_key);let value_len = _value.len();// 写入value_lenbuf.put_u16(value_len as u16);// 写入valuebuf.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出来:

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中:

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_lenlet key_len = rbuf.get_u16() as usize;// 读取keylet key = Bytes::copy_from_slice(&rbuf[..key_len]);rbuf.advance(key_len);// 读取value_lenlet value_len = rbuf.get_u16() as usize;// 读取valuelet 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日志:
// 创建新的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:

pub fn force_freeze_memtable(&self, _state_lock_observer: &MutexGuard<'_, ()>) -> Result<()> {let memtable_id = self.next_sst_id();// 根据enable_wal开关是否开启,创建是否需要记录WAL日志的MemTablelet 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日志中的内容。


http://www.ppmy.cn/news/1569781.html

相关文章

网络安全视角:从地域到账号的阿里云日志审计实践

作者&#xff1a;羿莉&#xff08;萧羿&#xff09; 为什么要进行日志审计&#xff1f; 日志集中审计分析是现代信息安全管理中的关键组成部分&#xff0c;将来自不同地域、不同账号甚至不同云产品来源的日志数据进行集中化处理和分析&#xff0c;能够打破 “信息孤岛” &…

Unity游戏(Assault空对地打击)开发(7) 飞机坠毁后的操作

前言 本文之后基本操作不再演示。 详细操作 导入Free Fire VFX插件&#xff0c;生成火的效果。 在该文件夹下挑一个你喜欢的火&#xff0c;拖至Camera下&#xff0c;重命名为Fire。 调整一下火的位置&#xff0c;让摄像机清晰看到火&#xff0c;如下图&#xff0c;火在摄像机的…

vue2-mixin的定义与和使用

文章目录 1. 什么是mixin2. 局部混入3. 全局混入4. 多mixin混入冲突4.1 替换性4.2 合并型4.3 合并队列型4.4 叠加性 5. 使用场景 #vue2-mixin的使用 1. 什么是mixin Mixin是面向对象语言中的一个类&#xff0c;提供了方法的实现&#xff0c;其他类可以访问mixin类的方法而不用…

[SAP ABAP] 面向对象程序设计-属性和方法

属性( Attributes) &#xff1a;对象的属性及特征 方法( Method )&#xff1a;定义对象的行为 从对象属性和方法是属于类还是属于对象的实例&#xff0c;可以区分为静态属性(static attributes) / 静态方法(static methods)&#xff0c;实例属性(instance attributes) / 实例方…

标准库发送数据深入理解USART

如何使用USART&#xff08;编程理论讲解&#xff09; 如下是串口发送信息的原理图&#xff0c;CPU将数据写入TDR寄存器&#xff0c;然后串口外设将寄存器中的数据发送出去 这就是串口发送的全部流程 (图中所有图片均来自博主 铁头山羊) 在这个发送流程的过程中&#xff0c;我…

如何在 Kafka 中实现自定义分区器

今天我来给大家分享一下如何在 Kafka 中实现一个自定义分区器。Kafka 是一个分布式流处理平台&#xff0c;能够高效地处理海量数据。默认情况下&#xff0c;Kafka 使用键的哈希值来决定消息应该发送到哪个分区&#xff0c;但是有时我们需要根据特定的业务逻辑来定制分区策略。这…

需求分析应该从哪些方面来着手做?

需求分析一般可从以下几个方面着手&#xff1a; 业务需求方面 - 与相关方沟通&#xff1a;与业务部门、客户等进行深入交流&#xff0c;通过访谈、问卷调查、会议讨论等方式&#xff0c;明确他们对项目的期望、目标和整体业务需求&#xff0c;了解项目要解决的业务问题及达成的…

S4 HANA手工记账Tax Payable – FB41

本文主要介绍在S4 HANA OP中手工记账Tax Payable – FB41。具体请参照如下内容&#xff1a; 手工记账Tax Payable – FB41 该事务代码用于手工处理税码统驭科目的记账&#xff0c;一般税码科目需要设置为只能自动记账&#xff0c;因此无法手工对税码统驭科目记账&#xff0c;但…