本文基于 RocksDB v6.8.1 版本
键值的内部编码
RocksDB 内部将用户提供的键值对 称为 user_key
, 并在内部将其编码为 internal_key
. 编码过程发生在 MemTable::Add
中, internal_key
的编码过程大致如下:
1 | uint32_t key_size = static_cast<uint32_t>(key.size()); |
其将序列号(SequenceNumber s
)和键值类型(ValueType type
) 编码为一个8字节整数, 并添加到 internal_key
内部.
因此 internal_key
的整体排布如下:
1 | |user_key|timestamp|seqno|type| |
Memtable
提供的比较器 Memtable::KeyComparator comparator_
在进行比较时, 只使用 InternalKeyComparator comparator
比较 key + Fixed64(packed)
这部分, 其中 key
部分 InternalKeyComparator
使用 用户指定的 Comparator user_comparator_
进行比较.
值得注意的是 RocksDB 支持 User-defined-Timestamp 功能, 这个功能允许用户为键值对插入一个可指定长度的时间戳, 并可指定一个 Timestamp-aware
的 Comparator
. 时间戳部分在进入 MemTable::Add
时是已经编码在 key
中的尾部的, 其比较过程直接由 Comparator::Compare
处理.
键值的内部类型
键值类型 ValueType
定义在 db/dbformat.h
中, 其主要类型如下:
1 | enum ValueType : unsigned char { |
其中kTypeDeletion
, kTypeValue
, kTypeMerge
, kTypeBlobIndex
及 kTypeSingleDeletion
为 “值类型”
1 | // Checks whether a type is an inline value type |
具体实现中, RocksDB 将 ValueType 的数据与 kTypeRangeDeletion
的数据分开存储, 这些类型被合称为 “扩展值类型”
1 | // Checks whether a type is from user operation |
kTypeMerge
, kTypeSingleDeletion
及 kTypeRangeDeletion
类型分别对应 RocksDB 提供的 Merge 和 Single-Delete 以及 DeleteRange 操作, 此处不再赘述.
键值的存储过程
WAL
这部分还没仔细看, 鸽了.
MemTable
键值对通过 MemTable::Add
添加到 Memtable
中, 在编码为 internal_key
后, Memtable
根据值的类型对其进行存储.
Memtable
默认的存储结构为跳表(SkipListRep : MemTableRep
), RocksDB 支持的所有存储结构均继承了 MemTableRep
(memtable representation). 完整的 MemTableRep
需实现内存分配(Allocate
), 插入(Insert
), 读取(Get
) 以及 迭代器(GetIterator
) 等操作, Memtable
通过这些操作来实现最终的键值插入和查找功能.
在 MemTable::Add
中, 值类型(IsValueType() == true
) 放置在 MemTableRep table_
中, kTypeRangeDeletion
类型放置在 MemTableRep range_del_table_
中. 值得注意的一点是 range_del_table_
总是 SkipListRep
.
在满足条件后, DBImpl::SwitchMemtable
将被调用, 将 Memtable
变为 Immutable memtable. 具体过程就是通过 MemTableList::Add
将 Memtable
放入 MemTableList cfd->imm()
, 在 MemTableList::Add
中还会调用 Memtable::MarkImmutable
通知 Memtable.
Flush & Compaction
Flush 过程主要在 FlushJob::WriteLevel0Table
中, 笔者认为其与 Compaction 过程的最大不同只是其构建 SST 的数据来源不同.
在 Flush 过程中, 所有待 Flush 的 Memtable
的迭代器((Memtable m)->NewIterator
) 将通过 NewMergingIterator
被组合为一个 ScopedArenaIterator
; 并和所有的 (Memtable m)->NewRangeTombstoneIterator
一起传递给 BuildTable
, 进行最终的 SST 创建过程.
Compaction 过程主要在 CompactionJob::ProcessKeyValueCompaction
中.
SST 构建
BlockBasedTable 是 RocksDB 默认的 SST 实现. 它由 Block
和 Footer
组成, 其文件布局如下:
1 | <beginning_of_file> |
其中 [Footer]
中存放了 metaindex block 和 index block 的 BlockHandle
; [metaindex block]
则按 (块名称, BlockHandle
) 存储了 元信息块 (meta block) 的偏移, 例如 range deletion block 的 key 为 std::string kRangeDelBlock = "rocksdb.range_del";
[Footer]
内容:
1 | legacy footer format: |
BlockHandles
用于描述块的偏移量和大小
1 | offset: varint64 |
值得注意的是每个 Block (除 [Footer]
外的所以类型的均相同), 都设计为用于存储键值数据, 其大概结构如下:
1 | <完整KV 1> |
其中 <IndexTypeAndNumRestarts>
通过 PackIndexTypeAndNumRestarts
编码, 记录了索引类型及重启点个数.
Block 通过 BlockBuilder
进行创建, 通过 BlockBuilder::Add
方法可写入一个键值对; 在写入完成后通过 BlockBuilder::Finish
写入重启点信息和 block footer 完成单个块的构建过程.
在存储和读取时, SST 会以 Block 为单位进行读取和写入. 创建 SST 时, 在使用 BlockBasedTableBuilder::Add
按顺加入全部键值对后, 调用 BlockBasedTableBuilder::Finish
将 按文件布局顺序写入各元信息块和[Footer]
, 完成整个 SST 的构建过程.
写入顺序:
1 | 1. [meta block: filter] |