mini-lsm通关笔记Week2Day5

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

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

Summary

在本章中,您将:

  • 实现manifest文件的编解码。
  • 系统重启时从manifest文件中恢复。

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

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

Task 1-Manifest Encoding

系统使用manifest文件来记录引擎中发生的所有操作。目前只有两种类型:合并和转储SST。当引擎重新启动时,它将读取manifest文件,重建状态,并将磁盘上SST文件加载到内存中。

存储LSM状态的方法有很多。最简单的方法之一是简单地将完整状态存储到JSON文件中。每当我们执行一次合并或转储SST时,我们可以将整个LSM状态序列化到一个文件中。这种方法的问题是,当数据库变得超大(即10k SST)时,将manifest写入磁盘将超级慢。因此,我们将manifest设计为一个追加写的文件。

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

src/manifest.rs

我们使用JSON对manifest进行编码。你可以使用serde_json::to_vec将manifest编码为json,并将其写入manifest文件,然后执行fsync。当你从manifest文件读取时,你可以使用serde_json::Deserializer::from_slice,它将返回一个记录流。你不需要存储记录长度等,因为serde_json可以自动找到记录的拆分。

manifest文件格式如下:

| JSON record | JSON record | JSON record | JSON record |

再次注意,我们并没有记录每条记录有多少字节的信息。

在引擎运行几个小时后,manifest文件可能会变得非常大。此时,您可以定期压缩manifest文件以存储当前快照并截断日志。这是您可以作为奖励任务的一部分实现的优化。

serde_json该库可以实现JSON的自动拆分,就是说serde_json::Deserializer::from_slice可以解析如下格式的json文件:

{...
}
{...
}
{...
}

与标准的json数组相比前后不需要[]包裹,中间不需要,分隔。

所有我们实现add_record_when_init函数只需要序列化对象,然后对文件进行追加写操作:

pub fn add_record_when_init(&self, record: ManifestRecord) -> Result<()> {// 获取锁,避免两个线程竞争写入let mut file = self.file.lock();// 将对象序列化成二进制数据let buf = serde_json::to_vec(&record)?;// 写入文件file.write_all(&buf)?;// 避免操作系统缓存,强制写入磁盘file.sync_all()?;Ok(())
}

Task 2-Write Manifests

现在,您可以继续并修改您的LSM引擎以在必要时写入manifest文件。在此任务中,您需要修改:

src/lsm_storage.rs
src/compact.rs

目前,我们只使用两种类型的manifest记录:转储SST和合并。转储SST操作的manifest记录中存储转储到磁盘的SST id。合并操作的manifest记录中存储了合并任务和生成的SST id。每次向磁盘写入一些新文件时,首先同步文件和存储目录,然后写入manifest并同步manifest。manifest文件应写入<path>/MANIFEST

要同步目录,可以实现sync_dir函数,其中可以使用File::open(dir).sync_all()?来同步它。在Linux上,目录是一个文件,包含目录中的文件列表。通过在目录上执行fsync,您将确保在断电时,新写入的(或删除的)文件可以对用户可见。

记住为后台合并触发器(leveled/simple/universal)和用户请求执行强制合并时写一个合并manifest记录。

  • 创建Manifests文件,先不考虑恢复场景,修改LsmStorageInner::open函数
let mut manifest = None;
if !manifest_path.exists() {manifest = Some(Manifest::create(manifest_path)?);
}...let storage = Self {...manifest,...
};
Ok(storage)
  • 转储SST时写入Manifests文件,修改force_flush_next_imm_memtable,在转储后记录一条记录,ManifestRecord::Flush的变体中只需要记录sst_id
pub fn force_flush_next_imm_memtable(&self) -> Result<()> {...self.manifest.as_ref().unwrap().add_record(&_state_lock, ManifestRecord::Flush(sst_id))?;self.sync_dir()?;
}
  • 合并sst写入Manifests文件,修改trigger_compaction,在合并任务后记录一条记录,ManifestRecord::Compaction的变体中只需要记录合并的task任务和合并结果产生的新的sst
self.manifest.as_ref().unwrap().add_record(&_state_lock, ManifestRecord::Compaction(task, output))?;self.sync_dir()?;

