mini-lsm通关笔记Week1Day2

news/2024/9/24 18:04:33

在今天的任务中主要是实现下面一层一层的迭代器:

Task 1: Memtable Iterator

在本章中,我们将实现LSM scan接口,scan使用迭代器API按顺序返回一系列键值对。在上一章中,您已经实现了get API和创建不可变memtable的逻辑,您的LSM state现在应该有多个memtable。您需要首先在单个memtable上创建迭代器,然后在所有memtable上创建合并迭代器,最后实现迭代器的范围限制。

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

src/mem_table.rs

所有的LSM迭代器都实现了StorageIterator特性。它有4个函数:keyvaluenextis_valid。当迭代器被创建时,它的光标将停止在某个元素上,而key/value将返回memtable/block/SST中满足开始条件的第一个键(即开始键)。这两个接口会返回一个&[u8],以避免复制。请注意,这个迭代器接口不同于Rust风格的迭代器。

next将光标移动到下一个位置。如果迭代器已经到达末尾或出错,则返回is_valid。你可以假设只有当is_valid返回true时才会调用next。Task3中将有一个用于迭代器的FusedIterator包装器,当迭代器无效时,它会阻止对next的调用,以避免用户误用迭代器。

回到memtable迭代器。你应该已经发现迭代器没有任何与之相关的生命周期。假设你创建了一个Vec<u64>并调用vec.iter(),迭代器类型将是类似于VecIterator<'a>的东西,其中'avec对象的生命周期。SkipMap也是如此,它的iter API返回一个具有生命周期的迭代器。然而,在我们的情况下,我们不希望在迭代器上有这样的生命周期,以避免使系统过于复杂(并且难以编译...)。

如果迭代器没有生命周期泛型参数,我们应该确保每当使用迭代器时,底层的skiplist对象都不会被释放。实现这一点的唯一方法是将Arc<SkipMap>对象放入迭代器本身。要定义这样的结构,

pub struct MemtableIterator {map: Arc<SkipMap<Bytes, Bytes>>,iter: SkipMapRangeIter<'???>,
}

好了,问题来了:我们想表达迭代器的生命周期和结构体中的map是一样的。我们怎么能做到呢?

这是你在本教程中遇到的第一个也是最棘手的Rust语言的东西——自引用结构。如果可以这样写:

pub struct MemtableIterator { // <- with lifetime 'thismap: Arc<SkipMap<Bytes, Bytes>>,iter: SkipMapRangeIter<'this>,
}

那么问题就解决了!你可以在一些第三方库的帮助下实现这一点,比如ouroboros。它提供了一种简单的方法来定义自引用结构。使用不安全的Rust也可以做到这一点(事实上,我们自己内部使用不安全的Rust...)

我们已经为您定义了自引用MemtableIterator字段,您需要实现MemtableIteratorMemtable::scan API。

进行本章的内容,需要先对ouroboros三方工具库有一定了解:https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html

对于以下定义的自引用结构体:

#[self_referencing]
struct MyStruct {tail_field: i32,int_data: i32,float_data: f32,#[borrows(int_data)]int_reference: &'this i32,#[borrows(mut float_data)]float_reference: &'this mut f32,
}

其中int_data有一个不可变的借用int_referencefloat_data有一个可变的借用float_referencetail_field是没有被借用的字段。

如果需要获取字段的值可以使用borrow_{field_name}()方法:

my_value.borrow_tail_field()my_value.borrow_int_reference()my_value.borrow_int_data()my_value.borrow_float_reference()

如果需要获取到字段的可变引用然后修改值,需要使用with_mut或者with_{field_name}_mut

my_value.with_mut(|fields| {**fields.float_reference = (**fields.int_reference as f32) * 2.0;*fields.tail_field = 12;
});my_value.with_float_reference_mut(|float_ref| {**float_ref = 32.0
});

MemtableIterator

value方法:

结构体的属性不能直接访问,需要通过self.borrow_{field_name}访问。先通过self.borrow_item()获取到item这个元组,再通过self.borrow_item().1获取到第二个元素,因为返回的是字符串切片所以变成self.borrow_item().1[..],为了不产生拷贝最终变成:

