数据存储及索引结构

有两种主要的数据存储及索引结构:LSM-Tree 和 B-Tree。由于索引结构总是针对数据存储结构而设计和优化的,因此两者是紧密关联的,很难分开来讨论。

LSM-Tree

LSM:Log-Structured Merge-Tree。从 AOF 和 SSTable 演变而来。

AOF

  • Append-Only File
  • 只添加新记录,不修改或删除已有记录;
  • 当前写入文件超出一定大小时,创建新的文件再写入;这些文件称为 Segment Files ;
  • 需要压缩和合并 Segment Files,让最终的 AOF 文件体积更小;
  • 合并完成后,删除旧的 Segment Files,读请求会转发给最新的 AOF 文件;
  • Bitcask 模型: 多个不可变 Segment Files + 一个活跃的可写 Segment File;
  • 适合 key 比较少,但 value 更新频繁的场景;适合日志文件场景。

索引结构:

  • 需要内存中的 HashMap 来索引指定 key 在磁盘文件上的记录;
  • HashMap 索引:key 是记录的 key, value 是记录在磁盘文件上的 offset。

实现细节:

  • 文件格式:文本文件更可读,二进制文件会更快。通常格式是“checksums + 数据长度+数据内容”;
  • 删除记录:丢弃对应 key 的之前记录,能加快合并过程;
  • 故障恢复:在磁盘上保持 AOF 的 HashMap 的快照, Bitcask 可以加速恢复速度;
  • 部分写入:Bitcask 包含 checksums, 确保部分写入的脏数据被检测到和忽略掉;
  • 并发控制:已有文件是不可变的,支持并发读。

优点:

  • 顺序写,写磁盘效率更高;
  • 并发控制和恢复更简单,无需考虑已有记录被更新成新值。

缺点:

  • 不支持范围查找;
  • 有大量 keys 时,内存占用过大,而磁盘实现的 HashMap 的性能会很低(随机 IO 太多)。

SSTable

  • Sorted String Table
  • keys 是有序的;
  • 合并时可以采用归并排序;
  • 可以将 keys 分组成一个个有序块,压缩后存放在磁盘文件里,支持范围查找。

SSTable 内存索引:

  • 依然是 HashMap 结构;
  • 只需包含更少 keys , 比如这些 keys 是一组记录 keys 的前缀集合;
  • 一个 Segment File 可以只需要一个 key;
  • 可以使用红黑树或 B 树;
  • 当内存索引过大时,可以写入磁盘文件 SSTable File;
  • 查询记录时,先查询内存索引,找到记录所在的文件位置,再取记录;如果在内存索引里找不到,则依次从最近的 SSTable File 里去查找记录的磁盘索引位置。

优化:

  • 如果数据写入了内存索引但还没写入磁盘文件 SSTable File,突然发生故障了,就可能导致索引不完整。可以加一个额外日志,每当写内存索引时,也写一条记录在这个日志里。

优点:

  • 支持范围查找;
  • 由于是顺序写,写吞吐量很高。

LSM-Tree

  • LSM-Tree 基于 SSTable 构建并进行了优化;
  • 压缩和合并可以采用 size-tiered 或 leveled;
  • 使用 BloomFilter 来定位不存在的 keys ,避免在找不到 key 时在磁盘中搜索过多次数;

B-Tree

  • 将记录集分割成固定块或页,一次读写一个块或页; LSM-tree 的记录集分割是可变大小的;
  • 多重有序表的链接;

优化:

  • WAL:Write-ahead Log ,保证 B 树可靠性。避免在 B 树节点分裂发生故障时,B 树会 corrupted ;
  • Copy-on-write:
  • B+ 树:内节点只存 key ,不存数据,让树的内节点能存储更多的 keys ,减小树的高度,减少磁盘读写次数;
  • 叶子页使用链表连接起来,保持有序。

对比

  • LSM 写性能更好,而 B-Tree 读性能更好;
  • LSM 压缩文件比 B-Tree 体积更小;
  • 对于一个 key 来说, LSM 存在多份拷贝,而 B-Tree 只有一份; B-Tree 能更好地支持事务。

参考资料

上一篇:一文带你看透基于LSM-tree的NoSQL系统优化方向(到2020年为止 最全、最新)


下一篇:KVELL:快速持续键值存储的设计与实现