Reviewing Sled「1」

638 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

sled 是一个用Rust编写的嵌入式数据库。最近在工作中频繁使用它,本篇文章打算好好了解它的工作原理。它的底层结构是 Log Structure Merge (并且暴露给client),同时具有 B+Tree 的读性能。(顺便还可以复习一些 LSM 的知识,不错不错)

通常来说,在阅读源码的时候,会选取一个较早的版本:

  1. 尽快理解作者开发的初衷
  2. 初代设计更为直接和简洁明了
  3. 可以观察项目的演进

这里选取该 commit 。我们可以看到 sled 的最初组织结构:

image.png

我们需要关注正是这个 crates 下的核心代码。

crates/pagecache → 从文件夹名已经给出sled很多的存储细节:

  • 使用 page 作为数据组织单元 (比如 levedb 就不是)
  • 而且对其在内存中有 cache (所以可能不会使用 mmap)

这个推论或许对后面理解源码会比较重要。

image.png

image.png

上述显示:有N个分片;

fn traverse<'g, T: 'static + Send>(
    head: Shared<'g, Node1<T>>,
    k: PageId,
    guard: &'g Guard,
) -> &'g Atomic<T> {
    let (l1k, l2k) = split_fanout(k);

    debug_delay();
    let l1 = unsafe { &head.deref().children };

    debug_delay();
    let mut l2_ptr = l1[l1k].load(SeqCst, guard);

    if l2_ptr.is_null() {
        let next_child = Owned::new(Node2::default()).into_shared(guard);

        debug_delay();
        let ret = l1[l1k].compare_and_set(l2_ptr, next_child, SeqCst, guard);

        match ret {
            Ok(_) => {
                // CAS worked
                l2_ptr = next_child;
            }
            Err(e) => {
                // another thread beat us, drop unused created
                // child and use what is already set
                l2_ptr = e.current;
            }
        }
    }

    debug_delay();
    let l2 = unsafe { &l2_ptr.deref().children };

    &l2[l2k]
}

基本流程:

  1. 遍历节点tree → 找到key对应的leaf node
  2. 修改这个leaf node,设置新的<k, v>键值对

<l2_ptr> 中:如果在CAS中没有成功,究竟是谁要放弃未使用的创建值?答案在Guard和Epoch身上。我们在调用 into_shared 时将该值与它们相关联,由于我们没有将它从 Guard 中取出,所以当 Epoch 结束时,它将被删除。

traverse()的最终结果是直接返回第二层的值:

debug_delay();
let l2 = unsafe { &l2_ptr.deref().children };

&l2[l2k]

TODO!!!