土法炼钢兴趣小组的算法知识备份

【RocksDB 内核机制】Iterator 与 MergeIterator:范围扫描与 tombstone

文章导航

分类入口
databasestorage
标签入口
#rocksdb#iterator#merge-iterator#internal-iterator#range-scan#tombstone#level-iterator#db-iter

目录

第 7 篇 讲清了 GetImpl 如何在 SuperVersion 上做点查;范围查询(NewIterator、前缀扫描、Compaction 输入归并)走的是 另一套栈:底层是多个 InternalIterator,中间用 MergeIterator 按 Internal Key 序归并,最外层 DBIter同一 user key 的多版本 折叠成用户可见的一条记录,并处理 tombstone 可见性

本文是系列 第 8 篇,源码锚点:RocksDB 9.xDBImpl::NewInternalIteratordb/db_impl/db_impl.cc)、MergingIteratortable/merging_iterator.cc)、DBIter::IsVisibledb/db_iter.cc)。多路归并的 最小堆直觉lsm-tree DIY Compaction 篇 同源——那篇实现了 toy MergeIterator;本篇对照生产路径与 读侧 tombstone 语义

版本锚定:RocksDB 9.xreproduce/rocksdb/)。不粘贴未实测的 scan QPS。


一、为什么 Iterator 比 Get 多一层

维度 Get(key) Iterator
目标 单个 user key 的最新可见版本 有序区间上所有 可见 user key
层内 SST FilePicker 定向打开候选文件 L0 每文件一个 iter;L1+ LevelIterator 顺序切换
多版本 GetContext 在文件内从新到旧找 DBIter 跳过 sequence > snapshot 的条目
Tombstone 可见删除 → NotFound 可见 tombstone → 不暴露 该 user key,继续 Next

点查可以在第一个命中文件停止;范围扫描必须把 mem + imm + 各层 SST 的键流 合并 成全局 Internal Key 序,再由 DBIter 去重 / 删隐

flowchart TB
  USER["DB::NewIterator"] --> DBI["DBIter (ArenaWrappedDBIter)"]
  DBI --> MI["MergingIterator"]
  MI --> M["MemTable Iterator"]
  MI --> I["Imm MemTable Iters"]
  MI --> L0["L0 Table Iters (×N)"]
  MI --> L1["LevelIterator L1"]
  MI --> LN["LevelIterator L2..Ln"]
  L0 --> TC["TableCache::NewIterator"]
  L1 --> TC
  LN --> TC

二、InternalIterator 抽象

InternalIteratortable/internal_iterator.h)是 SST、MemTable、MergeIterator 的统一接口:Seek / SeekForPrev / Next / Prev / key / value / status。键值为 Internal Key(含 sequence 与 type)。

典型实现:

实现 职责
MemTableIterator SkipList 上 Seek
BlockBasedTableIterator Index → Data Block 二分
LevelIterator 同层 SST 不重叠,按文件边界 lazy open(db/version_set.cc
MergingIterator 多子 iter 堆归并
EmptyInternalIterator 占位

TableCache::NewIteratordb/table_cache.cc)打开 SST:FindTableTableReader 放入 Table Cache,再 table_reader->NewIterator(...)。Iterator 销毁时通过 RegisterReleaseAsCleanup 释放 cache handle。


三、NewInternalIterator:子迭代器如何组装

DBImpl::NewInternalIteratordb/db_impl/db_impl.cc)在已 ref 的 SuperVersion 上构建整棵树:

MergeIteratorBuilder merge_iter_builder(
    &cfd->internal_comparator(), arena,
    !read_options.total_order_seek && prefix_extractor != nullptr,
    read_options.iterate_upper_bound);

// 1. 可变 MemTable
auto mem_iter = super_version->mem->NewIterator(...);
merge_iter_builder.AddPointAndTombstoneIterator(mem_iter, mem_tombstone_iter);

// 2. Immutable MemTable 列表
super_version->imm->AddIterators(..., &merge_iter_builder, ...);

// 3. L0–Ln(非 kMemtableTier)
super_version->current->AddIterators(read_options, file_options_,
                                     &merge_iter_builder, ...);

internal_iter = merge_iter_builder.Finish(...);
internal_iter->RegisterCleanup(CleanupSuperVersionHandle, ...);

顺序:mem → imm(新→旧)→ L0 各文件 → L1 LevelIterator → … → Ln。MergeIteratorBuilder::Finish 调用 MergingIterator::Finish,若有 range tombstone iter 则初始化堆项(merging_iterator.cc 文件头注释)。

Version::AddIteratorsForLeveldb/version_set.cc):

这与 lsm-tree Compaction 篇 的 L0/L1+ 不变式一致;该文用最小堆归并多个 SST 输入,堆元素来源不同,比较器与 tie-break 规则相同(Internal Key 序)。


四、MergeIterator:堆归并与 range tombstone

MergingIterator 类注释(table/merging_iterator.cc)说明核心机制:

不变式(节选,forward 扫描):

  1. minHeap_.top()ITERATOR 类型。
  2. 堆顶 key 不被任何 range tombstone 覆盖

NewMergingIteratorn == 1直接返回 子 iter,避免堆开销;n >= 2 才分配 MergingIterator(可放在 Arena 上)。

