首页 > 其他分享 >mini-lsm通关笔记Week1Day5

mini-lsm通关笔记Week1Day5

时间:2024-08-26 21:49:29浏览次数:7  
标签:mini 迭代 self lsm iter Week1Day5 valid key table

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

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

Task1-Two Merge Iterator

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

src/iterators/two_merge_iterator.rs

你已经在Week1Day2中实现了一个合并迭代器,它合并相同类型的迭代器(如:memtable迭代器)。既然我们已经实现了SST格式,我们就同时拥有了磁盘上的SST结构和内存中的memtable。当我们从存储引擎扫描时,我们需要将memtable迭代器和SST迭代器的数据合并到一个迭代器中。在这种情况下,我们需要一个TwoMergeIterator<X, Y>来合并两个不同类型的迭代器。

可以在two_merge_iter.rs中实现TwoMergeIterator。因为我们这里只有两个迭代器,所以我们不需要维护二叉堆。相反,我们可以简单地使用一个标志来指示要读取哪个迭代器。与MergeIterator类似,如果在两个迭代器中找到相同的键,则第一个迭代器优先。

skip_b

跳过迭代器B中与迭代A重复的数据,已迭代器A为主。

choose_a

选择A或者B迭代器

疑问:

1、为什么只有skip_b,没有skip_a

因为在出现重复的时候,需要以迭代器A为主。

2、为什么先执行skip_b,在执行choose_a

需要先跳过迭代器中重复的数据,避免重复数据输出。

use anyhow::Result;

use super::StorageIterator;

pub struct TwoMergeIterator<A: StorageIterator, B: StorageIterator> {
    a: A,
    b: B,
    choose_a: bool,
}

impl<
        A: 'static + StorageIterator,
        B: 'static + for<'a> StorageIterator<KeyType<'a> = A::KeyType<'a>>,
    > TwoMergeIterator<A, B>
{
    fn choose_a(a: &A, b: &B) -> bool {
        if !a.is_valid() {
            return false;
        }
        if !b.is_valid() {
            return true;
        }
        a.key() < b.key()
    }

    fn skip_b(&mut self) -> Result<()> {
        if self.a.is_valid() && self.b.is_valid() && self.b.key() == self.a.key() {
            self.b.next()?;
        }
        Ok(())
    }

    pub fn create(a: A, b: B) -> Result<Self> {
        let mut iter = Self {
            choose_a: false,
            a,
            b,
        };
        iter.skip_b()?;
        iter.choose_a = Self::choose_a(&iter.a, &iter.b);
        Ok(iter)
    }
}

impl<
        A: 'static + StorageIterator,
        B: 'static + for<'a> StorageIterator<KeyType<'a> = A::KeyType<'a>>,
    > StorageIterator for TwoMergeIterator<A, B>
{
    type KeyType<'a> = A::KeyType<'a>;

    fn key(&self) -> Self::KeyType<'_> {
        if self.choose_a {
            self.a.key()
        } else {
            self.b.key()
        }
    }

    fn value(&self) -> &[u8] {
        if self.choose_a {
            self.a.value()
        } else {
            self.b.value()
        }
    }

    fn is_valid(&self) -> bool {
        if self.choose_a {
            self.a.is_valid()
        } else {
            self.b.is_valid()
        }
    }

    fn next(&mut self) -> Result<()> {
        if self.choose_a {
            self.a.next()?;
        } else {
            self.b.next()?;
        }
        self.skip_b()?;
        self.choose_a = Self::choose_a(&self.a, &self.b);
        Ok(())
    }
}

Task 2: Read Path - Scan

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

src/lsm_iterator.rs
src/lsm_storage.rs

在实现TwoMergeIterator之后,我们可以将LsmIteratorInner更改为具有以下类型:

type LsmIteratorInner =
    TwoMergeIterator<MergeIterator<MemTableIterator>, MergeIterator<SsTableIterator>>;

因此,我们的LSM存储引擎的内部迭代器将是一个结合来自memtable和SST的数据的迭代器。

请注意,我们的SST迭代器不支持传递它的右边界。因此,您需要在LsmIterator中手动处理end_bound。您需要修改LsmIterator逻辑,以便在内部迭代器的键到达结束边界时停止。

我们的测试用例将在l0_sstables中生成一些memtables和SST,您需要在此任务中正确地扫描出所有这些数据。到下一章之前不需要修改SST。因此,您可以继续修改您的LsmStorageInner::scan接口,以在所有memtable和SST上创建一个合并迭代器,从而完成您的存储引擎的读取路径。

