基于 RocksDB 的 WAL
架构
在本节中,我们将介绍单机版 WAL 的实现(基于 RocksDB)。预写日志(write-ahead logs,以下简称日志)在本实现中是按表级别进行管理的,对应的数据结构为 TableUnit。为简单起见,所有相关数据(日志或元数据)都存储在单个 column family(RocksDB 中的概念,可以类比关系型数据库的表) 中。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  |             ┌─────────────────────────┐
            │         HoraeDB         │
            │                         │
            │ ┌─────────────────────┐ │
            │ │         WAL         │ │
            │ │                     │ │
            │ │        ......       │ │
            │ │                     │ │
            │ │  ┌────────────────┐ │ │
 Write ─────┼─┼──►   TableUnit    │ │ │
            │ │  │                │ │ │
 Read  ─────┼─┼──► ┌────────────┐ │ │ │
            │ │  │ │ RocksDBRef │ │ │ │
            │ │  │ └────────────┘ │ │ │
Delete ─────┼─┼──►                │ │ │
            │ │  └────────────────┘ │ │
            │ │        ......       │ │
            │ └─────────────────────┘ │
            │                         │
            └─────────────────────────┘
  | 
数据模型
通用日志格式
通用日志格式分为 key 格式和 value 格式,下面是对 key 格式各个字段的介绍:
namespace: 出于不同的目的,可能会存在多个 WAL 实例(例如,manifest 也依赖于 wal), namespace 用于区分它们。region_id: 在一些 WAL 实现中我们可能需要在共享日志文件中,管理来自多个表的日志,region 就是描述这样一组表的概念, 而 region id 就是其标识。table_id: 表的标识。sequence_num: 特定表中单条日志的标识。version: 用于兼容新旧格式。
1
2
3
  | +---------------+----------------+-------------------+--------------------+-------------+
| namespace(u8) | region_id(u64) |   table_id(u64)   |  sequence_num(u64) | version(u8) |
+---------------+----------------+-------------------+--------------------+-------------+
  | 
下面是对 value 格式各个字段的介绍(payload 可以理解为编码后的具体日志内容):
1
2
3
  | +--------------------+----------+
| version header(u8) | payload  |
+--------------------+----------+
  | 
元数据
与日志格式相同,元数据以 key-value 格式存储, 本实现的元数据实际只是存储了每张表最近一次 flush 对应的 sequence_num。下面是定义的元数据 key 格式和其中字段的介绍:
namespace, table_id, version 和日志格式中相同。key_type, 用于定义元数据的类型,现在只定义了 MaxSeq 类型的元数据,在。
因为在 RocksDB 版本的 WAL 实现中,日志是按表级别进行管理,所以这个 key 格式里面没有 region_id 字段。
1
2
3
  | +---------------+--------------+----------------+-------------+
| namespace(u8) | key_type(u8) | table_id(u64)  | version(u8) |
+---------------+--------------+----------------+-------------+
  | 
这是定义的元数据值格式,如下所示,其中只有 version 和 max_seq(flushed sequence):
1
2
3
  | +-------------+--------------+
| version(u8) | max_seq(u64) |
+-------------+--------------+
  | 
主要流程
- 打开 
TableUnit:- 读取所有表的最新日志条目,目的是恢复表的 next sequence num(将会分配给下一条写入的日志)。
 - 扫描 metadata 恢复上一步遗漏的表的 next sequence num(因为可能有表刚刚触发了 flush,并且之后没有新的写入日志,所以当前不存在日志数据)。
 
 - 读写日志。从 RocksDB 读取或者写入相关日志数据。
 - 删除日志。为简单起见,在本实现中只是同步地删除相应的日志数据。