&self.borrow_item().1[..]

借助ouroboros::self_referencing方法,能简单的实现返回引用。

is_valid方法:

判断元组第一个元素是否为空:

!self.borrow_item().0.is_empty()

key方法:

KeySlice::from_slice(&self.borrow_item().0[..])

next方法:

可以使用with_iter_mut方法获取到iter的可变引用,通过with_item_mut可以获取到item的可变引用。

let entry = self.with_iter_mut(|iter| {let entry = iter.next();match entry {None => { (Bytes::new(), Bytes::new()) }Some(entry) => {(entry.key().clone(), entry.value().clone())}}
});
self.with_item_mut(|item|{*item = entry;
});
Ok(())

scan

通过ouroboros::self_referencing的构造函数生成对象,注意iter字段的复制需要使用闭包的方式赋值给iter_builder

let (lower, upper) = (map_bound(_lower), map_bound(_upper));
let mut iterator = MemTableIteratorBuilder {map: self.map.clone(),iter_builder: |map| map.range((lower, upper)),item: (Bytes::new(), Bytes::new()),
}
.build();
iterator.next().unwrap();
iterator

Task 2-Merge Iterator

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

src/iterors/merge_iterator.rs

现在你有了多个memtable,你将创建拥有多个memtable的迭代器。您需要合并memtables中的结果,并将每个键的最新版本返回给用户。

MergeIterator在内部维护了一个二叉堆(BinaryHeap)。请注意,您需要处理错误(即当迭代器无效时),并确保键值对的最新版本。

例如,如果我们有以下数据:

iter1: b->del, c->4, d->5

iter2: a->1, b->2, c->3

iter3: e->4

合并迭代器输出的顺序应该是:

a->1、b->del、c->4、d->5、e->4

合并迭代器的构造函数接受迭代器的数组Vec。我们假设索引较小的那个(即第一个)拥有最新的数据。

一个常见的陷阱是错误处理。例如:

let Some(mut inner_iter) = self.iters.peek_mut() {inner_iter.next()?; // <- will cause problem
}

如果next返回错误(即,由于磁盘故障、网络故障、校验和错误等),则不再有效。但是,当我们走出if条件并将错误返回给调用者时,PeekMut的drop将尝试在堆中移动元素,这导致访问无效的迭代器。因此,您需要自己完成所有错误处理,而不是使用?在PeekMut的范围内。

我们希望尽量避免动态分派,因此我们在系统中不使用Box<dyn StorageIterator>。相反,我们更倾向于使用泛型进行静态分派。另请注意,StorageIterator使用泛型关联类型(GAT),因此它可以同时支持KeySlice&[u8]作为键类型。我们将更改KeySlice以在第3周中包含时间戳,现在为它使用单独的类型可以使过渡更加平滑。

从本节开始,我们将使用Key<T>来表示LSM键类型,并将它们与类型系统中的值区分开来。你应该使用Key<T>提供的API,而不是直接访问内部值。在第3部分中,我们将为这个键类型添加时间戳,使用键抽象将使转换更加平滑。目前,KeySlice等价于&[u8], KeyVec等价于Vec<u8>,KeyBytes等价于Bytes

二叉堆(BinaryHeap)其实就是一个优先队列,先查看其比较规则:

match self.1.key().cmp(&other.1.key()) {cmp::Ordering::Greater => Some(cmp::Ordering::Greater),cmp::Ordering::Less => Some(cmp::Ordering::Less),cmp::Ordering::Equal => self.0.partial_cmp(&other.0),
}

先比较其StorageIterator当前元素的key值,如果相同再比较Vec索引下标的值。再验证一下,修改BinaryHeap中的元素,BinaryHeap是否会重新排列:

use std::collections::BinaryHeap;#[derive(Debug, Eq, PartialOrd, PartialEq)]
#[derive(Ord)]
struct MyStruct {x: i32,y: String,
}fn main() {let mut heap = BinaryHeap::new();let xiaoming = MyStruct {x: 16,y: String::from("xiaoming"),};let xiaohong = MyStruct {x: 17,y: String::from("xiaohong"),};heap.push(xiaoming);heap.push(xiaohong);println!("{:?}", heap.peek());let xiaohong = heap.peek_mut();xiaohong.unwrap().x = 1;println!("{:?}", heap.peek());
}