Task 3-Flush on Close

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

src/lsm_storage.rs

您需要实现close函数。如果self.options.enable_wal = false(我们将在下一章介绍WAL),那么在停止存储引擎之前,应该将所有的memtable转储到磁盘,这样所有的用户更改都会被持久化。

此前的任务中修改过close函数,就是在close前关闭合并转储线程。新增逻辑:

  • 开启enable_wal开关,待合并转储线程线程停止后直接返回

  • 未开启enable_wal开关,应该将所有的memtable转储到磁盘

pub fn close(&self) -> Result<()> {// 向合并线程发送停止信号self.compaction_notifier.send(()).ok();// 向转储线程发送停止信号self.flush_notifier.send(()).ok();let mut compaction_thread = self.compaction_thread.lock();if let Some(compaction_thread) = compaction_thread.take() {compaction_thread.join().map_err(|e| anyhow::anyhow!("{:?}", e))?;}let mut flush_thread = self.flush_thread.lock();if let Some(flush_thread) = flush_thread.take() {flush_thread.join().map_err(|e| anyhow::anyhow!("{:?}", e))?;}// 开启enable_wal开关直接返回if self.inner.options.enable_wal {return Ok(());}// 未enable_wal开关,转储所有`memtable`if !self.inner.state.read().memtable.is_empty() {self.inner.force_freeze_memtable(&self.inner.state_lock.lock())?;}while {let snapshot = self.inner.state.read();!snapshot.imm_memtables.is_empty()} {self.inner.force_flush_next_imm_memtable()?;}self.inner.sync_dir()?;Ok(())
}

Task 4-Recover from the State

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

src/lsm_storage.rs

现在,您可以修改open函数以从manifest文件中恢复引擎状态。要恢复它,您需要首先生成需要加载的SST列表。您可以通过调用apply_compaction_result并恢复LSM状态下的SST id来完成此操作。之后,您可以迭代状态并加载所有SST(更新sstables哈希映射)。在此过程中,您需要计算最大SST id并更新next_sst_id字段。之后,您可以使用该id创建一个新的memtable,并将id递增1。

如果您实施了分级合并,则可能在每次应用合并结果时对SST进行排序。但是,使用manifest recover,你的排序逻辑将被破坏,因为在恢复过程中,你无法知道每个SST的开始键和结束键。要解决这个问题,您需要读取apply_compaction_result函数的in_recovery标志。在恢复过程中,不应尝试检索SST的第一个密钥。在LSM状态恢复并打开所有SST之后,您可以在恢复过程结束时进行排序。

或者,您可以在manifest中包含每个SST的开始密钥和结束密钥。在RocksDB/BadgerDB中使用了这种策略,在apply_compaction_result过程中不需要区分恢复模式和正常模式。

您可以使用mini-lsm-cli来测试您的实现。

cargo run --bin mini-lsm-cli
fill 1000 2000
close
cargo run --bin mini-lsm-cli
get 1500

要运行起mini-lsm-cli还需要执行path参数:cargo run --bin mini-lsm-cli -- --path /tmp/lsm。会将生成的sst保存在该目录下。

从Manifests文件读取记录

使用以下代码可以从文件中反序列化出记录:

pub fn recover(path: impl AsRef<Path>) -> Result<(Self, Vec<ManifestRecord>)> {let mut file = OpenOptions::new().read(true).append(true).open(path).context("failed to recover manifest")?;let mut buf = Vec::new();file.read_to_end(&mut buf)?;let mut stream = Deserializer::from_slice(&buf).into_iter::<ManifestRecord>();let mut records = Vec::new();while let Some(x) = stream.next() {records.push(x?);}Ok((Self {file: Arc::new(Mutex::new(file)),},records,))
}

修改LsmStorageInner::open函数,当Manifests文件文件存在时,走恢复流程