因为SsTableIterator::create涉及到I/O操作,可能会很慢,所以我们不想在状态临界部分中这样做。因此,首先应该读取状态并克隆LSM状态快照的Arc。然后,你应该放下锁。之后,您可以遍历所有L0 SST并为每个SST创建迭代器,然后创建一个合并迭代器来检索数据。

fn scan(&self) {
    let snapshot = {
        let guard = self.state.read();
        Arc::clone(&guard)
    };
    // create iterators and seek them
}

在LSM存储状态下,我们只将SST id存储在l0_sstables向量中。您需要从sstables哈希映射中检索实际的SST对象。

range_overlap

判断user_begin到user_end的范围和table_begin到table_end的范围是否有交集。

fn range_overlap(
    user_begin: Bound<&[u8]>,
    user_end: Bound<&[u8]>,
    table_begin: KeySlice,
    table_end: KeySlice,
) -> bool {
    match user_end {
        Bound::Excluded(key) if key <= table_begin.raw_ref() => {
            return false;
        }
        Bound::Included(key) if key < table_begin.raw_ref() => {
            return false;
        }
        _ => {}
    }
    match user_begin {
        Bound::Excluded(key) if key >= table_end.raw_ref() => {
            return false;
        }
        Bound::Included(key) if key > table_end.raw_ref() => {
            return false;
        }
        _ => {}
    }
    true
}
  • Included

    当一个边界是Included时,这意味着该边界值是包含在范围内的。例如,如果你有一个范围1..=5,那么数字5就是被包含在内的。

  • Excluded
    当一个边界是Excluded时,这意味着该边界值是不包含在范围内的。例如,如果你有一个范围1..5,那么数字5是不包含在内的。

  • Unbounded
    当一个边界是Unbounded时,这意味着该端点是没有定义的。这通常用于表示一个开放的边界,例如1....5这样的范围。

scan

  1. 遍历sstables,将与_lower到_upper范围有交集的SsTable创建迭代器SsTableIterator
  2. 使用MergeIterator将多个SsTableIterator合并
  3. 使用TwoMergeIteratormentable的合并迭代器和SsTableIterator的合并迭代器合并
// SST迭代器
let mut sst_iters = Vec::with_capacity(snapshot.l0_sstables.len());
for table_id in snapshot.l0_sstables.iter() {
    let table = snapshot.sstables[table_id].clone();
    if range_overlap(
        _lower,
        _upper,
        table.first_key().as_key_slice(),
        table.last_key().as_key_slice(),
    ) {
        let iter = match _lower {
            Bound::Included(key) => {
                SsTableIterator::create_and_seek_to_key(table, KeySlice::from_slice(key))?
            }
            Bound::Excluded(key) => {
                let mut iter = SsTableIterator::create_and_seek_to_key(
                    table,
                    KeySlice::from_slice(key),
                )?;
                if iter.is_valid() && iter.key().raw_ref() == key {
                    iter.next()?;
                }
                iter
            }
            Bound::Unbounded => SsTableIterator::create_and_seek_to_first(table)?,
        };

        sst_iters.push(Box::new(iter));
    }
}
let l0_iter = MergeIterator::create(sst_iters);

let iter = TwoMergeIterator::create(merge_memtable_iter, l0_iter)?;
Ok(FusedIterator::new(LsmIterator::new(
    iter,
    map_bound(_upper),
)?))

LsmIterator

先修改类型定义:

type LsmIteratorInner =
    TwoMergeIterator<MergeIterator<MemTableIterator>, MergeIterator<SsTableIterator>>;

修改构造函数:

pub(crate) fn new(iter: LsmIteratorInner, upper: Bound<Bytes>) -> Result<Self> {
    let mut lsm = Self { inner: iter, upper };
    if lsm.is_valid() && lsm.value().is_empty() {
        lsm.next();
    }
    Ok(lsm)
}

修改is_valid函数:

fn is_valid(&self) -> bool {
    if !self.inner.is_valid() {
        return false;
    }
    let mut is_valid = true;
    match self.upper.as_ref() {
        Bound::Included(upper) => is_valid = self.inner.key().raw_ref() <= upper.as_ref(),
        Bound::Excluded(upper) => is_valid = self.inner.key().raw_ref() < upper.as_ref(),
        Bound::Unbounded => {}
    }
    is_valid
}

在值有效的情况下,判断右边界是否满足要求。

Task 3: Read Path - Get

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

src/lsm_storage.rs

对于get请求,它将被处理为在memtable中查找,然后在SST上扫描。您可以在探测所有memtable之后创建一个覆盖所有SST的合并迭代器。您可以查找到用户想要查找的键。寻找有两种可能:键与用户探测的键相同,键不相同/不存在。你应该只返回值给用户,当键存在,并且是相同的探测。您还应该像上一节一样减少状态锁的临界区。还要记住处理删除的键。

