mini-lsm通关笔记Week1Day2

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

在这里插入图片描述

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 'this
    map: 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),
)?))
  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值