)
RocksDB 远程压缩恢复机制中的输出目录清理缺陷修复解析OpenAndCompact / allow_resumption【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb导读本文围绕 RocksDB 中一条关于可恢复远程压缩Resumable Remote Compaction的缺陷修复记录展开当远程压缩CompactionService/DB::OpenAndCompact以OpenAndCompactOptions::allow_resumptiontrue发起、但恢复能力因与paranoid_file_checks或输出迭代校验不兼容而在内部被禁用时重试尝试可能失败——因为输出目录在重新开始前没有被清理上一次被中断的尝试遗留的输出文件与重试时复用的文件编号发生冲突。读完本文你将理解可恢复远程压缩的完整工作流、该 Bug 的根因链路、修复方案在源码中的具体落点以及回归测试如何验证这一行为。一、背景远程压缩与可恢复远程压缩RocksDB 允许通过CompactionServiceAPI 将压缩任务卸载到远程 worker。其工作模式为主实例primary选择输入文件序列化CompactionServiceInput发送给 worker远程 worker调用DB::OpenAndCompact()将输出 SST 写入output_directory返回序列化的CompactionServiceResult主实例将结果安装进自己的 LSM 树。DB::OpenAndCompact的声明位于 include/rocksdb/db.h其中重载版本接收OpenAndCompactOptions以支持取消、secondary 重开重试与恢复等高级行为。RocksDB 为此专门提供了直方图统计项OPEN_AND_COMPACT_DB_OPEN_MICROS见 include/rocksdb/statistics.h用于观测 worker 端打开 secondary DB 的耗时。远程压缩任务可能非常耗时动辄处理数百 GB 输入。一旦 worker 崩溃、被抢占或超时整个压缩必须从头重来浪费此前产出的全部输出。为此RocksDB 引入了**检查点-恢复checkpoint-and-resume**机制HISTORY.md 10.8.0 的 New Features 中记录了该实验特性的引入见 HISTORY.md检查点每完成一个输出 SST 文件后worker 将进度持久化到output_directory下的压缩进度文件compaction progress file中记录恢复所需的内部 key 与已完成输出文件的元数据并采用增量编码以保持序列化成本线性恢复后续使用同一output_directory调用OpenAndCompact()时扫描进度文件从最近检查点继续而非从头开始。该机制的整体流程可参考仓库中的官方示意图 resume-flow.svg出自官方博客 Resumable Remote Compaction。二、Bug 的本质allow_resumptiontrue被内部禁用时的输出目录残留修复记录原文如下Fixed a bug where a remote compaction (CompactionService/DB::OpenAndCompact) requested withOpenAndCompactOptions::allow_resumptiontruebut for which resumption was internally disabled (incompatible withparanoid_file_checksor output-iteration verification) could fail a retried attempt, because the output directory was not cleaned before starting fresh and output files left by a previously interrupted attempt collided with the reused file numbers.翻译为要点触发条件调用方设置了OpenAndCompactOptions::allow_resumption true但恢复能力在 worker 内部被禁用禁用原因恢复路径与输出哈希校验output hash verification不兼容具体包括paranoid_file_checkstrue或verify_output_flags中包含输出迭代校验VerifyOutputFlags::kVerifyIteration失败表现重试尝试失败因为重新开始时未清理输出目录上一次中断尝试遗留的输出文件与重试时复用reuse的文件编号发生冲突。2.1allow_resumption的语义契约OpenAndCompactOptions定义于 include/rocksdb/options.h其核心字段包括字段默认值说明cancelednullptr允许取消进行中的压缩的原子标志max_secondary_open_retries2因 CURRENT/MANIFEST 被并发替换导致初次打开失败时最大重试次数0 表示不重试allow_resumptionfalse是否尝试从已持久化的压缩进度恢复关于allow_resumption的契约要点true优先尝试从output_directory中已持久化的进度恢复若恢复无法实现如进度文件损坏或缺失作为 best-effort 回退系统会清理该目录下的相关文件以获得干净状态然后从头开始全新压缩若连全新压缩都无法启动则返回非 OK 状态false直接从零开始全新压缩不保存任何进度关键要求调用前output_directory必须为空否则已有文件可能引发正确性错误。2.2 内部禁用逻辑在 worker 端的核心入口DBImplSecondary::CompactWithoutInstallation见 db/db_impl/db_impl_secondary.cc中是否真正启用恢复由以下逻辑决定bool output_hash_verification_enabled mutable_cf_options.paranoid_file_checks || !!(mutable_cf_options.verify_output_flags VerifyOutputFlags::kVerifyIteration); bool allow_resumption options.allow_resumption !output_hash_verification_enabled; if (options.allow_resumption output_hash_verification_enabled) { ROCKS_LOG_WARN(immutable_db_options_.info_log, Resume compaction configured but disabled due to incompatibility with output hash verification (paranoid_file_checkstrue or verify_output_flags ...); }即调用方请求恢复options.allow_resumption true但一旦paranoid_file_checks或输出迭代校验生效allow_resumption就被置为false同时记录一条 WARN 日志说明禁用原因。恢复路径之所以与输出哈希校验不兼容是因为恢复后重放输出文件的哈希校验语义在中断边界处难以保证与未中断压缩完全一致官方选择在该组合下直接禁用恢复。2.3 冲突的根因文件编号复用 × 未清理的残留文件冲突发生的机理主实例与 worker 通过序列化的CompactionServiceInput协商worker 新建输出文件时复用了与输入文件编号相关的编号序列第一次尝试被中断如取消、崩溃后output_directory中遗留了已写出的部分输出 SST 文件重试时由于allow_resumption已被内部禁用走全新压缩路径而全新压缩路径契约要求output_directory为空参见allow_resumptionfalse的 CRITICAL REQUIREMENT 说明但此时调用方并不知道恢复已被禁用仍按allow_resumptiontrue的约定不预先清空目录恢复路径本应自己接管目录状态全新压缩复用了与残留文件相同的文件编号试图通过NewWritableFile创建同名文件时发生冲突。在no-reopen语义的文件系统如崩溃注入测试中的文件系统实现下这种冲突会表现为创建文件时违反 no-reopen-for-write 契约而直接失败正如回归测试注释中所描述。三、修复方案在启动全新压缩前先清理残留修复的落点在DBImplSecondary::InitializeCompactionWorkspace见 db/db_impl/db_impl_secondary.cc。该函数统一负责压缩工作区的初始化区分真正启用恢复与仅请求了恢复但被禁用两种情况Status DBImplSecondary::InitializeCompactionWorkspace( bool allow_resumption, bool resumption_requested, std::unique_ptrFSDirectory* output_dir, std::unique_ptrlog::Writer* compaction_progress_writer) { // 创建输出目录如不存在 Status s CreateAndNewDirectory(fs_.get(), secondary_path_, output_dir); if (!s.ok()) { return s; } if (allow_resumption) { s PrepareCompactionProgressState(); if (!s.ok()) { return s; } return FinalizeCompactionProgressWriter(compaction_progress_writer); } if (resumption_requested) { // 恢复被请求但被内部禁用与输出哈希校验不兼容。 // 调用方因此未清空 output_directory它期望恢复路径自己管理该状态 // 所以在此履行 allow_resumptiontrue 的回退契约 // 清理遗留的进度文件与输出文件从干净目录开始全新压缩。 // 否则上一次被中断尝试遗留的输出文件会与全新压缩复用的文件编号冲突。 CompactionProgressFilesScan scan_result; s ScanCompactionProgressFiles(scan_result); if (!s.ok()) { return s; } s CleanupOldAndTemporaryCompactionProgressFiles( /*preserve_latest*/false, scan_result); if (!s.ok()) { return s; } s HandleInvalidOrNoCompactionProgress( /*compaction_progress_file_path*/std::nullopt, scan_result); } return s; }关键点allow_resumption true未被禁用走PrepareCompactionProgressState()扫描目录、判定是否恢复、清理旧/临时进度文件与多余输出文件详见下文resumption_requested true但allow_resumption false被禁用这是本 Bug 修复新增的关键分支。由于调用方按契约没有清空目录worker 必须在此主动完成清理先扫描进度文件删除全部旧进度与临时进度文件preserve_latestfalse再通过HandleInvalidOrNoCompactionProgress清理物理输出文件从而保证全新压缩从干净目录开始若清理过程中任一步失败则返回错误状态交由上层处理。四、恢复路径内部的目录状态管理为了完整理解修复的上下文有必要了解恢复路径PrepareCompactionProgressState见 db/db_impl/db_impl_secondary.cc是如何管理目录状态的。其前置条件与后置条件在注释中明确给出进入函数前的文件系统状态可能同时存在最新进度文件来自最近一次压缩尝试较旧的进度文件上次InitializeCompactionWorkspace调用中途崩溃遗留临时进度文件同上0 个或多个压缩输出文件。后置条件分两种情形最新进度文件存在且可解析、包含有效进度仅保留最新进度文件删除所有旧/临时进度文件保留进度中登记的所有输出文件删除额外输出文件崩溃在持久化进度之前遗留的。结果可从保存的进度恢复无最新进度文件或解析失败、进度无效删除全部进度文件最新 旧 临时与全部输出文件。结果尽管allow_resumptiontrue因无有效进度可恢复以干净状态开始全新压缩。错误处理若任一后置条件无法达成函数返回错误状态文件系统可能处于部分修改状态此时调用方应手动清理secondary_path_后重试后续对干净目录的OpenAndCompact()调用将等效于全新压缩。该函数内部按步骤执行STEP 1单次目录扫描ScanCompactionProgressFiles同时收集进度文件与表文件信息存于CompactionProgressFilesScan该结构在 db/db_impl/db_impl_secondary.h 定义持有最新进度文件名、时间戳及待删除的旧文件列表STEP 2依据是否存在最新进度文件决定should_resumeSTEP 3执行清理——可恢复时保留最新、删除旧/临时进度文件不可恢复时全部删除STEP 4可恢复则调用LoadCompactionProgressAndCleanupExtraOutputFiles加载进度并清理额外输出文件加载失败则回退到HandleInvalidOrNoCompactionProgress清理全部后全新开始。相关辅助函数在 db/db_impl/db_impl_secondary.h 中声明包括ScanCompactionProgressFiles、DeleteCompactionProgressFiles、CleanupOldAndTemporaryCompactionProgressFiles、ParseCompactionProgressFile、HandleInvalidOrNoCompactionProgress、CleanupPhysicalCompactionOutputFiles、FinalizeCompactionProgressWriter等共同构成完整的状态机。其中HandleInvalidOrNoCompactionProgress见 db/db_impl/db_impl_secondary.cc执行两项关键清理删除无效的进度文件若存在调用CleanupPhysicalCompactionOutputFiles(/*preserve_tracked_files*/false, scan_result)删除物理输出文件。这正是修复分支中被复用的清理入口确保请求了恢复但被禁用的场景也能获得与恢复回退到全新压缩场景一致的干净目录。另外CalculateResumedCompactionBytes见 db/db_impl/db_impl_secondary.cc用于汇总各 subcompaction 在输出层与近端输出层已登记输出文件的总字节数为REMOTE_COMPACT_RESUMED_BYTES统计提供数据使用户能观测恢复节省的工作量。五、回归测试CleansOutputDirWhenResumptionDisabled修复由专门的回归测试覆盖CompactionServiceTest.CleansOutputDirWhenResumptionDisabled见 db/compaction/compaction_service_test.cc。测试要点测试夹具自定义LeftoverOutputCompactionServicedb/compaction/compaction_service_test.cc继承MyTestCompactionService其Wait()中先向输出目录写入一个名为leftover_file_的 SST 残留文件内容 leftover再以allow_resumptiontrue调用DB::OpenAndCompact并记录残留文件是否被清理leftover_cleaned标志禁用恢复的手段在主实例 options 中设置paranoid_file_checks true使 worker 端output_hash_verification_enabled为真从而内部禁用恢复场景构造disable_auto_compactions true两次 Flush 产生两个有重叠的文件再通过CompactRange触发远程压缩断言cs-leftover_cleaned()为真——全新压缩启动前残留输出已被清理各 key 的读取结果正确v1、v2、v3new——压缩结果与未中断场景一致。测试注释明确指出修复前遗留文件被保留在 no-reopen 文件系统如崩溃测试所用下全新压缩的NewWritableFile会因违反 no-reopen-for-write 契约而失败。测试中use_session_tmp_dir_for_remote_compaction true表明输出写入会话临时目录dbname/session_tmp/job_id该选项由DBOptions::use_session_tmp_dir_for_remote_compaction控制见 include/rocksdb/options.h启用时DB::Open()会创建目录并 best-effort 清理上一会话遗留内容。六、对使用者的启示与最佳实践结合修复记录、OpenAndCompactOptions契约与源码实现可总结如下实践要点不要把allow_resumption当作绝对开关恢复能力可能被paranoid_file_checks或输出迭代校验内部禁用。若你的 worker 开启了这些校验allow_resumptiontrue的实际效果是全新压缩 自动清理目录而不是恢复保持output_directory跨重试一致恢复依赖同一目录每次重试调用同一目录时系统会自动检测并恢复或在恢复不可用时自动清理后全新开始allow_resumptionfalse时务必保证目录为空该路径不做任何清理目录中的任何残留文件包括上次运行留下的恢复状态或输出文件都可能引发正确性错误监听日志与统计worker 在禁用恢复时会记录 WARN 日志Resume compaction configured but disabled...REMOTE_COMPACT_RESUMED_BYTES统计可量化恢复节省的字节数OPEN_AND_COMPACT_DB_OPEN_MICROS直方图可用于观测 worker 打开 secondary DB 的开销关注文件编号复用风险远程压缩重试依赖输出文件编号的复用因此启动前目录干净不是可选优化而是正确性前提——这正是本次修复要保证的契约。七、小结本修复解决的是可恢复远程压缩在请求恢复但被内部禁用这一边界组合下的重试失败问题。其核心价值在于无论恢复是否真正启用worker 都必须履行allow_resumptiontrue的回退契约——要么从进度恢复要么将输出目录清理干净后全新开始绝不允许残留输出文件与复用文件编号发生冲突。修复通过InitializeCompactionWorkspace中新增的resumption_requested分支完成清理并由CleansOutputDirWhenResumptionDisabled回归测试固化行为为基于CompactionService构建远程压缩基础设施的开发者消除了一个隐蔽的重试故障源。【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考