mini-lsm通关笔记Week2Day3

news/2024/11/20 11:36:39/文章来源:https://www.cnblogs.com/cnyuyang/p/18432030

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

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

Summary

在本章中,您将:

  • 实现tiered合并策略并在压缩模拟器上对其进行模拟。
  • tiered合并策略纳入系统。

我们在本章所讲的tiered合并和RocksDB的universal合并是一样的。我们将互换使用这两个术语。

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

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

Task 1-Universal Compaction

在本章中,您将实现RocksDB的universal合并,它是tiered合并家族的合并策略。与simple leveled合并策略类似,在此合并策略中,我们只使用文件数量作为指标。当我们触发合并任务时,我们总是在合并任务中包含一个完整的排序run(层)。

Task 1.0-Precondition

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

src/compact/tiered.rs

universal合并中,我们不使用LSM状态下的L0 SST。相反,我们直接将新的SST转储到单个排序run(称为层)。在LSM状态中,levels现在将包括所有层,其中最小的索引是最新转储的SSTlevels数组中的每个元素存储一个元组:级别ID(用作层ID)和该级别中的SST。每次转储L0 SST时,都应该将SST转储到放置在向量前面的层中。compaction模拟器根据第一个SST id生成层id,您应该在您的实现中执行相同的操作。

只有在层数(排序run数)大于num_tiers时,universal合并才会触发任务。否则,它不会触发任何合并。

该子任务就是在generate_compaction_task函数中先判断_snapshot.levels数组的大小是否大于num_tiers。若不大于则无需进行任务合并操作。

pub fn generate_compaction_task(&self,_snapshot: &LsmStorageState,
) -> Option<TieredCompactionTask> {if _snapshot.levels.len() < self.options.num_tiers {return None}None
}

Task 1.1-Triggered by Space Amplification Ratio

universal合并的第一个触发因素是空间放大比。正如我们在概述章节中所讨论的,空间放大可以通过engine_size/last_level_size来估计。在我们的实现中,我们通过除最后一级之外的所有级别大小之和/最后一级大小来计算空间放大比,从而可以将比值缩放为[0,+inf]而不是[1, +inf]。这也与RocksDB的实现是一致的。

除最后一级之外的所有级别大小之和/最后一级大小 >= max_size_amplification_percent * 100%时,我们将需要触发一个完整的合并。

实现了这个触发器之后,就可以运行合并模拟器了。您将看到:

cargo run --bin compaction-simulator tiered
--- After Flush ---
L3 (1): [3]
L2 (1): [2]
L1 (1): [1]
--- Compaction Task ---
compaction triggered by space amplification ratio: 200
L3 [3] L2 [2] L1 [1] -> [4, 5, 6]
--- After Compaction ---
L4 (3): [3, 2, 1]

有了这个触发器,我们只有在达到空间放大比的时候才会触发完全压缩。在模拟结束时,您将看到:

--- After Flush ---
L73 (1): [73]
L72 (1): [72]
L71 (1): [71]
L70 (1): [70]
L69 (1): [69]
L68 (1): [68]
L67 (1): [67]
L40 (27): [39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 13, 14, 15, 16, 17, 18, 19, 20, 21]

合并模拟器中的num_tiers设置为3。然而,LSM状态中的层远远不止3层,这导致了很大的读放大。

当前触发合并机制只能减少空间放大。我们需要在合并算法中添加新的触发器,以减少读取放大。

generate_compaction_task

  • 统计除最后一级之外的所有级别大小之和,因为每个SST的大小都是固定的,所以可以近似的使用数量代替大小计算
  • 计算除最后一级之外的所有级别大小之和/最后一级大小,若大于阈值,则创建新的合并任务
pub fn generate_compaction_task(&self,_snapshot: &LsmStorageState,
) -> Option<TieredCompactionTask> {// Task 1.0-Preconditionif _snapshot.levels.len() < self.options.num_tiers {return None;}// Task 1.1-Triggered by Space Amplification Ratiolet levels_num = _snapshot.levels.len();let last_level_size = _snapshot.levels[levels_num - 1].1.len();let mut engine_size = 0;for (index, level) in _snapshot.levels.iter().enumerate() {if index == levels_num - 1 {break;}engine_size += level.1.len();}println!("levels_num:{}, last_level_size:{}, engine_size:{}", levels_num, last_level_size, engine_size);if engine_size as f64 / last_level_size as f64 >= self.options.max_size_amplification_percent as f64 / 100.0f64 {return Some(TieredCompactionTask { tiers: _snapshot.levels.clone(), bottom_tier_included: true });}None
}

apply_compaction_result

  • 将此前的都清空删除,新合并的SST放置到最底层
pub fn apply_compaction_result(&self,_snapshot: &LsmStorageState,_task: &TieredCompactionTask,_output: &[usize],
) -> (LsmStorageState, Vec<usize>) {let mut snapshot = _snapshot.clone();let mut files_to_remove = Vec::new();let tiered_id = _output.first().unwrap();if _task.bottom_tier_included {snapshot.levels.clear();}for levels in _task.tiers.iter() {files_to_remove.extend(levels.clone().1)}snapshot.levels.insert(0, (*tiered_id, _output.to_vec()));(snapshot, files_to_remove)
}