if !manifest_path.exists() {manifest = Some(Manifest::create(manifest_path)?);
} else {// 读取持久化的记录let (m, records) = Manifest::recover(&manifest_path)?;manifest = Some(m);// 遍历记录,回放流程for record in records {match record {ManifestRecord::Flush(sst_id) => {if compaction_controller.flush_to_l0() {state.l0_sstables.insert(0, sst_id);} else {state.levels.insert(0, (sst_id, vec![sst_id]));}next_sst_id = next_sst_id.max(sst_id);}ManifestRecord::NewMemtable(_) => {}ManifestRecord::Compaction(task, output) => {let (new_state, _) =compaction_controller.apply_compaction_result(&state, &task, &output);state = new_state;next_sst_id =next_sst_id.max(output.iter().max().copied().unwrap_or_default());}}}// 读取state中需要读取的SSTfor table_id in state.l0_sstables.iter().chain(state.levels.iter().map(|(_, files)| files).flatten()){let table_id = *table_id;let sst = SsTable::open(table_id,Some(block_cache.clone()),FileObject::open(&Self::path_of_sst_static(path, table_id)).context("failed to open SST")?,)?;state.sstables.insert(table_id, Arc::new(sst));}next_sst_id += 1;state.memtable = Arc::new(MemTable::create(next_sst_id));next_sst_id += 1;
}

可以在指导运行的目录,直接使用cat命令查看Manifests文件,查看写入的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/475208.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ESP8266 STA模式TCP服务器 电脑手机网络调试助手

STA模式TCP服务器和手机电脑网络调试助手多连接

JMeter监听器与压测监控之Grafana

Grafana 是一个开源的度量分析和可视化套件&#xff0c;通常用于监控和观察系统和应用的性能。本文将指导你如何在 Kali Linux 上使用 Docker 来部署 Grafana 性能监控平台。 前提条件 Kali Linux&#xff1a;确保你已经安装了 Kali Linux。Docker&#xff1a;确保你的系统已…

基于AIRTEST和Jmeter、Postman的自动化测试框架

基于目前项目和团队技术升级&#xff0c;采用了UI自动化和接口自动化联动数据&#xff0c;进行相关测试活动&#xff0c;获得更好的测试质量和测试结果。

STM32设计防丢防摔智能行李箱-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展&#xff0c;嵌入式系统、物联网技术、智能设备…

全面解析:HTML页面的加载全过程(一)--输入URL地址,与服务器建立连接

用户输入URL地址&#xff0c;与服务器建立连接 用户在浏览器地址栏输入一个URL 浏览器开始执行以下三步操作操作&#xff1a;url解析、DNS查询、TCP连接 第一步&#xff1a;URL解析 什么是URL&#xff1f; URL(Uniform Resource Locator&#xff0c;统一资源定位符)是互联网…

window 中安装 php 环境

window 中安装 php 环境 一、准备二、下载三、安装四、测试 一、准备 安装前需要安装 Apache &#xff0c;可以查看这篇博客。 二、下载 先到这里下载 这里选择版本为“VS16 x64 Thread Safe”&#xff0c;这个版本不要选择线程安全的&#xff0c;我试过&#xff0c;会缺少文…

Android Studio启动模拟器显示超时

问题报错: Timed out after 300seconds waiting for emulator to come online. 解决方案&#xff1a;升级Android Emulator 情况二&#xff1a;Error while waiting for device:AVD Pixel_4a_API_32 is already running. If that is not the case, delete the files at E:\An…

基于企业微信客户端设计一个文件下载与预览系统

在企业内部沟通与协作中&#xff0c;文件分享和管理是不可或缺的一部分。企业微信&#xff08;WeCom&#xff09;作为一款广泛应用于企业的沟通工具&#xff0c;提供了丰富的API接口和功能&#xff0c;帮助企业进行高效的团队协作。然而&#xff0c;随着文件交换和协作的日益增…

【算法一周目】滑动窗口(1)

目录 长度最小的子数组 解题思路 代码实现 无重复字符的最大字串 解题思路 代码实现 最大连续1的个数l l l 解题思路 代码实现 将x减到0的最小操作数 解题思路 代码实现 长度最小的子数组 题目链接&#xff1a;209. 长度最小的子数组题目描述&#xff1a; 给定一个…

硬件工程师零基础入门:一.电子设计安全要点与欧姆定律

