RocksDB 的 Group Write 机制

RocksDB 是 LSM-tree 结构的 KV 存储,写入的数据先通过 WAL 持久化,再写入到 memtable 中。WAL 的写入需要保证顺序性,只能由单个线程来进行操作。但 memtable 在设计上是一个支持无锁并发的 skiplist,可以通过多线程写入进行加速。为了提升写入性能,RocksDB 引入了 group write 机制。

RocksDB group WAL write workflow

详细流程

流程图引用于SpanDB: A Fast, Cost-Effective LSM-tree Based KV Store on Hybrid Storage,本文将以流程图的大致顺序进行展开讨论。

Join Batch Group

对应流程图中的 1、2、3 步骤。第一个进入到 batch group 的写线程会成为 leader,随后进入到 batch group 的写线程则作为 leader 线程。link as leader 的过程十分简单,成功成为 leader 的线程会成功设置 newest_writer 变量,其余的 follower 线程则会以链表的形式将自己加入到 leader 的 writer 成员中。leader 线程会继续执行后续的 WAL 写入操作,follower 线程将会等待 leader 线程通知。follower 线程的等待采用了自旋+条件变量的方式进行,首先尝试在原子变量上进行一个短时间的自旋,再尝试挂起进程在 condition variable 上进行等待。

bool WriteThread::LinkOne(Writer* w, std::atomic* newest_writer) {
  assert(newest_writer != nullptr);
  assert(w->state == STATE_INIT);
  Writer* writers = newest_writer->load(std::memory_order_relaxed);
  while (true) {
...
    w->link_older = writers;
    if (newest_writer->compare_exchange_weak(writers, w)) {
      return (writers == nullptr);
    }
  }
}

Write WAL

对应流程图中 4 步骤。因为 WAL 的写入需要保证不同 batch group 之间写入的顺序性,只能通过单线程进行写入,因此leader 线程会执行具体的 WAL 操作。在互斥锁的保护之下,leader 线程会收集自身和来自 follower 线程的 writer,合并称一个 batch,并设置 batch 一个 seq 值,随后写入 WAL 文件并进行 fsync 操作。需要说明的是,每一个 batch group 都会有一个 leader 线程,但不同的 batch group leader 线程是互斥的,这一点由互斥锁来保证。

Insert Into MemTables

leader 线程完成 WAL 写入后,唤醒 follower 线程。随后,leader 线程和 follower 线程均开始并行地执行 memtable 写操作。RocksDB 的 memtable 采用无锁设计,通过 CAS 操作支持并发写入。每个写入线程的 writer 在开始写入前会被赋值一个 seq,保证每个线程写入 key 的唯一性,因此无需考虑并发写入导致 key 覆盖的问题,这是 RocksDB 相对于其他存储引擎的一大优势。

Complete

leader 线程会等待 batch group 所有的线程执行完成,随后退出,完成一个 batch group 的写入。

你可能感兴趣的:(RocksDB 的 Group Write 机制)