如图展示前两次的情况,第一次触发合并:

第二次触发合并:

可以看到后续触发合并的条件越来越苛刻,以至于层数过多,这将导致读放大。因为每一层都需要进行一次IO。

通过运行合并模拟器可以看到最后转储50个SST后的结果,可以看到存在24层:

=== Iteration 49 ===
--- After Flush ---
L89 (1): [89]
L88 (1): [88]
L87 (1): [87]
L86 (1): [86]
L85 (1): [85]
L84 (1): [84]
L83 (1): [83]
L82 (1): [82]
L81 (1): [81]
L80 (1): [80]
L79 (1): [79]
L78 (1): [78]
L77 (1): [77]
L76 (1): [76]
L75 (1): [75]
L74 (1): [74]
L73 (1): [73]
L72 (1): [72]
L71 (1): [71]
L70 (1): [70]
L69 (1): [69]
L68 (1): [68]
L67 (1): [67]
L40 (27): [39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 13, 14, 15, 16, 17, 18, 19, 20, 21]
--- Compaction Task ---
--- Compaction Task ---
levels_num:24, last_level_size:27, engine_size:23
no compaction triggered
--- Statistics ---
Write Amplification: 89/50=1.780x
Maximum Space Usage: 54/50=1.080x
Read Amplification: 24x

Task 1.2-Triggered by Size Ratio

下一个触发器是大小比触发器。对于所有层,如果有一个层n的所有之前层的大小/此层>= (100 + size_ratio) * 100%,我们将合并所有n层。我们只在要合并的层超过min_merge_width的情况下执行此合并。

使用此触发器,您将在合并模拟器中观察到以下内容:

L207 (1): [207]
L204 (3): [203, 202, 201]
L186 (15): [185, 178, 179, 180, 181, 182, 183, 184, 158, 159, 160, 161, 162, 163, 164]
L114 (31): [113, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56]

将有更少的1-SST层,并且合并算法将保持这些层按大小比从较小到较大的大小。但是,当处于LSM状态的SST更多时,仍然会有超过num_tiers层的情况。为了限制层数,我们需要另一个触发器。

generate_compaction_task函数新增触发条件

// Task 1.2-Triggered by Size Ratio
let mut previous_tiers_size = 0;
for (index, level) in _snapshot.levels.iter().enumerate() {if (index + 1) < self.options.min_merge_width {previous_tiers_size += level.1.len();continue;}let this_tier = level.1.len();if previous_tiers_size as f64 / this_tier as f64 >= (self.options.size_ratio as f64 + 100.0f64) / 100.0f64 {println!("previous_tiers_size:{}, this_tier:{}, n:{}", previous_tiers_size, this_tier, index + 1);return Some(TieredCompactionTask {tiers: _snapshot.levels.iter().take(index + 1).cloned().collect::<Vec<_>>(),bottom_tier_included: index == levels_num - 1,});}previous_tiers_size += level.1.len();
}

实现第二个触发器后,除了往最底层合并外,也能触发前n层进行合并:

Task 1.3:-Reduce Sorted Runs

如果前面的触发器都没有产生合并任务,那么我们将进行一次合并以减少层数。我们将简单地将前面几层压缩为一层,以便最终状态将具有正好num_tiers层(如果在压缩期间没有刷新SST)。

启用此压缩后,您将看到:

L427 (1): [427]
L409 (18): [408, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407]
L208 (31): [207, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72]

所有的合并结果都不会超过num_tiers层。

注意:这部分我们不提供细粒度的单元测试。您可以运行合并模拟器,并与参考解决方案的
输出进行比较,以查看您的实现是否正确。

generate_compaction_task函数新增触发条件,就是在最后返回的时候,判断当前层级个数是否大于num_tiers。如果大于就将前面几个层级进行合并。

if _snapshot.levels.len() == self.options.num_tiers {return None;
}
// Task 1.3:-Reduce Sorted Runs
let nums = _snapshot.levels.len() - self.options.num_tiers + 1;
println!("Reduce Sorted Runs, levels size: {}, num_tiers:{}, nums:{}", _snapshot.levels.len(), self.options.num_tiers, nums);
return Some(TieredCompactionTask {tiers: _snapshot.levels.iter().take(nums).cloned().collect::<Vec<_>>(),bottom_tier_included: false,
});

Task2-Integrate with the Read Path

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

src/compact.rs
src/lsm_storage.rs

由于tiered合并不使用LSM状态的L0级别,所以应该直接将memtable转储到新的层,而不是作为L0 SST。可以通过self.compaction_controller.flush_to_l0()来知道是否要刷新到L0。您可以使用第一个输出SST id作为新排序运行的级别/层ID。您还需要修改您的合并过程,以为tiered合并作业构造合并迭代器。

lsm_storage.rsforce_flush_next_imm_memtable函数中的force_flush_next_imm_memtable修改为:

if self.compaction_controller.flush_to_l0() {// In leveled compaction or no compaction, simply flush to L0snapshot.l0_sstables.insert(0, sst.sst_id());
} else {// In tiered compaction, create a new tiersnapshot.levels.insert(0, (sst.sst_id(), vec![sst.sst_id()]));
}

compact.rs修改compact函数,实现CompactionTask::Tiered分支,因为tired合并中只有排好序的run所以实现比较简单:

CompactionTask::Tiered(TieredCompactionTask { tiers, .. }) => {let mut iters = Vec::with_capacity(tiers.len());for (_, tier_sst_ids) in tiers {let mut ssts = Vec::with_capacity(tier_sst_ids.len());for id in tier_sst_ids.iter() {ssts.push(snapshot.sstables.get(id).unwrap().clone());}iters.push(Box::new(SstConcatIterator::create_and_seek_to_first(ssts)?));}self.compact_generate_sst_from_iter(MergeIterator::create(iters),_task.compact_to_bottom_level(),)
},

相关阅读

Universal Compaction - RocksDB Wiki

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

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

相关文章

mini-lsm通关笔记Week2Day2

项目地址:https://github.com/skyzh/mini-lsm 个人实现地址:https://gitee.com/cnyuyang/mini-lsmSummary在本章中,您将: 要将测试用例复制到启动器代码中并运行它们,实现一个simple leveled合并策略,并在合并模拟器上进行仿真。 将compaction作为后台任务启动,并在系统…

指针2)

1.& &是地址符,类型是其后面的类型加一个“*”,任何变量都可以使用&来获取地址,但不能用在常量上。 char a = 10; short b = 20; int c = 30; char*=pa pa=&alea取地址:ebp-4的地址放入eax,再将eax里的地址放入ebp-10里 &可以取任何一个变量…

nssctf(web

web 1.浏览器也能套娃 查看wp,是ssrf漏洞 介绍:SSRF漏洞产生的原因是服务端提供了能够从其他服务器应用获取数据的功能,比如从指定的URL地址获取网页内容,加载指定地址的图片、数据、下载等等。 SSRF的利用:进行内网资源的访问:url?url=http://内网的资源url利用伪协议:…

RTE 大会报名丨AI 时代新基建:云边端架构和 AI Infra ,RTE2024 技术专场第二弹!

所有 AI Infra 都在探寻规格和性能的最佳平衡,如何构建高可用的云边端协同架构?语音 AI 实现 human-like 的最后一步是什么?AI 视频的爆炸增长,给新一代编解码技术提出了什么新挑战?当大模型进化到实时多模态,又将诞生什么样的新场景和玩法?AI 加持下,空间计算和新硬件…

002-QOS基本原理

QOS基本原理QOS概述什么是QOS QoS服务模型 区分服务模型 QoS常用技术 (DiffServ模型) QoS数据处理流程 (DiffServ模型)QoS流分类和流标记QoS数据处理流程 为什么需要流分类和流标记简单流分类外部优先级 - VLAN报文 外部优先级 - MPLS报文 外部优先级 - IP报文 各外部优先级间的…

本地部署运行 Google Gemma 开源大模型

Google 开源了 Gemma 大模型,有 7B 和 2B 两个版本,7B 模型的能力已经是开源模型中的领先水平。Gemma 可以轻松的在本地部署运行,如果你的显存在 8G 以上,可以体验 7B 版本,8G 以下的话可以试试 2B 版本。 部署过程如下: 1、使用 ollama 运行 Gemma 模型 2、使用 Chatbox…

mini-lsm通关笔记Week2Overview

Week 2 Overview: Compaction and Persistence在上周,您已经实现了LSM存储引擎的所有必要结构,并且您的存储引擎已经支持读写接口。在本周中,我们将深入探讨SST文件的磁盘组织,并研究在系统中实现性能和成本效益的最佳方法。我们将花4天时间学习不同的compaction策略,从最…

001-什么是VOQ

1、什么是VOQ(Virtual Output Queues)? VOQ(虚拟输出序列)是一种存储结构,由FIFO与RAM以及逻辑结构组合构成。在一些数据应用场景中能够有效存储数据并且能够及时输出,避免阻塞。一句话来说VOQ的优点在于:共享存储,较少存储资源,避免数据阻塞,提高数据输出效率。 2、…

pl/sql小技巧

pl/sql中文乱码 select userenv(language) from dual cmd命令行 set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK pl/sql拖到cmd窗口下执行 pl/sql 显示行号pl、sql字体大小调整

结对项目-四则运算

github链接这个作业属于哪个课程 班级的链接这个作业要求在哪里 作业要求的链接这个作业的目标 实现四则运算自动生成程序,结对协作开发姓名 学号柳浩 3122004444洪吉潮PSP表格PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)Planning 计划 20 25Esti…

基于Sentinel自研组件的系统限流、降级、负载保护最佳实践探索

一、Sentinel简介 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Sentinel 具有以下特征: •丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、…