# 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