
一块盘写入时是好的三个月后读出来才发现中间有几位变成了别的。这种损坏在写入路径上永远不会被发现因为写进去的那个字节就是坏的那个。能发现它的只有一条路在没人读写的时候把数据重新读一遍、算一遍校验。RustFS 的后台扫描器干的就是这件事而它的默认节奏是把这件事压到 30 天才做一次。这个数字容易被读成「RustFS 一个月才检查一次数据」。env 参考页给出的措辞比这更具体它描述的是周期长度跟一次巡检跑多少没关系。也别把它读成承诺2592000 是期望的触发间隔实际一轮要多久取决于后面讲的预算和节流经常比这个值长得多。一个周期里有浅扫和深扫两件事扫描器的行为分两层。常规周期负责遍历对象、更新元数据缓存、顺手统计一些异常指标这一层是廉价的跑得很勤默认周期由RUSTFS_SCANNER_CYCLE控制文档举的例子是 3600 秒。深扫是另一层它要逐块读数据并做位腐校验代价高一个数量级由RUSTFS_SCANNER_BITROT_CYCLE_SECS控制。这两个变量是分开的调一个不影响另一个。运维上真正重要的组合关系是常规周期决定元数据多久刷新一次深扫周期决定静默损坏最多能在盘上躺多久。前者快一点没有代价后者快一点直接反映成磁盘读带宽的占用。RUSTFS_SCANNER_BITROT_CYCLE_SECS的默认值是2592000也就是 30 天。文档对几个特殊值有明确说明0、true、on、yes都会让每一个周期都执行深扫false、off、no、disabled会关掉。把RUSTFS_SCANNER_BITROT_CYCLE_SECS设成0的后果和按默认周期走完全不同每一轮都会做全量位腐校验磁盘上会出现一段持续的高读负载。这一轮到底查到多少还有两个变量在中间打折。RUSTFS_HEAL_OBJECT_SELECT_PROB默认1024文档对它的定义是扫描器提交的修复检查按这个分母抽样大约每 N 个对象挑一个做低优先级的检查RUSTFS_SCANNER_DEEP_VERIFY_COOLDOWN_SECS默认60意味着 60 秒之内被改过的对象在当前这一轮会跳过深校验。所以估覆盖率的时候别按「一轮等于全量走一遍」算40 GB 的集群和 4 PB 的集群差出来的不是时间是真实的校验覆盖面。一个周期跑多久有预算超了就中断真正让 30 天这个数字站得住的是后面那几个预算变量。文档给RUSTFS_SCANNER_CYCLE_MAX_DURATION_SECS的默认值是1800含义是单个周期最多跑 1800 秒到点中断剩下的留到下一轮。另有RUSTFS_SCANNER_CYCLE_MAX_OBJECTS和RUSTFS_SCANNER_CYCLE_MAX_DIRECTORIES两个上限各自默认0文档写明0表示不限制。这组设计的意图很清楚扫描器不能把磁盘读满。复制一个 PB 级集群的位腐校验按时长算要几十天也不奇怪如果允许一轮跑完它会和线上读写抢带宽。设预算的意思是把工作量切成小块长时间慢慢磨宁可让周期往后推也不要在一次运行里把 IO 打满。RUSTFS_SCANNER_BITROT_CYCLE_SECS604800 RUSTFS_SCANNER_CYCLE_MAX_DURATION_SECS2700反过来如果集群规模小、磁盘是 NVMe、业务有明确的低峰期把预算放宽到 2 到 4 小时是合理的能让深扫在一夜之间跑完。评估的时候按「总对象数除以单轮预算」算一下要几轮比拍脑袋定时长可靠。这个算式的结果如果大于一就要当心另一件事预算频繁触顶意味着扫描器长期停在「每轮只推进一点」的状态。一轮走不完整个命名空间下一轮又从上次停下的地方接着走几轮下来被反复覆盖的还是同一批目录后面的目录一直排不上。于是坏得早的那批对象始终没被人碰过而监控上一切正常读写成功率也是绿的。位腐就是这么攒出来的。判断这件事有没有发生看状态接口比看配置值靠谱。/v3/scanner/status里有一组字段可以直接用来判断字段能看出什么metrics.pacing_pressure.last_cycle_budget_limited上一轮是被预算截断的last_cycle_partial_reason/last_cycle_partial_source卡在哪一项预算、归哪个来源metrics.cycle_last_progress_age距上一次有进展过了多久长时间不下降就是追不上metrics.cycle_timeout_total周期内任务被打断的次数metrics.maintenance_control.*.partial_cycles各来源各自贡献了多少个部分周期metrics.pacing_pressure里还有last_cycle_throttle_sleep_ratio、last_cycle_total_pause_ratio两个比值都是相对上一轮时长的比例看着涨说明扫描器自己压得厉害。这几个数连续几轮都不动比把 30 天这个数字再调大有用得多。空闲模式下它会自己减速RUSTFS_SCANNER_IDLE_MODE默认是true文档的说法是开启后扫描器会自己限流设成false则全速跑。这只是一句概括拆开是两件事准确理解它才能判断自己该不该关一是那套预设睡眠二是一条前台读退避地板。后者才是真正的减速依据它统计的是并发前台读的数量每有一个并发的 GetObject 或流式读扫描器就叠加一段退避单次最多 250 毫秒。它数的是存储自己看到的并发读请求不管这些请求来自哪个应用、走没走网关。这么拆开看有几个后果值得注意。业务流量全走一层代理或网关时落到存储上的并发读可能比业务侧 QPS 低一个数量级退避地板几乎不起作用扫描器基本按预设速度跑反过来直接暴露给公网、请求量忽高忽低的部署地板会在业务高峰被反复顶上去扫描进度反而变慢。想判断自己属于哪一种看/v3/scanner/status里metrics.pacing_pressure的active_scans与queued_scans后者持续堆积说明扫描队列已经跟不上此时先降并发比延长预算有效。背后另外三个变量在管具体数值RUSTFS_SCANNER_SPEED的默认值是default可选fastest、fast、default、slow、slowest它一起控制睡眠系数、最大睡眠时间和周期间隔RUSTFS_SCANNER_DELAY覆盖睡眠倍率文档例子是30.0RUSTFS_SCANNER_MAX_WAIT_SECS覆盖最大睡眠秒数RUSTFS_SCANNER_CYCLE覆盖周期间隔。调这几个之前先看懂一件事它们互相覆盖。改了RUSTFS_SCANNER_SPEED之后再设RUSTFS_SCANNER_DELAY前者里对应的值就被后者顶掉了两个都写反而容易让人误判当前生效的是哪个。只改一个、把另一个留空排查时少一层干扰。另外两个并发阀门也值得记一下RUSTFS_SCANNER_MAX_CONCURRENT_SET_SCANS和RUSTFS_SCANNER_MAX_CONCURRENT_DISK_SCANS默认都是4文档写明设为0时各自回到按拓扑或盘数自动决定的并发度。真正吃紧的时候先降并发比延长预算更有效因为并发直接决定单块盘上的读队列深度。扫描器会顺手报三样东西深扫换来的回报是三个告警阈值它们都是扫描器在遍历时顺手统计的不需要额外任务变量默认值触发条件RUSTFS_SCANNER_ALERT_EXCESS_VERSIONS100单个对象版本数超过 100RUSTFS_SCANNER_ALERT_EXCESS_VERSION_SIZE1099511627776版本累计体积超过 1 TiBRUSTFS_SCANNER_ALERT_EXCESS_FOLDERS65538单目录直接子文件夹数超过 65538这三个阈值指向的问题通常是同一个写入侧没做生命周期管理。版本数超标说明删除策略没跟上文件夹数超标说明上层应用在用「一个前缀当目录用」的方式造出海量子键。扫描器只是在路过时看到了它不会替你清理。还有一个容易被忽略的细节。RUSTFS_SCANNER_START_DELAY_SECS默认未设置控制首次扫描前的等待旧名字RUSTFS_DATA_SCANNER_START_DELAY_SECS仍然可用但会打警告规范名字优先级更高两个都写在配置里时生效的是后者。rc admin scanner status rustfs这个命令调的是/v3/scanner/status需要带ServerInfoAdminAction权限的管理身份。它返回里每个生效值都标了来源是env、config、scanner_compat_config还是default一眼能看出哪几项是显式配置、哪几项还在按默认值跑。修复那边有几个并发阀门扫描器发现降级对象之后接下来的活交给修复器。它自己有一组默认值RUSTFS_HEAL_AUTO_HEAL_ENABLE为true说明自动后台修复是开着的RUSTFS_HEAL_QUEUE_SIZE是10000候选队列容量RUSTFS_HEAL_INTERVAL_SECS是10修复调度器的运行间隔RUSTFS_HEAL_TASK_TIMEOUT_SECS是300单个修复任务的超时RUSTFS_HEAL_MAX_CONCURRENT_HEALS是4RUSTFS_HEAL_MAX_CONCURRENT_PER_SET是1。最后两个的差别是关键。全局并发 4 允许同时修四个任务但每纠删集并发 1 意味着同一个纠删集内的修复任务排队。这两条规则合在一起的含义是一次坏两块盘时修复不会自我加速到相互抢资源代价是修复时间按坏掉的集合数拉长。真正让人卡住的场景是同一个纠删集里多片同时损坏每片都要单独排一次队修完一片才轮到下一片整体耗时会按集合里坏掉的数量成倍翻。盘坏得多的集群值得评估的是放宽第二个还是先把坏盘换掉。队列长度要盯着看。rc admin heal status rustfs加--json能拿到healQueueLength和healActiveTasks两个计数队列一直有积压、活跃任务却不多多半是 per-set 那条规则在串行不是修不动。官方文档也提醒过一句队列长度为零并不能证明离线盘已经更换、也不能证明每个对象都可恢复要跟存储拓扑和就绪状态一起看。任务超时 300 秒这条也要注意。修复一个超大对象可能超过 300 秒超时的任务会被丢弃重新入队表现是队列一直在跑但进度不前进。看日志里反复出现的同一个对象第一反应应该是去核对它的实际大小。如果确认是对象的体积让单次修复稳定超过 300 秒直接把RUSTFS_HEAL_TASK_TIMEOUT_SECS调大比反复观察更省事代价只是修复期间多占一段时间真正要警惕的是调大之后依然反复超时那说明修复带宽本身不够得看盘的读速而不是继续抬超时。读路径本身也在修别把希望全押在扫描器上上面这一整套节奏说的是后台扫描器。处理读请求的那条路还另有一套官方文档把集群修复的来源分成五类autoHeal、scanner、readRepair、internal、admin。其中readRepair的说法是「读取修复路径可以提交在处理请求时发现的工作」也就是说业务刚好读到一个对象、而这个对象的分片已经坏了读请求本身就会把它记下来并提交修复跟扫描器跑到没跑到这个对象无关。这条路径的意义是它不守 30 天。一个刚坏的位腐对象如果业务恰好要读它修复提交可能发生在几分钟内反过来如果它写在一份写进去就没人再碰的数据里可能真的要等扫描器走到。所以冷数据上出问题的风险并不对称越是没人读的数据越只能等后台扫描越是频繁读的数据越容易被读路径兜住。监控上别把两条路混着看。修复队列里的条目是可以按来源区分的healOperations状态用那几个来源名分开统计排队中、活跃和重试中的工作。如果队列里scanner来源积压明显而readRepair数量一直很低说明当前能自动发现的损坏全押在后台扫描上此时要么缩短深扫周期要么把预算放宽让扫描器至少能追上新增数据。把一个月的沉默切成可解释的几段把上面这些串起来看30 天这个数字背后是一组默认值凑出来的结果深扫每 30 天一轮单轮最多 30 分钟并发按 4 走发现的问题进 10000 容量的队列慢慢修。这个组合对大多数部署是安全的对数据量大、带宽紧张的部署则偏保守。它给的是节奏而不是完成保证实际推进到哪一步得靠状态接口去看。判断自己需不需要动它可以按这三个问题过一遍集群的有效数据有多少、单轮 1800 秒能覆盖多少比例、剩下的需要几轮才能磨完。举例来说一块 NVMe 盘在纯顺序读下的带宽远大于业务高峰期的预留量30 分钟能扫完的量对一个中等集群来说可能已经超过全部数据换成一堆 7200 转机械盘同样 30 分钟只能覆盖一个很小的片段这时把预算调到数小时反而更实际。另一个角度是看数据本身的价值分布。真正需要位腐兜底的是那些写进去之后再也不碰、也没有第二份副本的数据。批处理产出但下游还有暂存的数据坏了几位的代价可能低于多花一个月扫一遍的成本。把这两类分开深扫周期就能按数据分层来定而不是整个集群统一取一个值。扫描器本身还有一件事要注意它的默认值是按「不打扰业务」设计的所以在多数生产部署里它是隐形的。正因为它隐形一旦某次调整把RUSTFS_SCANNER_IDLE_MODE设成了false或者把周期预算放宽了而没人记录业务侧会先感觉到延迟波动反过来怀疑是不是硬件出了问题。这类变更写进变更单比调参数本身更值得花力气。要改的话按影响面从小到大是这条顺序先调单轮预算和并发把扫描对业务带宽的影响压到可接受再决定深扫周期要不要从 30 天缩短到每周最后才动自动修复的并发。反过来做的话一上来把深扫周期改成 0等于给每台机器排了一段持续的高读负载业务侧会先感觉到延迟上来。