硬件工程师零基础入门:一.电子设计安全要点与欧姆定律 第一节 电子设计安全要点第二节 欧姆定律 第一节 电子设计安全要点 电路小白最好先买直流稳压电源&#xff08;将高压转成低压直流电&#xff09;使用&#xff0c;尽量不要使用市电。 1.尽量不要捏住电源两端。 正确做法&a…

【C++】踏上C++学习之旅(九):深入“类和对象“世界,掌握编程的黄金法则(四)(包含四大默认成员函数的练习以及const对象)

文章目录 前言1. 实现Date类的构造函数2. 实现Date类的拷贝构造函数3. 实现Date类的赋值运算符重载4. 实现各Date对象之间的比较接口5. 实现Date对象的加减接口6. const成员7. 取地址及const取地址操作符重载 前言 在我们前面学习到了"类和对象"的四大默认成员函数(…

【含文档】基于django+Vue的荣誉证书管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 主要技术: django,mysql,vue 2.视频演示地址 3.功能 系统定义了三个角色&#xff1a;管理员和学生和教师。 管理员进…

学习QT第二天

QT6示例运行 运行一个Widgets程序运行一个QT Quick示例 工作太忙了&#xff0c;难得抽空学点东西。-_-||| 博客中有错误的地方&#xff0c;请各位道友及时指正&#xff0c;感谢&#xff01; 运行一个Widgets程序 在QT Creator的欢迎界面中&#xff0c;点击左侧的示例&#xf…

Flutter:AnimatedContainer实现导航侧边栏

导航侧边栏 import package:flutter/material.dart;void main() {runApp(const MyApp()); }class MyApp extends StatelessWidget {const MyApp({Key? key}):super(key: key);overrideWidget build(BuildContext context) {return const MaterialApp(title: Flutter Demo,home…

CI配置项,IT服务的关键要素

随着现今数字经济的不断发展&#xff0c;逐渐成熟的IT 基础设施已不再是简单的竞争优势&#xff0c;而已成为企业生存和发展的基石。然而&#xff0c;仅仅拥有强大的基础设施是不够的。为了保障 IT 服务的平稳运行和持续交付&#xff0c;企业还需要重点关注 IT 服务的核心构建模…

Spring Boot教程之四:在IntelliJ IDEA 以及 Eclips IDE中创建和配置Spring Boot

在本文中&#xff0c;我们将讨论如何使用IntelliJ IDEA 以及 Eclips IDE创建和设置 Spring Boot 项目。 Spring Boot建立在Spring 框架之上&#xff0c;包含 Spring 的所有功能。它现在越来越受到开发人员的青睐&#xff0c;因为它可以在极短的时间内快速构建生产环境&#xf…

我用豆包MarsCode IDE 做了一个 CSS 权重小组件

作者&#xff1a;夕水 查看效果 作为一个前端开发者&#xff0c;应该基本都会用VSCode来做开发&#xff0c;所以也应该见过如下这张图的效果: 以上悬浮面板分为2个部分展示内容。 <element class"hljs-attr">: 代表元素只有一个类名叫hljs-attr的类选择器&am…

短视频矩阵矩阵,矩阵号策略

随着数字媒体的迅猛发展&#xff0c;短视频平台已经成为企业和个人品牌推广的核心渠道。在这一背景下&#xff0c;短视频矩阵营销策略应运而生&#xff0c;它通过高效整合和管理多个短视频账号&#xff0c;实现资源的最优配置和营销效果的最大化。本文旨在深入探讨短视频矩阵的…

实验室管理流程优化:Spring Boot技术实践

3系统分析 3.1可行性分析 通过对本实验室管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本实验室管理系统采用SSM框架&#xff0c;JAVA作为开发语言&a…

如何用Excel批量提取文件夹内所有文件名?两种简单方法推荐

在日常办公中&#xff0c;我们有时需要将文件夹中的所有文件名整理在Excel表格中&#xff0c;方便管理和查阅。手动复制文件名既费时又易出错&#xff0c;因此本文将介绍两种利用Excel自动提取文件夹中所有文件名的方法&#xff0c;帮助你快速整理文件信息。 方法一&#xff1…