输出:

Some(MyStruct { x: 17, y: "xiaohong" })
Some(MyStruct { x: 16, y: "xiaoming" })

可知BinaryHeap的堆顶元素永远是key值最小且最新的那个。

create

构造函数实现:

let mut iter = MergeIterator {iters: BinaryHeap::new(),current: None,
};
if iters.iter().all(|x| !x.is_valid()) && !iters.is_empty() {let mut iters = iters;iter.current = Some(HeapWrapper(0, iters.pop().unwrap()));return iter;
}
for (index, storage_iter) in iters.into_iter().enumerate() {if storage_iter.is_valid() {iter.iters.push(HeapWrapper(index, storage_iter));}
}
if !iter.iters.is_empty() {iter.current = Some(iter.iters.pop().unwrap())
}
iter
  • 创建一个默认对象

  • 判断Vec数组里面的元素是否都为非法值,如果都是非法值则将current随便赋值一个。因为在使用MergeIterator也会先调用is_valid方法

  • Vec数组里面有合法元素的迭代器都加到堆中

  • 取堆顶的元素赋值给current

key/value

current元素中迭代器的key/value方法:self.current.as_ref().unwrap().1.key()self.current.as_ref().unwrap().1.value()

is_valid

先判断是否为None,再调用current中迭代器的is_valid方法:

if let None = self.current {return false;
}
self.current.as_ref().unwrap().1.is_valid()

next

let current = self.current.as_mut().unwrap();
while let Some(mut inner_iter) = self.iters.peek_mut() {if inner_iter.1.key() != current.1.key() {break;}if let e @ Err(_) = inner_iter.1.next() {PeekMut::pop(inner_iter);return e;}if !inner_iter.1.is_valid() {PeekMut::pop(inner_iter);}
}current.1.next()?;if !current.1.is_valid() {if let Some(iter) = self.iters.pop() {*current = iter;}return Ok(());
}if let Some(mut inner_iter) = self.iters.peek_mut() {if *current < *inner_iter {std::mem::swap(&mut *inner_iter, current);}
}Ok(())
  • 先把和当前current迭代器key相同的迭代器移除。

  • 当前current迭代器取下一个元素

  • 当前current迭代器非法,从堆顶pop出一个迭代器

  • 当前current迭代器合法,和堆顶迭代器比较,若小于则交换

Task 3-LSM Iterator + Fused Iterator

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

src/lsm_iterator.rs

我们使用LsmIterator结构来表示内部的LSM迭代器。当系统中添加了更多的迭代器时,您将需要在整个教程中多次修改此结构。目前,因为我们只有多个memtable,所以它应该定义为:

类型LsmIteratorInner=MergeIterator<MemTableIterator>

您可以继续实现LsmIterator结构,它调用相应的内部迭代器,并且也跳过删除的键。

我们不在此任务中测试LsmIterator。在任务4中,将有一个集成测试。

然后,我们希望在迭代器上提供额外的安全性,以避免用户误用它们。当迭代器无效时,不应调用key、value或next。同时,如果next返回错误,则不应该再使用迭代器。FusedIterator是一个围绕迭代器的包装器,用于规范化所有迭代器的行为。你可以自己去实现它。

LsmIterator

主要还是直接调用inner,就是在空value的时候需要跳到下一个键值对:

impl LsmIterator {pub(crate) fn new(iter: LsmIteratorInner) -> Result<Self> {let mut lsm = Self { inner: iter };if lsm.is_valid() && lsm.value().is_empty() {lsm.next();}Ok(lsm)}
}impl StorageIterator for LsmIterator {type KeyType<'a> = &'a [u8];fn is_valid(&self) -> bool {self.inner.is_valid()}fn key(&self) -> &[u8] {self.inner.key().raw_ref()}fn value(&self) -> &[u8] {self.inner.value()}fn next(&mut self) -> Result<()> {self.inner.next();if self.inner.is_valid() && self.inner.value().is_empty() {return self.next();}Ok(())}
}

FusedIterator

判断是否有异常、是否合法,否则报错

