# ReadOptions
## 点查和Scan都适用
- **`snapshot`**:`const Snapshot *`,快照读,默认为空
- **`deadline`**:`microseconds`,调用超时,可以设置为当前时间+timeout
- 适用于一次 Get,MultiGet,Seek 和 Next 调用。
- 默认为零,表示不启用
- **`io_timeout`**:`microseconds`,传递给底层文件系统的超时,例如每次读文件时。
- **`read_tier`**:`ReadTier`,读取的数据范围,例如只从 cache 中读取,读不到返回 `Status::Incompletle`
- `kReadAllTier`:默认值,读取 memtable, block cache, OS cache 和 storage
- `kBlockCacheTier`:读取 memtable 和 block cache
- `kPersistedTier`:只读取持久化过的数据,当禁用 WAL 时,跳过 memtable。
- 只支持 Get 和 MultiGet,不支持迭代去
- `kMemtableTier`:只读取 memtable
<br>
- **`rate_limiter_priority`**:`Env::IOPriority`,读取时更改 `DBOptions::rate_limiter` 的 priority,默认为 `Env::IO_TOTAL`,表示不更改。
- **`value_size_soft_limit`**:`uint64_t`,适用于 MultiGet,当累积的 value size 达到此限制后,剩余的 keys 会返回 `Status::Aborted`。
- **`merge_operand_count_threshold`**:`optional<size_t>`,当前只适用于点查,当应用的merge操作超过此阈值后,会返回一个特殊的 Ok(subcode 为 `kMergeOperandThresholdExceeded`)
- **`verify_checksums`**:读取时检验 checksum,默认为 true。
- **`fill_cache`** :`bool`,是否将读过的 block 放入 block cache,默认 true
- **`ignore_range_deltions`**:`bool`,是否跳过 range tombstone,默认 false
- 对于不使用 `DeleteRange()` 的 DB,设置它可以提升性能。
- **`async_io`**:`bool`,启用后 Rocksdb 会异步 prefetch 一些数据,默认 false。
## 只适用于 Scan
- **`iterate_lower_bound`**:`const Slice *`, 定义 backward iterator 能返回的最小 key(包含),超过 Valid() 会返回 false。
- 如果配置了 `prefix_extractor`,要求此 key 跟 Seek 目标具有相同的 prefix
- **`iterate_upper_bound`**:`const Slice *`,定义 forward iterator 能返回的上届(不包含)。
- **`total_order_seek`**:`bool`,默认 false。用于跳过 prefix bloom filter
- 一些 table format(如 plain table)不支持该选项。
- doc: [How to ignore prefix bloom filters in read](https://github.com/facebook/rocksdb/wiki/Prefix-Seek#how-to-ignore-prefix-bloom-filters-in-read)
- commit: [ReadOptions.total_order_seek to allow total order seek for block-based table when hash index is enabled](https://github.com/facebook/rocksdb/commit/23861857c461cd61b3b42b736783131c317758b8)
> When `options.prefix_extractor` is not `nullptr` and with default `ReadOptions`, iterators are not guaranteed a total order of all keys, but only keys for the same prefix.
- **`auto_prefix_mode`**:`bool`,默认 false。当为 true 时,默认使用 `total_order_seek = true`,并且 RocksDB 可以选择性地启用 prefix seek mode 如果不会产生跟 `total_order_seek`不同的结果。
- doc: [Adaptive Prefix Mode](https://github.com/facebook/rocksdb/wiki/Prefix-Seek#adaptive-prefix-mode)
- **`prefix_same_as_start`**:`bool`,默认 false,表示迭代器只迭代跟 seek 目标相同前缀的数据。
<br>
- **`readahead_size`**:`size_t`,用户提供的 block prefetch size。
- **`adaptive_readahead`**:`bool`,默认 false。
- PR: [Reuse internal auto readhead_size at each Level (expect L0) for Iterations](https://github.com/facebook/rocksdb/pull/9056)
- 切换文件时,继承之前的 `readahead_size`
- **`auto_readahead_size`**:`bool`,默认 true。scan 时自动对 `readahead_size` 调优。
- 需要设置 `iterate_upper_bound` 才能启用。
- 只推荐 forward scans 使用。如果是 backward scans 内部会自动禁用(即使重新发起 forward scan)
- PR:[Lookup ahead in block cache ahead to tune Readaheadsize](https://github.com/facebook/rocksdb/pull/11860)
<br>
- **`background_purge_on_iterator_cleanup`**:`bool`,默认为 false。避免在迭代器释放时在前台删除文件。而是调度到高优先级队列(flush job queue)后台执行。
- **`table_filter`**:`function<bool(const TableProperties&)>`,基于 table property 返回相关的 key 是否存在这个 table 中。返回 false 则跳过 scan 该 table。默认为空。
- **`max_skippable_internal_keys`**:`size_t`,seek 操作时当 skipped 的 ikeys 超过此阈值时,
直接失败返回 incomplete。默认 0,表示不启用。
- **`tailing`**:`bool`,默认 false。用于创建 tailing iterator,可以用于读取后续新添加的数据。
- **`pin_data`**:`bool`, 默认 false,将迭代器加载的 blocks 固定到内存中(直到迭代器释放)。<br>
------
# GetContext
- 点查的上下文信息/中间状态,例如指向 value slice 的指针,key,merge context 等。
- 当找到 internal key 时调用 SaveValue(),有 merge 的话可能会调用很多次。
- **GetState** enum
- kNotFound : 初始状态
- kFound :发现完整 value,结束状态
- kDeleted :读到 Deletion / Range Deletion entry,结束状态
- kMerge :读到 kTypeMerge entry,需要读取更多进行 merge
- kUnpectedBlobIndex
- kMergerOperatorFailed
- kCorrupt
## 核心成员
- **`state_`**:`GetState`,初始值由外部提供
- 继承之前的结果(例如从 memtable Get 的结果),kNotFound 或者 kMerge
- **`user_key_`**:`Slice`,查找的目标 key
- **`pinnable_val_`**:`PinnableSlice *`,构造时传入的指针
- **`value_found_`**:`bool *`,构造时传入的指针。是否读到了 value,KeyMayExist 会使用。
- **`seq_`**: `SequenceNumber`,找到的 key 的最大 seq,构造时传入的指针
- **`callback_`**:`ReadCallback *`,额外的回调,用于事物检测 key 是否可见
- **`max_covering_tombstone_seq_`**:`SequenceNumber *`
- 覆盖 `user_key_` 的 range tombstones 的最大 seq,读取 memtables / tables 时会尝试更新
- MemTable::Get() 和 TableCache::Get() 中
- PR: [Use only "local" range tombstones during Get](https://github.com/facebook/rocksdb/pull/4449)
- **`do_merge_`**:`bool`,是否进行 merge 操作,不进行的话就只收集 merge operands
- **`merge_context_`**:`MergeContext`,merge 的状态,初始值由外部提供
- 继承之前的结果(例如从 memtable 读到的)
- **`replay_log_`**:`std::string *`,row cache entry 指针,如果不为空 Get 时会进行填充
## NeedToReadSequence
- 是否需要读 key 的 seq
- 如果构造时传递的 `seq_` 不为 nullptr,则需要读
## MarkKeyMayExist()
- index/filter block 显示 key 可能存在,但 block cache 中没有该 KV。
- 应用场景:实现 `DBImpl::KeyMayExist()`,只尝试读取内存(memtable + block cache)。
- 操作流程:
- 设置 `state_` 为 kFound
- 设置 `value_found_` 为 false
## appendToReplayLog()
- 入参:
- **`type`**:`ValueType`
- **`value`**:`Slice`
- **`ts`**:`Slice`
- SaveValue() 时填充 `replay_log_`(row cache entry)
## SaveValue()
- 入参:
- **`parsed_key`**:`const ParsedInternalKey &`
- **`value`**:`const Slice &`
- **`matched`**:`bool *`
- **`read_status`**:`Status *`
- 返回值:
- 如果由于 merge 需要读更多的 key,则返回 true。
- 如果发现了完整的 value,则返回 false。
- 记录这个 key,value 和 metadata(如 seq)到 GetContext。
- 如果 user_key 不匹配,直接返回 false。即目标 key 不存在。
- 如果 `parsed_key` 与 `user_key_` 匹配,设置 `matched` 为 true。
- 设置 `seq_` 如果还没有初始化
- 设置为 `parsed_key.seq` 和 `max_covering_tombstone_seq_`中的最大值(如果后者不为空的话)
- 如果 `max_covering_tombstone_seq_` 大于 `parsed_key` 的 seq,重写 value type 为 kTypeRangeDeletion(有更新的 range deletion 覆盖该 key)
- 根据当前 `state_` 和 value type 进行不同的处理,例如设置 value 或者处理 merge 等。
---
# TableCache
- 负责管理 TableReader 缓存,并包装了对底层 SST 文件的读取。
- 通过缓存可以提升性能,避免不必要的文件打开、对象分配和初始化。
- 例外:compaction 的迭代器不走 cache。
- 另外还负责管理 row cache。
- 每个 cf 一个。
## 核心成员
- **`cache_`**:`CacheInterface`, TableReader cache
- cache key:table file number
- **`imortal_tables_`**:TableReader 不会淘汰,是永生的。
- 当配置 `max_open_files == -1` 时为 true,此时 cache 的 capacity 为无限大
- **`loader_mutex_`**:`Striped<Mutex>`,Load/插入 TableReader 到 cache 的写锁
- 按 key(fd number)hash 使用 128 个锁中的某一个
- **`row_cache_id_`**:`std::string`,row cache entry 前缀,标识当前使用者(即当前 TableCache)
- 构造函数中调用 `ioptions_.row_cache->NewId()`分配,避免不同的 db 实例/cf 共享同一个 row cache 时,因为 key 冲突导致混乱。
- **`ioptions_`**:`ImmutableOptions`,使用到内部的 `row_cache`实例和一些配置。
## Cache 结构
- **Table cache**:
- cache key:SST file number
- cache value:TableReader
- **Row cache**:
- cache key:prefix + user key
- cache value:GetContext 中的 `replay_log_`
## CreateRowCacheKeyPrefix()
- 创建 row cache key 的前缀。
- 入参:
- **`options`**:`const ReadOptions &`
- **`fd`**:`const FileDescriptor &`
- **`internal_key`**:`const Slice &`
- **`get_context`**:`GetContext *`
- **`row_cache_key`**:`IterKey&`,输出参数
| row_cache_id | fd_number | cache_seq_no |
| ------------ | --------- | ------------ |
- `cache_seq_no` 默认为 0,相当于前缀不含序列号,cache key 为 user_key,缓存非快照读。
- 此时缓存的是文件内该 key 的最新值。
- `cache_seq_no` 不为 0 时,表示缓存快照读(缓存带版本的 key)
- 此时赋值为 `internal_key` 的 seq + 1,以和 0 作区分。
- 不为 0 的条件:
- 快照读(`option.snapshot`)且
- 快照序列号小于等于 `fd.largest_seqno`
- 即大于的话,也设置为 0,即读到的是文件内最新 key,对该快照可见。
- *存在 ReadCallback* ([#11816](https://github.com/facebook/rocksdb/pull/11816))
- 返回值:`cache_seq_no`(未加一)
## GetFromRowCache()
- 从 row cache 读取该 key, 调用 `replayGetContextLog()` 回放到 GetContext。
- 返回是否读到。
## GetTableReader()
- 创建一个新的 TableReader
- 执行流程:
1. 根据文件编号拼接路径,调用 `FileSystem::NewRandomAccessFile()` 打开文件
- 文件如不存在会尝试 leveldb 文件(`.ldb`)
2. 设置 hint(`advise_random_on_open`),构建 `RandomAccessFileReader`
3. 从 `ioptions_.table_factory`调用 `NewTabelReader()` 创建 TableReader 对象
## FindTable()
- 从 cache 中查找某个 file_number 对应的 TableReader;
- 如果 cache 不存在则新建(通过 `GetTableReader()`),并更新到 cache 中。
- 如果指定了 `no_io` 选项,则不新建,直接返回 Incomplete 错误
- 例如 read option 指定了 `read_tier == kBlockCacheTier`时
## Evict()
- 从 table cache 中删除 `file_number` 对应的 TableReader。
## Get()
- 从某个 SST 中读取 key
- 流程:
1. 尝试读取 row cache,前提条件:
- 配置了 row cache 且 不需要读 key 的 seq(row cache 中没有存储 key 原始的 seq)
- 如果读到了就直接返回
2. 获取 TableReader (调用 `FindTable`)
3. 更新 GetContext 的 `max_covering_tombstone_seq`
- 前提条件: `ignore_range_deletions` 为 false
- 调用`t->NewRangeTombstoneIterator()` 和 `MaxCoveringTombstoneSeqnum()` 获取
- 如果比 GetContext 当前的大,则更新
4. 从 TableReader 中读取 key
- 调用 `TableReader::Get()` 方法
- 先 SetReplayLog,这样读取过程中可以填充 row cache entry
5. 填充 row cache
## NewIterator()
- 为某个特定编号的 SST 文件创建迭代器。
- 执行流程:
1. 调用 `FindTable()` 获取 TableReader
2. 创建 Table 迭代器
- 应用 `ReadOptions`中的 `table_filter`, 如果返回 false 表示不需要读取该 table,则创建空迭代器;
- 否则调用 `TableReader::NewIterator()`
3. 如果 `for_compaction`参数为 true,调用 `TableReader::SetupForCompaction()`
- BlockBasedTable 下为空操作
4. 创建 range del 迭代器(如果 `ignore_range_deletions` 为 false)
- [ ] RangeDelAggregator::AddFile
## UpdateRangeTombstoneSeqnums()
- 根据某个 TableReader , 根据 MultiGetContext 中各个 key 的 `max_covering_tombstone_seq`
## ApproximateKeyAnchors()
- 算法见 [[compaction#^1c1c5a|GenSubcompactionBoundaries()]]
## GetMemoryUsageByTableReader()
- 获取某个文件编号的 TableReader 内存占用
- ==不包括== block cache 的内存占用(即 filter/index blocks)
- 对应 DB 的 `kEstimateTableReadersMem`即 `rocksdb.estimate-table-readers-mem` 属性
- `Verson::GetMemoryUsageByTableReaders()` 统计所有文件的。
- 调用 `TableReader::ApproximateMemoryUsage()` 方法获取。
- [[block based#^8a19fc|BlockBasedTable::ApproximateMemoryUsage]]
-------
# FilePicker
- 用于为某个特定的 key 挑选下一个文件,一层层地搜索文件。
- 当在较小的 level 中找到后,就可以停止搜索(除非处于 `MergeInProgress` 状态)
## 核心成员
- **`num_levels_`**:`unsigned int`,非空 levels 数量
- 来自 `VersionStorageInfo::num_non_empty_levels_`
- **`level_files_brief_`**:`Vector<LevelFilesBrief*>`,各层的文件列表
- **`file_indexder`**:[[version#^20bb12|FileIndexder]],快速定位 SST 文件
- **`user_key_`**, **`ikey_`**: `Slice`,查找的目标用户/内部 key
<br>
- **`curr_level_`**:`unsigned int`,当前查找到哪层了,初始值 -1
- **`curr_file_level_`**:`LevelFilesBrief *`,当前层的文件列表
- **`returned_file_level_`**: `unsigned int`
- [ ] 变量未使用
- **`hit_file_level_`**:`unsigned int` ,有文件命中时,当前的 level
- 用于获取 `GetNextFile()` 返回的文件所属的 level
- **`is_hit_file_last_in_level_`**:`bool`,是否命中了该层的最后一个文件
- 用于 `optimize_filters_for_hits`选项为 true 时,最后一个文件跳过 filter。
- **`search_ended_`**:`bool`,搜索结束(所有 levels 都查找完了)
- **`search_left_bound_`**, **`search_right_bound_`**:`int32_t`
- FileIndexder 中通过搜索上一个 level 后,确定的当前 level 的搜索范围
- **`curr_index_in_curr_level_`**:搜索到当前 level 的哪个文件了
- **`start_index_in_curr_level_`**:当前 level 搜索的起始位置(通过 search bound 确定)
## PrepareNextLevel()
- 准备搜索下一层,对 level 局部变量进行初始化
- 如果没有下一层了就返回 false。
## GetNextFile()
- 返回下一个需要读取的文件。
---
# Version
## Get()
1. 初始化 GetContext 和 FilePicker
2. 循环调用 `FilePicker::GetNextFile()` 获取需要读的文件,针对每个文件:
1. 调用 `TableCache::Get()`读取该文件
2. 检测 GetContext 当前的状态,针对不同状态作不同处理:
- `kNotFound`:当前文件没找到继续搜索下一个文件
- `kMerge`:没获取到完整 key,继续搜索下一个文件
- `kFound`:找到了,函数返回
- `kDeleted`:找到了 delete 记录,返回 NotFound
- 错误状态:也直接返回
3. 如果 GetContext 最后为 merge 状态,作最终的 merge 动作
----
# DBImpl
## GetImpl()
1. 调用 `GetAndRefSuperVersion()` 获取 sv
2. 确定读取的 seq
- 有快照使用快照的,无则使用 `VersionSet::GetLastPublishedSequence()`
- 并基于该 seq 构建 LookupKey:`user_key | read_seq | kValueTypeForSeek`
3. 从 memtables 中读取 LookupKey
- 如果跳过 memtable 则不执行该步骤(例如配置 `read_tier` 为 kPersistedTier)
- 先读取 mutable memtable,如果读取完整 value 不成功(如不存在或者 MergeInProgress),则再读取 immutable memtables
- 如果最终能读取到完整 value,则归还 sv,函数返回
4. 从 SSTs 中读取
- 调用 `sv->current->Get()`
5. 归还 sv
### DeleteRange 处理
- 读取每个 tier 时,获取一个 `read_seq` 可见的、覆盖目标 user key 的、seq 最大的 tombstone,并记录其 seq 为 `max_covering_tombstone_seq`,后续如果读到 value 的 seq 小于它,则视为删除(NotFound)。
----
# TODO
- [ ] MultiGet
- [PR: Introduce a new MultiGet batching implementation](https://github.com/facebook/rocksdb/pull/5011)
- [PR: MultiGet batching in memtable](https://github.com/facebook/rocksdb/pull/5818)
- [x] 如何确定 row cache 中的是最新的
- cache entry 是绑定 SST 的。先确定读哪个 SST,才加载对应的 row cache,而 SST 是静态的。
- [ ] Merge
- [ ] [PR: FullMergeV3](https://github.com/facebook/rocksdb/pull/11858)
- [ ] MergeContext
- BlockBasedTable