get函数

在扫描imm_memtables之后,返回之前:

for table in self.state.read().l0_sstables.iter() {
    let table = self.state.read().sstables[table].clone();
    let iter = SsTableIterator::create_and_seek_to_key(table, KeySlice::from_slice(_key))?;
    if iter.is_valid() && iter.key().raw_ref() == _key {
        if iter.value().is_empty() {
            return Ok(None);
        }
        return Ok(Some(Bytes::copy_from_slice(iter.value())));
    }
}

遍历每个SsTable,使用seek_to_key创建迭代器,如果找到一样的key,且value值不是空字符串则返回值。

标签:mini,迭代,self,lsm,iter,Week1Day5,valid,key,table
From: https://www.cnblogs.com/cnyuyang/p/18381643

相关文章

  • 【Kubernetes安装】WSL2+ubuntu22.04+K8s+minikube最新安装教程
    系列综述:......
  • Minimum Steiner Tree 题解
    原题,详见P10723。几乎相同,我们只需要以一个需要选择的点为根,遍历子树看看有没有出现需要选择的点,然后直接去删除即可,可以看我的博客。但是我们也可以换一种做法,用类似拓扑排序的算法。先找到所有只连一条边且没有被选择的点,然后放进队列,接着依次取出队头遍历与它相连的点,同时记......
  • vue3的天气组件vue3-mini-weather为何安装会报错?
    参考于:https://gitee.com/maocaoying_admin/vue3-mini-weather安装上述地址的组件报错:实现的效果图:实现步骤:1将vue3-mini-weather的lib直接拿到自己的项目中来:2将lib中的组件引入到自己项目中使用点击查看代码<template><divclass="section-income"><div......
  • ABC 368D Minimum Steiner Tree
    题意给你一颗由N个点组成的树,指定K个节点,求包含这K个节点的最小子树的大小思路考虑正难则反,我们从开始的树当中剪掉那些没有任何指定点的子树,剩下来的子树就是最小的、能包含所有指定节点的子树。关于剪去这个操作,就是dfs一旦遇到以当前节点为根的子树没有任何指定点时,就停止df......
  • 004.MinIO-DirectPV分布式存储部署
    MinIO部署介绍部署概述Kuberneteshostpath、local和本地静态配置都存在需要事先在node节点准备好可用的块存储或文件系统,例如对插入的硬盘,或者磁盘阵列做分区格式化,文件系统则需提前创建好Kubernetes即将利用的挂载目录,并且两种方法都会有亲和性限制,无法做到让Kubernetes自身的......
  • 读论文《Behavior Pattern Mining-based Multi-Behavior Recommendation》
    论文地址:arxiv.org/pdf/2408.12152v1项目地址:GitHub-rookitkitlee/BPMR基于行为模式挖掘的多行为推荐:论文提出了一种新颖的多行为推荐算法(BPMR),旨在通过分析用户和项目之间的复杂交互模式来提高推荐系统的有效性。这种方法特别关注于用户除了购买之外的其他行为,例如页面浏览......
  • MacBook Air M1 使用 miniconda 安装python3.11.7 和 tensorflow2.16.1详细
    1m1mac安装xcode命令工具在Terminal终端执行以下代码:xcode-select--install2下载支持m1芯片arm64的miniconda在miniconda官网,找到下图中保护AppleM1的bash安装包,Miniconda—Anacondadocumentation3安装miniconda在Terminal执行下列代码:1)cd"miniconda下......
  • minio安装
    一、下载https://min.io/download?license=agpl&platform=docker二、安装直接解压三、启动bin\minio.exeserverdata--console-address"127.0.0.1:9000"--address"127.0.0.1:9005"三、登录控制台控制台地址:http://127.0.0.1:9000四、管理存储桶五、通过批处理修......
  • 001.MinIO简介
    MinIO简介MinIO是天然的云原生存储,可以作为轻量级容器运行,由相关编排服务(如Kubernetes)管理。整个服务器是一个不到100MB的静态二进制文件,并且在使用CPU和内存资源方面即使在高负载场景也非常高效,因此可以在共享硬件上共同托管大量租户。MinIO可以在任何地方和任何云设施......
  • LeetCode 2952. Minimum Number of Coins to be Added
    原题链接在这里:https://leetcode.com/problems/minimum-number-of-coins-to-be-added/description/题目:Youaregivena 0-indexed integerarray coins,representingthevaluesofthecoinsavailable,andaninteger target.Aninteger x is obtainable ifthere......