impl<I: StorageIterator> StorageIterator for FusedIterator<I> {type KeyType<'a> = I::KeyType<'a> where Self: 'a;fn is_valid(&self) -> bool {!self.has_errored && self.iter.is_valid()}fn key(&self) -> Self::KeyType<'_> {if !self.is_valid() {panic!("invalid access to the underlying iterator");}self.iter.key()}fn value(&self) -> &[u8] {if !self.is_valid() {panic!("invalid access to the underlying iterator");}self.iter.value()}fn next(&mut self) -> Result<()> {if self.has_errored {bail!("the iterator is tainted");}if self.iter.is_valid() {if let Err(e) = self.iter.next() {self.has_errored = true;return Err(e);}}Ok(())}
}

Task 4-Read Path - Scan

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

src/lsm_storage.rs

我们终于实现了——有了所有已经实现的迭代器,您终于可以实现LSM引擎的扫描接口了。你可以简单地用memtable迭代器构造一个LSM迭代器(记得把最新的memtable放在merge迭代器的前面),然后你的存储引擎就可以处理扫描请求了。

将此前写的迭代器用于scan接口:

let snapshot = {let guard = self.state.read();Arc::clone(&guard)
};
let mut memtable_iters = Vec::with_capacity(snapshot.imm_memtables.len() + 1);
memtable_iters.push(Box::new(snapshot.memtable.scan(_lower, _upper)));
for memtable in snapshot.imm_memtables.iter() {memtable_iters.push(Box::new(memtable.scan(_lower, _upper)));
}
Ok(FusedIterator::new(LsmIterator::new(MergeIterator::create(memtable_iters),
)?))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

绿色化使用Foxmail

本文介绍了一种备份以及使用Foxmail邮箱数据库的方法,以便在不同电脑之间挪动包含本地文件夹的邮箱数据库。Foxmail有个非常实用的功能叫“本地文件夹”,可以将邮件挪到本地文件夹内从而不占用邮箱宝贵的云端存储空间:但是Foxmail本身是没有绿色版的,这就为这些本地邮件的迁…

Minio实现文件上传、下载、预览、删除

环境:JDK11Minio8服务器搭建Minio:https://www.cnblogs.com/warmNest-llb/p/18233203完成项目 AjaxResult 结果返回使用的 若依。 1. pom.xml<!-- MinIO Client --><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId&…

一个ArcGIS中我知道但是不常用的工具

如题,这个工具好像很有用 Raster To Point

PHP 5.4 编译 configure: error: libXpm.(a|so) not found.

https://blog.csdn.net/niugang123/article/details/84972934

[模式识别复习笔记] 第8章 决策树

1. 决策树 1.1 决策树简介 决策树(Decision Tree)是一种以 树形数据结构 来展示决策规则和分类结果的模型。每一条从根结点(对最终分类结果贡献最大的属性)到叶子结点(最终分类结果)的路径都代表一条决策的规则。1.2 决策树的构建过程首先生成一个 根节点,其 包含所有的…

CM3调试系统简析

包括对两大调试接口:JTAG接口和SWD串行线调试接口、CoreSight调试接口:基于CoreSight架构的CM3调试系统和标准CoreSight架构和CM3中调试系统异同点、CoreSight跟踪接口、 调试功能的总结、调试模式、调试事件、STM32调试单元、SWV调试、JTAG边界扫描 、代码断点类型等知识点的…

KOPRA论文阅读笔记

Joint Knowledge Pruning and Recurrent Graph Convolution for News Recommendation论文阅读笔记 Abstract ​ 最近,利用知识图谱(KG)来丰富新闻文章的语义表征已被证明对新闻推荐有效。这些解决方案的重点是利用知识图谱中的附加信息对新闻文章进行表征学习,用户表征主要…

R语言估计时变VAR模型时间序列的实证研究分析案例|附代码数据

原文链接: http://tecdat.cn/?p=3364 原文出处:拓端数据部落公众号最近我们被客户要求撰写关于时变VAR模型的研究报告,包括一些图形和统计输出。 加载R包和数据集加载包后,我们将此数据集中包含的12个心情变量进行子集化: mood_data <- as.matrix(symptom_data$data[…