InternalIterator* NewMergingIterator(const InternalKeyComparator* cmp,
                                     InternalIterator** list, int n,
                                     Arena* arena, bool prefix_seek_mode) {
  if (n == 0) return NewEmptyInternalIterator<Slice>(arena);
  if (n == 1) return list[0];
  // n >= 2: new MergingIterator(cmp, list, n, ...)
}

Compaction 侧:FlushJob 也用 NewMergingIterator 合并多个 MemTable(db/flush_job.cc),与读路径共享同一归并组件——读是 在线多路归并, compaction 是 离线多路归并,堆逻辑同构(对读 lsm-tree compaction 堆 的图示)。

prefix_seek_mode

当 CF 配置了 prefix_extractor 且未设 total_order_seek 时,MergeIterator 在 prefix 边界 上优化 Seek(prefix_seek_mode_)。全表扫描或需要严格全序时应设 ReadOptions::total_order_seek = true


五、DBIter:user key 折叠与可见性

DB::NewIterator 最终返回 ArenaWrappedDBIter,内部包装 DBIterdb/db_iter.cc),持有:

IsVisible

bool DBIter::IsVisible(SequenceNumber sequence, const Slice& ts,
                       bool* more_recent) {
  bool visible_by_seq = (read_callback_ == nullptr)
                            ? sequence <= sequence_
                            : read_callback_->IsVisible(sequence);
  bool visible_by_ts = /* timestamp_ub_ / timestamp_lb_ 检查 */;
  if (more_recent) *more_recent = !visible_by_seq;
  return visible_by_seq && visible_by_ts;
}

Next / Seek 循环从 MergeIterator 取 Internal Key,跳过不可见版本,直到:

因此 范围扫描不会返回已删除 key(除非 ReadOptions::fill_cache 等副作用;语义上 Valid()==false 或已越过)。与 GetGetContext::kDeleted 一致。

同一 user key 多版本

Internal Key 序下,同一 user key 按 sequence 降序 排列。DBIter 只向用户展示 第一个可见版本;若可见的是 tombstone,user key 不出现在迭代结果中。更旧版本 不再读取(已在新版本决策中忽略)。


六、Tombstone 可见性细则

点删除(Point Tombstone)

ValueType 含义
kTypeDeletion 普通删除标记
kTypeSingleDeletion 与单个非-merge 值配对删除,compaction 可优化
kTypeDeletionWithTimestamp 带 UDT 的删除

MemTable / SST 内仍存 tombstone 记录;对 snapshot ≤ tombstone.sequence 的读可见,读 更新 snapshot 则 key 消失。

Range Tombstone

Range deletion 存在 meta blockkTypeRangeDeletion),MemTable 也有 range del skiplist。MergeIterator 与 GetImpl 共用 覆盖 sequence 逻辑:max_covering_tombstone_seq 大于点查/迭代 key 的 sequence 时,key 视为删除。

ReadOptions::ignore_range_deletions = true 时跳过 range tombstone iter 构建(危险,仅特殊工具路径)。

Compaction 与 scan 的交互

长范围扫描可能触发大量 SST 的 Index/Data Block 读;tombstone 密集时 DBIter 需 连续 Next 跳过 大量内部键(db/db_iter.cc 注释 long-running operation due to tombstones)。Compaction 不及时会导致 scan 放大空间膨胀(第 10–11 篇)。


七、ReadOptions 对扫描的影响

选项 作用
snapshot 绑定 sequence_,与 Get 一致
iterate_lower_bound / iterate_upper_bound 裁剪 user key 区间;MergeIterator / LevelIterator 可提前终止
total_order_seek 禁用 prefix 优化,全序 Seek
read_tier kMemtableTier 只扫 mem;kBlockCacheTier 仅缓存层
pin_data 迭代期间 pin SST 块,减少 copy,占 cache
background_purge_on_iterator_cleanup Iterator 销毁时后台 purge SuperVersion

Flink / TiKV 嵌入场景的大前缀 scan 需关注 prefix_extractorauto_prefix_mode 是否与 key 编码一致(第 16 篇)。


八、与 Get 路径的对照

                    Get                          Iterator
                      |                              |
            LookupKey + snapshot           DBIter.sequence_
                      |                              |
              mem -> imm -> Version          MergeIterator (mem+imm+L0..Ln)
                      |                              |
              FilePicker 单文件链            全层键流归并
                      |                              |
              GetContext::SaveValue          DBIter::IsVisible + tombstone 折叠
                      |                              |
              单 key 返回                        Valid / key / value 循环

两者共享 SuperVersionsequence 可见性TableCache;Iterator 额外付 堆维护DBIter 去重 成本。L0 文件多时,MergeIterator 子源数量 ≈ imm_count + L0_files + (Ln 层数),是 scan 延迟的重要因子。


九、小结


上一篇Get 与 Snapshot

下一篇Block Cache 与 Bloom

返回 系列目录


参考资料

  1. RocksDB 9.x 源码:db/db_impl/db_impl.ccNewInternalIterator);db/version_set.ccVersion::AddIteratorsForLevelLevelIterator);table/merging_iterator.ccdb/db_iter.ccIsVisible);db/table_cache.cc(A 级)。
  2. RocksDB Wiki, Iterator / Read-only and Secondary Instances(A 级)。
  3. lsm-tree Compaction 与 MergeIterator(DIY 堆归并对照)。
  4. 第 7 篇 Get/Snapshot第 6 篇 SST

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .