ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RocksDB 3.5 版本解析:WriteBatchWithIndex、total_order_seek 与 API 变更要点

RocksDB 3.5 版本解析:WriteBatchWithIndex、total_order_seek 与 API 变更要点 RocksDB 3.5 版本解析WriteBatchWithIndex、total_order_seek 与 API 变更要点【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdbRocksDB 3.52014-09 发布引入了两项影响深远的核心能力带可搜索索引的写批处理工具类WriteBatchWithIndex支撑 read-your-own-writes 语义与强制全序定位的读选项ReadOptions.total_order_seek解决 hash index 下的前缀 Seek 缺陷同时完成了 BlockBasedTable 选项从Options向BlockBasedTableOptions的结构性迁移。本文基于官方发布文档逐条梳理该版本的特性与破坏性 API 变更并结合当前仓库源码追踪这些设计在今天的落地形态帮助你在移植历史版本代码或理解 RocksDB 读写路径时准确把握每个选项的真实行为与边界条件。WriteBatchWithIndex在构建 WriteBatch 的同时查询其中的数据3.5 发布文档的第一条特性是新增utilities/write_batch_with_index.h提供一个工具类允许在构建WriteBatch的同时从中查询数据。这个类的设计目标是解决上层数据库当时正被 MongoDB 用作存储引擎的核心难题——read-your-own-writes读取自己未提交写入事务的原子性可以通过在事务结束时用一个WriteBatch原子提交所有写操作来保证但如果所有写都只落在最终的批中事务执行期间无法通过DB::Get()读到自己刚写入的值上层数据库被迫为每个事务维护一个内部写缓冲并在每次读时合并数据库与缓冲两个来源的结果——这正是 MongoDB RocksDB 存储引擎最初遇到的痛点。WriteBatchWithIndex的解法是给WriteBatch附加一个可搜索的索引。当前仓库中的实现位于 include/rocksdb/utilities/write_batch_with_index.h其头注释概括了工作机制// A WriteBatchWithIndex with a binary searchable index built for all the keys // inserted. In Put(), PutEntity(), Merge(), Delete(), or SingleDelete(), the // corresponding function of the wrapped WriteBatch will be called. At the same // time, indexes will be built. By calling GetWriteBatch(), a user will get the // WriteBatch for the data they inserted, which can be used for DB::Write(). A // user can call NewIterator() to create an iterator. // If there are multiple updates to the same key, the most recent update is // ordered first (i.e. the iterator will return the most recent update first).从源码结构看它的工作流为写入调用Put()/Merge()/Delete()/SingleDelete()时底层包装的WriteBatch记录被追加同时索引同步构建读取通过NewIterator()创建迭代器可对批内任意 key 执行Seek()提交通过GetWriteBatch()取出底层WriteBatch交给DB::Write()原子落库。迭代器的顺序语义需要注意同一个 key 若被多次更新最新的更新排在最前即迭代器先返回最新值天然满足“读到自己最新写入”的语义。构造函数还接受若干可调参数见 write_batch_with_index.h 第 106 行起的类定义// backup_index_comparator: 同一列族内比较 key 的后备比较器 // reserved_bytes: 底层 WriteBatch 预留给日志等用途的字节数 // max_bytes: 底层 WriteBatch 的最大字节数 // overwrite_key: 若为 true重复插入相同 key 时索引中覆盖旧条目 // 迭代器永远不会返回同一 key 的两条记录 // 注意 Merge 总是作为新更新追加而非覆盖。 explicit WriteBatchWithIndex( const Comparator* backup_index_comparator BytewiseComparator(), ...);迭代器接口同文件中的WBWIIterator类支持SeekToFirst()、SeekToLast()、Seek(target)、SeekForPrev(target)、Next()、Prev()并对“同一 key 上单删记录被后续写覆盖”这类边界提供了HasOverWrittenSingleDel()与GetUpdateCount()等细粒度查询。此外实现层还支持NewIteratorWithBase()这类“批 DB 基线迭代器”的合并遍历其能力随版本持续扩展可在 HISTORY.md 中检索到WriteBatchWithIndex相关的一系列后续增强即把 DB 迭代器传入后得到一个“超级迭代器”让上层直接带着事务自己的未提交写入遍历数据库——这正是 MongoDB 场景下实现范围查询 read-your-own-writes 的关键路径。官方对这一特性的后续专文见 WriteBatchWithIndex 博客其中完整给出了“Get 先查批再查库”“范围查询用超级迭代器”的实现模式。ReadOptions.total_order_seekhash index 下强制全序定位3.5 的第二条特性是新增ReadOptions.total_order_seek当 block-based table 使用 hash index 构建时强制走全序total order定位。要理解它的价值需要理解 hash index 与前缀 Seek 的冲突block-based table 的索引块可以按kBinarySearch、kHashSearch或kBoth构建kHashSearch索引对“按完整 key 的 Get”极快但它无法支持前缀比较式的Seek()——hash 表中不存在“按字典序找第一个 ≥ target 的 key”因此当列族配置了 prefix extractor 并启用前缀 Bloom 过滤/前缀 Seek 优化时若某次 Seek 的 key 恰好用不上这些优化例如 key 不在 extractor 的InDomain内或用户期望全序遍历在 hash index 下就会出现问题。当前仓库中total_order_seek定义于 include/rocksdb/options.h 的ReadOptions// Enable a total order seek regardless of index format (e.g. hash index) // used in the table. Some table format (e.g. plain table) may not support // this option. // If true when calling Get(), we also skip prefix bloom when reading from // block based table, which only affects Get() performance. bool total_order_seek false;其语义有两层迭代器 Seek无论底层 SST 采用何种索引格式都按完整内部 key 的全序定位。在 hash index 路径中这个行为由 table/block_based/hash_index_reader.cc 落实const bool total_order_seek read_options.total_order_seek || disable_prefix_seek; auto it index_block.GetValue()-NewIndexIterator( internal_comparator()-user_comparator(), rep-get_global_seqno(BlockType::kIndex), iter, kNullStats, total_order_seek, ...);当total_order_seek为真时索引迭代器会忽略 prefix index、退化为对索引块的全序二分查找table/block_based/block.h 中同样声明了该参数“If total_order_seek is true, prefix_index_ is ignored.”Get 路径的 Bloom 过滤注释明确指出Get()时若total_order_seek true会跳过 block-based table 的前缀 Bloom 过滤仅影响 Get 性能不影响正确性。对应逻辑在 table/block_based/block_based_table_reader.cc 的NewIterator()中可见bool check_filter !skip_filters (!read_options.total_order_seek || read_options.auto_prefix_mode || read_options.prefix_same_as_start) prefix_extractor ! nullptr;即total_order_seek与 prefix extractor 同时存在时默认关闭 filter 检查。与之配套的还有两个相关读选项从当前源码可以推断出三者的分工定义同样在ReadOptions中选项默认值行为total_order_seekfalse强制全序 Seek忽略 hash index 与前缀 Bloom 优化auto_prefix_modefalse默认按全序处理但 RocksDB 可基于 seek key 与iterate_upper_bound在结果不变的前提下选择性启用前缀 Seekprefix_same_as_startfalse约束迭代器只遍历与 seek key 同前缀的 key使边界依赖当前列族的 prefix extractor需要特别留意auto_prefix_mode注释中记录的已知缺陷当 DB 中存在“短 key”长度小于 prefix extractor 的 full length且 upper bound 与 seek key 前缀不同时自动前缀模式可能漏掉全序迭代本应返回的短 key。若你的 key 空间包含短 key 且无法排除这种情况最稳妥的选择是显式设置total_order_seek true。关于 plain table 的支持边界table/plain/plain_table_reader.cc 中的注释提示plain table 对total_order_seek的处理有限制仅在不设置total_order_seek和auto_prefix_mode时才走前缀 Seek 快路径且构造迭代器时即按“总会以total_order_seektrue方式创建”做保守假设。也就是说该选项在 plain table 上是“尽力而为”的主要面向 block-based table。公开 API 变更四个必须回看的破坏性改动3.5 发布文档列出了四类公开 API 变更。以下逐条说明其含义并对照当前仓库源码确认其后续形态。1. V2 压缩过滤器中 Prefix Extractor 收到 user keyThe Prefix Extractor used with V2 compaction filters is now passeduser keytoSliceTransform::Transforminstead of unparsed RocksDB key.在 3.5 之前CompactionFilter::V2回调链里把未经解析的内部 key携带 8 字节序列号/类型后缀传给 prefix extractor导致 extractor 可能基于错误的内容提取前缀。修正后传入Transform()的是纯 user key。如果你的压缩过滤器依赖 extractor 的输出从 3.5 升级时需验证其前缀提取结果是否符合预期。这一条在 HISTORY.md 的 3.5.0 条目中得到了与发布文档一致的印证。2. BlockBasedTable 选项迁移到 BlockBasedTableOptionsMove BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface.以下选项从通用的Options中移出归入专门的BlockBasedTableOptionsno_block_cacheblock_cacheblock_cache_compressedblock_sizeblock_size_deviationblock_restart_intervalfilter_policywhole_key_filtering其中filter_policy的类型从裸指针改为shared_ptr意味着 filter policy 的生命周期管理从此交给选项结构同一实例可被多个列族共享由引用计数管理。JNI 接口同步调整Java 侧使用者需重新编译。这一重构的合理性在于这些选项只对 block-based table 有意义放在通用Options里对其他表工厂如 plain table、cuckoo table是噪音迁移后通过BlockBasedTableFactory携带自己的选项当前仓库中其定义见 include/rocksdb/table.h语义更清晰。3. 移除已废弃选项disable_seek_compaction 与 db_stats_log_intervalOptions/DBOptions中的disable_seek_compaction与db_stats_log_interval在这两个选项被标记废弃之后于 3.5 正式删除。从当前仓库源码结构看这两个标识符在代码库中已无踪迹升级时需将其从选项文件OPTIONS文件解析会在遇到未知字段时报错与配置代码中清除。4. OptimizeForPointLookup() 的参数与行为变化OptimizeForPointLookup()的签名调整为只接受一个参数——block cache 大小MB且其行为被重新定义为同时构建 hash index、Bloom filter 与 block cache的点查特化配置。当前仓库中的实现位于 options/options.cc 的ColumnFamilyOptions::OptimizeForPointLookupColumnFamilyOptions* ColumnFamilyOptions::OptimizeForPointLookup( uint64_t block_cache_size_mb) { BlockBasedTableOptions block_based_options; block_based_options.data_block_index_type BlockBasedTableOptions::kDataBlockBinaryAndHash; block_based_options.data_block_hash_table_util_ratio 0.75; block_based_options.filter_policy.reset(NewBloomFilterPolicy(10)); block_based_options.block_cache NewLRUCache(static_castsize_t(block_cache_size_mb * 1024 * 1024)); table_factory.reset(new BlockBasedTableFactory(block_based_options)); memtable_prefix_bloom_size_ratio 0.02; memtable_whole_key_filtering true; return this; }从该实现可以读出 3.5 承诺的三项能力在今天的完整形态hash indexdata_block_index_type kDataBlockBinaryAndHash数据块级索引同时带二分与 hash 两套查找结构点查先走 hashBloom filterNewBloomFilterPolicy(10)每 key 约 10 bitblock cache按传入 MB 值创建 LRU cache此外还配套设置了 memtable 侧的优化memtable_prefix_bloom_size_ratio 0.02、memtable_whole_key_filtering true使前缀 Bloom 过滤在 memtable 与 SST 两侧都生效——点查场景下“写路径刚写入的值”同样能被快速判定存在与否。版本信息与适用前提发布记录该发布文档 docs/_posts/2014-09-15-rocksdb-3-5-release.markdown 与 HISTORY.md 中## 3.5.0 (2014-09-03)条目相互印证3.5 发布于 2014-09。头文件位置说明发布文档中特性 1 写的是include/utilities/write_batch_with_index.h这是 3.x 早期的头文件布局3.4 版本已将include/utilities/*.h整体迁移到include/rocksdb/utilities/*.h当前仓库中的规范路径是 include/rocksdb/utilities/write_batch_with_index.h引用时以新路径为准。适用前提total_order_seek主要针对以 block-based table 为主表格式的部署plain table 对该选项的支持有限见前文源码注释。WriteBatchWithIndex的使用前提是上层自行管理事务的“写入—缓冲—提交”生命周期RocksDB 只负责批内索引与原子提交。延伸阅读仓库内与本篇直接相关的配套文章有 WriteBatchWithIndex 专文read-your-own-writes 的完整落地模式与 data_block_hash_index 博客OptimizeForPointLookup所依赖的数据块级 hash index 机制详解可作为本篇两个特性在后续版本演进中的深入材料。【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表