新闻详情

新闻详情

首页 / 资讯中心 / 详情

Rivet Actors 故障注入测试套件实践指南:depot-client fault-injection 的工程契约与实现剖析

发布时间:2026/9/18 22:03:49来源:尧图网络
Rivet Actors 故障注入测试套件实践指南:depot-client fault-injection 的工程契约与实现剖析
Rivet Actors 故障注入测试套件实践指南depot-client fault-injection 的工程契约与实现剖析【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors导读本文基于 engine/packages/depot-client/tests/inline/fault/CLAUDE.md 中的测试开发契约深入解析 Rivet Actors 项目中面向 SQLite-on-Depot 存储层的故障注入测试套件。文章逐条拆解该套件的 24 条工程规则并结合 scenario.rs、simple.rs、growth.rs 等源码说明每条规则背后的实现机制。读完本文你将掌握如何通过真实 SQLite VFS 与DirectDepotTransport注入 depot 语义故障fail / pause / delay / drop、如何用原生 SQLite oracle 与 depot 不变式扫描双通道验证、如何编写重载reload与强制压缩forced compaction故障场景以及test-faults特性门控为何必须与生产构建隔离。一、套件定位为状态化 Actor 的 SQLite 存储层建立故障证明Rivet Actors 以 SQLite 作为 Actor 状态存储而 SQLite 的页面读写由 depot分布式数据库通过 VFS 层提供。这意味着commit 是否落盘、页面读取是否命中正确的 shard 快照、压缩与回收流程是否破坏数据都是分布式存储语义问题而不仅仅是 SQLite 本地问题。depot-client的 fault-injection 套件位于tests/inline/fault/专门回答一个问题在 depot 层出现各类故障时Actor 的 SQLite 数据库是否仍然保持一致性与完整性。该套件不是一个孤立的测试文件而是一整套以FaultScenario为骨架的测试框架mod.rs 将套件拆分为growth、oracle、scenario、simple、verify、workload六个模块并导出FaultProfile、FaultReplayPhase、FaultScenario与LogicalOpscenario.rs 是核心运行时负责装配数据库、执行 setup → 严格工作负载 → 注入故障 → 验证 → 关闭的完整生命周期simple.rs 与 growth.rs 分别承载小型语义故障与跨 shard/fold 边界的增长型故障场景。运行方式与并行约束故障场景通过 cargo 测试运行。注意 scenario.rs 中有一个全局运行锁let Some(_run_guard) FAULT_SCENARIO_RUN_LOCK.try_lock() else { bail!( depot-client fault scenarios cannot run in parallel; rerun with cargo test -p depot-client fault -- --test-threads1 ); };原因是故障场景会安装进程级的工作流故障钩子用于压缩工作流随后启动并关闭工作流 worker若在同一测试进程中并发运行多个场景一个场景可能观察到另一个场景的 worker/debug 生命周期从而干扰自身强制压缩的 ack 判定。因此运行该套件必须使用cargo test -p depot-client fault -- --test-threads1二、传输层铁律只走真实 SQLite VFS DirectDepotTransportCLAUDE.md 前三条规定了测试必须经过的真实路径Fault-injection tests must exercise the real SQLite VFS through the explicit DirectDepotTransport.Do not add mock, envoy, or alternate transport variants to this fault suite.DirectDepotTransport must fail if reads require mirror fallback or seeded VFS cache state.这三条规则的目的很明确故障注入必须打在真实生产路径上。Actor 引擎在生产中通过 VFS 从 depot 读取页面、写入 commit若测试改用 mock 传输或 envoy 传输变体就测不到真实的页面提供逻辑shard 命中、delta 拼接、cold ref 解析等。DirectDepotTransport直接挂在DirectStorage之上而DirectStorage持有一个真实的universaldb::Database见 vfs_support.rs从而在进程内模拟 depot 而保留全部读写语义。规则的配套实现出现在 scenario.rs 的open_fault_databasefn open_fault_database( handle: tokio::runtime::Handle, storage: ArcDirectStorage, actor_id: str, ) - ResultNativeDatabase { let mut config VfsConfig::default(); config.assert_batch_atomic false; let transport Arc::new(DirectDepotTransport::new(storage)); let initial_main_page handle .block_on(fetch_initial_main_page_for_registration(transport.clone(), actor_id)) .map_err(anyhow::Error::msg)?; let vfs SqliteVfs::register_with_transport_and_initial_page( super::super::next_test_name(sqlite-fault-vfs), transport, actor_id.to_string(), handle.clone(), config, initial_main_page, None, )?; open_database(vfs, actor_id).map_err(anyhow::Error::msg) }每个故障场景都会注册一个独立的真实SqliteVfs并用fetch_initial_main_page_for_registration预取初始主页面。注意config.assert_batch_atomic false这是为了允许原子批处理断言被关闭从而能够精确地在单个页面操作上注入故障。第三条规则的严格性验证禁止 mirror 兜底第 3 条规则要求在严格工作负载阶段读取不得退化为 mirror 兜底也不得依赖预置的 VFS 缓存状态。FaultScenarioCtx::assert_strict_mirror_counters_unchangedscenario.rs实现了这条约束if after.stats.mirror_reads ! before.stats.mirror_reads { bail!(strict workload used mirror reads: before{}, after{}, ...); } if after.stats.mirror_seeds ! before.stats.mirror_seeds { bail!(strict workload used mirror seeds: before{}, after{}, ...); }而enter_strict_workload_modescenario.rs会在进入严格模式前关闭数据库、驱逐actor_db缓存再重新打开确保严格工作负载从干净状态开始。三、故障语义边界只注入 depot 语义故障规则 6 与 7 划定了故障类型边界Faults must be depot semantic faults: fail, pause, delay, or drop depot-owned artifacts.Do not add arbitrary byte corruption, UDB driver faults, Gasoline correctness tests, or global Rivet fault injection here.即本套件只允许四类故障原语——fail让操作报错、pause暂停工作流、delay延迟返回、drop丢弃 depot 持有的产物且必须作用于 depot 持有的产物页面读取、delta 写入、shard 发布、PIDX 清理、回收等。以下内容被明确排除在套件之外任意字节损坏这属于存储介质损坏测试而不是 depot 语义故障UDBuniversaldb驱动层故障应在上游 UDB 的测试中覆盖Gasoline分布式事务框架正确性测试有独立归属全局 Rivet 故障注入粒度太粗无法判定是哪个组件吞掉了故障。故障注入的锚点定义在depot::fault模块engine/packages/depot/src/fault/controller.rssimple.rs中可以看到实际使用faults .at(DepotFaultPoint::Read(ReadFaultPoint::BeforeReturnPages)) .optional() .once() .delay(Duration::from_millis(1))?;以及 commit 故障点faults .at(DepotFaultPoint::Commit(CommitFaultPoint::BeforeDeltaWrites)) .once() .fail(simple failed commit)?;从simple.rs的用例清单可以归纳出套件覆盖的故障点族ReadFaultPoint::BeforeReturnPages—— 页面返回前读取故障CommitFaultPoint::BeforeDeltaWrites—— delta 写入前的提交故障对应“pre-commit failure”CommitFaultPoint::AfterUdbCommit—— UDB 提交之后、报告成功之前的故障对应“ambiguous post-commit”HotCompactionFaultPoint::InstallAfterShardPublishBeforePidxClear—— 热压缩发布 shard 后、清理 PIDX 前的故障workflow-only 边界。四、双通道验证原生 SQLite oracle depot 不变式扫描规则 8 与 9 定义了验证策略Tests must validate with native SQLite oracle comparison and depot invariant scanning.Ambiguous post-commit tests must classify old or new oracle state after reload instead of assuming the new state committed.4.1 原生 SQLite oracle逐字节比较的“黄金数据库”NativeSqliteOracle 在内存中维护一个“未注入任何故障”的原生 SQLite 数据库与被测的 depot-backed 数据库执行完全相同的逻辑操作序列。其核心是canonical_dumporacle.rs按稳定顺序schema 按 type/name 排序、行按所有列排序导出两张库的全部 schema 与数据再做字符串级比较。BLOB 以十六进制、浮点以位模式、文本经过转义保证比较严格且可复现。更关键的是 oracle 理解“提交语义”。OracleCommitSemantics 有三个取值pub(crate) enum OracleCommitSemantics { PreCommitFailure, // 操作确定未提交oracle 不应用 Success, // 操作确定提交oracle 应用 AmbiguousPostCommit, // 结果不确定oracle 同时记录旧状态与新状态 }apply_logical_oporacle.rs依据语义决定是否把操作应用到 oracle 数据库PreCommitFailure直接丢弃操作真实库该操作必然失败Success应用操作AmbiguousPostCommit调用snapshot_ambiguous_oporacle.rs先把 oracle 当前状态 dump 为old_dump再在sqlite3_backup克隆出的临时库上应用操作得到new_dump两者都挂起等待验证阶段分类。4.2 模糊提交的分类Old / New / Invalidverify_matchesoracle.rs在验证阶段对挂起的模糊操作进行分类if let Some(pending) self.pending_ambiguous.take() { if actual pending.old_dump { return Ok(OracleVerification::Ambiguous(AmbiguousOracleOutcome::Old)); } if actual pending.new_dump { pending.op.apply(self.db)?; return Ok(OracleVerification::Ambiguous(AmbiguousOracleOutcome::New)); } bail!(native sqlite ambiguous oracle mismatch ...); }对应 AmbiguousOracleOutcome 的Old/New/Invalid三态。这正体现规则 9 的精神模糊提交后绝不能假设新状态已提交而必须把“仍是旧状态”与“已是新状态”都视为合法只有两种状态都不匹配即数据损坏才算失败。simple.rs 的simple_ambiguous_post_commit_fault_classifies_durable_outcome是该机制的端到端用例在AfterUdbCommit故障点注入一次 fail然后重载数据库验证阶段断言ambiguous_oracle_outcome落在Old | New并且match outcome { AmbiguousOracleOutcome::Old assert_kv_rows(ctx, Vec::new()).await?, AmbiguousOracleOutcome::New assert_kv_rows(ctx, vec![(ambiguous, ABCD)]).await?, AmbiguousOracleOutcome::Invalid panic!(...), }即两种提交结果都接受但“invalid”两种都不匹配必须失败。4.3 depot 不变式扫描从存储层证明结构合法oracle 证明“SQL 数据正确”不变式扫描证明“depot 内部结构合法”。DepotInvariantScanner 在universaldb::Database上以事务方式扫描一个分支的全部键空间InvariantScan::run 依次执行七类检查检查项作用check_database_pointer数据库指针唯一且与分支解析一致check_branch_record分支记录存在、状态为 Live、父记录存在check_live_headhead 行存在、可解码、head commit 行存在check_branch_rowscommit 行连续、delta 分块完整、hot shard 分块完整、cold ref 指向存在的 commitcheck_pidx每个 PIDX 页在库大小范围内且存在 backing 数据check_compaction_metadata压缩根水位不越过 head、dirty 标记、retired cold object 键与删除围栏合法、PITR 区间覆盖存在的 commitcheck_restore_points/check_history_pins恢复点与历史 pin 的 versionstamp 都有对应 VTX 行例如check_pidxverify.rs会校验if pgno 0 || pgno db_size_pages { self.violate(format!(PIDX page {pgno} was outside database size {db_size_pages})); } if !self.page_has_backing(pgno, owner_txid, rows) { self.violate(format!(PIDX page {pgno} pointed at missing backing txid {owner_txid})); }page_has_backingverify.rs会在 delta、hot shard、cold ref 三个来源中寻找该页的实际字节只有三处都找不到才算违反不变式。verify.rs 自身还带有自测用例depot_invariant_scanner_detects_missing_head_commit、depot_invariant_scanner_detects_broken_pidx_backing验证扫描器确实能发现被刻意破坏的结构。4.4 验证顺序规则先断言故障再跑不变式规则 10 强调Depot invariant verification must use a non-faulting cold-tier view and must run after expected workload faults are asserted.即不变式扫描必须以“不带故障的冷层视图”进行且必须排在预期工作负载故障已断言之后。这体现在FaultScenario::runscenario.rs的执行顺序中setup阶段进入严格工作负载模式快照 mirror 计数器、捕获故障前 branch head注册故障faults执行工作负载断言严格模式 mirror 计数器未变化fault_controller().assert_expected_fired()—— 断言所有预期故障均已触发mark_workload_faults_complete()记录工作负载阶段故障事件数量verify阶段oracle 对比、不变式扫描。这样不变式扫描看到的是一个“故障已按预期发生且被断言过”的最终状态避免把故障副作用误判为数据损坏。五、重放断言精确到故障点与阶段而非边界计数规则 11Replay assertions should check exact fault points and workload/verification phase, not only boundary counts.故障控制器维护一份重放日志。FaultScenarioCtx::replay_recordscenario.rs会把日志中的每个事件标注为 FaultReplayPhase 的Workload或Verification.map(|(index, event)| FaultScenarioReplayEvent { event, phase: if index workload_fault_event_count { FaultReplayPhase::Workload } else { FaultReplayPhase::Verification }, })workload_fault_event_count由mark_workload_faults_completescenario.rs在工作负载阶段结束时打点之后验证阶段产生的事件被归入Verification。测试断言如simple.rs中的assert_faults会同时校验event.point—— 精确的故障点如Read(ReadFaultPoint::BeforeReturnPages)event.boundary—— 故障边界ReadOnly、PreDurableCommit、AmbiguousAfterDurableCommit、WorkflowOnlyevent.phase—— 事件发生在 Workload 还是 Verification 阶段。单看“故障触发次数”无法区分故障发生在提交前还是提交后、发生在读路径还是写路径因此必须断言到故障点 边界 阶段三个维度。六、数据库重载测试的纪律规则 1214 针对 reload 场景提出三条纪律Database reload tests must reopen with fresh VFS state and a fresh depotDbhandle.Happy-path reload smoke tests must use non-failing read faults; failing read faults during initial page fetch should surface as reload errors.Register broad read faults immediately before the target read or reload so earlier strict workload reads cannot consume them.6.1 全新 VFS 状态 全新 Db 句柄reload_databasescenario.rs是 reload 的标准实现pub(crate) async fn reload_database(self) - Result() { self.close_database(); self.inner.storage.evict_actor_db(self.inner.actor_id).await; self.open_database_blocking()?; Ok(()) }它先关闭当前NativeDatabase然后通过evict_actor_db驱逐 depot 侧缓存的Db句柄对应DirectStorage.actor_dbs中的条目最后重新打开。这模拟了引擎 pod 重启后 Actor 重新冷启动读取数据库的真实路径确保新 VFS 不会复用任何预置的页面缓存。6.2 happy-path reload 只能用非致命读故障规则 13 的语义是一个“冒烟级”的 reload 测试如果只是验证正常路径能完成重载那么注册的读故障必须是非致命non-failing的如delay不能是fail。因为 reload 的第一步就是通过 VFS 抓取初始主页面page 1如果此时读故障是 fail那么故障会以“reload 错误”的形式暴露而不是以空数据库的形式暴露。simple.rs 的strict_reload_read_fault_returns_reload_error_instead_of_empty_database专门验证了这条路径在 reload 前注册一次Read(ReadFaultPoint::BeforeReturnPages)的fail故障断言 reload 返回的错误同时包含sqlite initial page fetch failed与注入的故障消息strict reload read failure且不得出现no such table那意味着数据库被当成了全新空库let message format!({err:#}); assert!(message.contains(sqlite initial page fetch failed)); assert!(message.contains(strict reload read failure)); assert!(!message.contains(no such table));而对照的 happy-path 冒烟测试fault_scenario_runs_setup_workload_reload_and_verifysimple.rs使用的是delay(Duration::from_millis(1))且optional().once()因此重载后数据库仍能读到alpha与beta两条记录。6.3 宽读故障的注册时机规则 14 要求宽泛的读故障必须在目标读取或 reload 之前紧邻注册。因为严格工作负载阶段的读取非常频繁若过早注册一个宽范围的读故障它可能被前面的严格读取消耗掉once()语义下只触发一次到真正想打的那个 reload 读取时反而没有故障可用了。因此故障注册应当与目标读取/重载在时间上紧邻。七、异步与并发约束block_in_place 与 Send 边界规则 1517 是一组 Tokio 运行时相关的硬约束Fault scenario SQL helpers must run throughtokio::task::block_in_placebecause the VFS usesHandle::block_oninternally.FaultScenarioCtxis notSend; do not move it intotokio::spawn.Overlap tests withFaultScenarioCtxshould use same-task future joining around depot pause handles instead of spawning.7.1 SQL 助手必须经 block_in_placeFaultScenarioCtx::sqlscenario.rs通过with_database_blocking执行 SQLfn with_database_blockingT(self, f: impl FnOnce(NativeDatabase) - ResultT) - ResultT { tokio::task::block_in_place(|| self.with_database(f)) }原因在规则中已点明VFS 内部使用Handle::block_on来驱动 depot 异步读取。如果 SQL 执行路径处于 Tokio 异步上下文中且未脱离当前线程block_on与运行时调度器会互相冲突block_on 阻塞线程而运行时无法驱动其他任务。block_in_place会先把当前线程从运行时借出让 SQL 的同步调用安全地使用Handle::block_on。open_database_blockingscenario.rs同理。7.2 FaultScenarioCtx 不是 SendFaultScenarioCtx内部持有的NativeSqliteOracle包装了裸*mut sqlite3指针虽然 oracle 本身通过unsafe impl Sendoracle.rs允许跨线程传递但FaultScenarioCtx整体并不满足Send。因此禁止把FaultScenarioCtx移入tokio::spawn——否则会编译失败或引入未定义行为。这也意味着套件的并发模型是单任务顺序执行。7.3 重叠测试同任务 future join 而非 spawn规则 17 针对“overlap tests”验证 pause 故障下多个操作重叠执行的场景应当用同任务内的 future 组合futures::join!/join_all围绕 depot 的 pause 句柄进行而不是tokio::spawn。结合规则 16原因很清楚FaultScenarioCtx非Sendspawn 需要static Send闭包二者矛盾而同任务 future join 只是交错轮询不要求跨线程移动上下文完全满足需求。八、强制压缩测试走 depot 压缩入口而非手工造数规则 18Forced compaction tests must use depot compaction entry points and assert depot-levelForceCompactionResultcontents.压缩故障测试不是直接调用内部函数而是通过 depot 的压缩测试驱动入口执行。FaultScenarioCtx提供三个强制压缩助手scenario.rspub(crate) async fn force_hot_compaction(self) - ResultForceCompactionResult { self.force_compaction(ForceCompactionWork { hot: true, cold: false, reclaim: false, final_settle: false }).await } pub(crate) async fn force_reclaim(self) - ResultForceCompactionResult { self.force_compaction(ForceCompactionWork { hot: false, cold: false, reclaim: true, final_settle: false }).await } pub(crate) async fn force_compaction(self, work: ForceCompactionWork) - ResultForceCompactionResult { let database_branch_id self.database_branch_id().await?; let manager_workflow_id self.manager_workflow_id(database_branch_id).await?; let test_ctx self.inner.test_ctx.lock().await; DepotCompactionTestDriver::new(test_ctx) .with_wait_timeout(self.force_compaction_wait_timeout()) .force_compaction(manager_workflow_id, database_branch_id, work) .await }ForceCompactionWork的四个布尔位分别控制hot热压缩、cold冷压缩、reclaim回收冷对象/旧 shard 版本、final_settle最终沉降。超时随 profile 变化FaultProfile::Simple为 30 秒FaultProfile::Chaos为 120 秒scenario.rs。manager_workflow_idscenario.rs会通过test_hooks::register_workflow_fault_controller安装工作流故障控制器并启动DbManagerWorkflow。压缩涉及的工作流在build_registryscenario.rs中注册DbManagerWorkflow、DbHotCompactorWorkflow、DbReclaimerWorkflow。断言ForceCompactionResult内容的示例见 simple.rs 的simple_failed_hot_compaction_preserves_vfs_statelet result ctx.force_hot_compaction().await?; assert_eq!(result.attempted_job_kinds, vec![CompactionJobKind::Hot]); assert!(result.terminal_error.as_deref().is_some_and(|err| err.contains(simple hot compaction failure)));该用例在InstallAfterShardPublishBeforePidxClear注入故障验证热压缩报告了terminal_error工作流失败但重载后hot键数据依然完好——即故障发生在 shard 发布之后、PIDX 清理之前VFS 状态没有被破坏。九、冷压缩与回收场景冷 ref 必须来自强制压缩工作流规则 20End-to-end cold compaction or reclaim scenarios must create cold refs through forced depot workflows; handcrafted cold refs are only for clearly named harness regression tests.冷压缩cold compaction会把 shard 镜像写入冷层并留下 cold shard ref回收reclaim随后删除旧的热 shard 版本。端到端场景必须通过强制压缩工作流产生冷 ref保证冷 ref 是真实工作流产物。手工构造冷 ref 仅在“harness 回归测试”中被允许且测试名必须能明确辨认例如*_harness_regression。这可以从shard_source_txidsscenario.rs的实现看出冷/热两套键空间hot 版本位于keys::branch_shard_prefix(branch_id)cold ref 位于keys::branch_compaction_cold_shard_prefix(branch_id)后者通过decode_cold_shard_ref解析出(shard_id, as_of_txid)。growth.rs的run_growth_roundsgrowth.rs展示了完整的强制冷压缩 回收循环ctx.force_compaction(ForceCompactionWork { hot: true, cold: true, reclaim: false, final_settle: false }).await?; if reclaim { ctx.force_compaction(ForceCompactionWork { hot: false, cold: false, reclaim: true, final_settle: false }).await?; } ctx.reload_database().await?; ctx.verify_page_one_matches_head().await?;而clear_hot_shard_version_for_harness_regressionscenario.rs是专门的手工构造助手用于构建“热层持有旧版本 shard 而最新冷 ref 更新”的回归场景它的注释明确区分了两种用途删除单个 shard 版本让读路径落到过期的热版本vs清除全部 shard 版本让读路径落到冷层。十、重型负载伪随机大 blob 证明分块与 shard 边界覆盖规则 21Heavy pager workloads should use large pseudorandom blobs when proving delta-chunk or shard-boundary coverage; small or patterned blobs may compress below depot thresholds.这是最容易踩的坑小 blob 或规律性 blob 会被 depot 压缩到阈值以下无法产生足够的页面体积去触发 delta 分块一个大的 commit 按 shard 对齐的页范围切分为多个 LTX 段或跨 shard 边界测试就形同虚设。growth.rs 的参数很有代表性/// Each row spills into overflow pages, so one insert commits roughly nine 4 KiB pages and a round /// of ROWS_PER_ROUND inserts grows the database past a 64-page shard boundary. const PAYLOAD_LEN: usize 32 * 1024; const ROWS_PER_ROUND: i64 12; const ROUNDS: i64 6;每行 32 KiB 的 payload 溢出到约 9 个 4 KiB 页面每轮 12 行使数据库跨过 64 页的 shard 边界6 轮后数据库跨越多个 shard 与 fold 边界并断言pages_before SHARD_SIZE * 4growth.rs。payload 必须是伪随机的growth.rs/// Pseudorandom bytes: patterned payloads compress below depots delta thresholds and would not /// produce the page volume the scenario depends on. fn growth_payload(id: i64, len: usize) - Vecu8 { let mut state (id as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1; (0..len).map(|_| { state ^ state 13; state ^ state 7; state ^ state 17; state as u8 }).collect() }即采用 xorshift 伪随机序列乘数 0x9E37_79B9_7F4A_7C15 即黄金比例常数既保证“不可压缩”又保证确定性可复现seed 由 id 派生。该场景验证的核心是verify_page_one_matches_headscenario.rsVFS 只在打开时从 page 1 一次性获取库大小若 depot 返回的 page 1 与/META/head记录的页数不一致就会“锁存”一个错误的短库大小下一次 commit 就会截断库尾。检查通过read_page_one_and_headscenario.rs在同一个读事务内读取 page 1、head 页数与 head txid再结合shard_source_txids枚举该 shard 的全部热版本与冷 ref 版本断言“胜出的来源 txid 不早于最新 shard 镜像”。growth.rs的用例名growth_across_fold_boundaries_keeps_page_one_size_at_head即是对这一 incident 形态的回归。十一、不变式扫描的细节hot shard 形状检查与冷 PIDX 页验证规则 22 与 23 是不变式扫描的两个精细要求Depot invariant scans should shape-check hot shard rows below the compaction root but not require compacted-away commit rows.Depot invariant scans must verify cold-backed PIDX pages exist in the decoded cold object, not only in the cold ref txid range.11.1 压缩根以下的 hot shard 只做形状检查check_shardsverify.rs中hot shard 缓存行可能比已压缩的 commit 行存活更久。压缩根compaction root是分界线压缩根以下的 commit 已被冷层覆盖因此其残留 hot shard 行只需要形状检查页大小、pgno 范围、shard 归属、header 覆盖 txid而不能要求它们还关联着未被压缩掉的 commit 行if commits.contains_key(as_of_txid) || as_of_txid compacted_through { self.check_ltx_pages(hot shard, as_of_txid, shard, commits); } else { self.check_ltx_page_shape(hot shard, as_of_txid, shard); }check_ltx_page_shapeverify.rs检查页大小等于keys::PAGE_SIZE、页号非 0、每页字节数等于页大小、header 的min_txid..max_txid覆盖该 txid。同时check_ltx_shard_pagesverify.rs验证 shard 行内的每个页都属于该 shardpgno / SHARD_SIZE shard_id。11.2 冷 PIDX 页必须能在解码后的冷对象中找到规则 23 针对一个隐蔽的缺陷page_has_backingverify.rs在检查 PIDX 页的 backing 时若页面由冷 ref 覆盖仅仅落在 cold ref 的min_txid..max_txid区间是不够的——必须实际解码冷对象并确认该页存在于解码出的页面集合中。不过当前扫描器对 cold ref 只能建立“txid 区间覆盖”因为冷对象本体在没有冷层时不可达代码注释也说明了这一点而通过 hot shard 与 delta 两个来源则要求真正调用get_page(pgno)找到字节。这条规则的实际含义是凡是能解码验证的冷对象如通过冷层可达的测试环境PIDX 检查必须解码冷对象验证页存在不能停在区间判断上。十二、预期故障必须触发未触发即失败规则 19 是整个套件的验收底线Every expected fault must assert that it fired, and unfired expected faults must fail the test.在 FaultScenario::run 中工作负载执行完毕后立即调用result ctx.fault_controller().assert_expected_fired();DepotFaultController区分“已触发”与“未触发”的故障事件replay_log()只包含已触发事件而replay_log_with_unfired()见replay_record的使用包含未触发事件以供诊断。若某个once()故障因为时机错误没有消费到assert_expected_fired会直接使场景失败并附带未触发故障的详情。这条规则与规则 14宽读故障紧邻注册协同既要让故障一定触发又要保证它精确作用在目标操作上。十三、特性门控test-faults 不得泄漏进生产构建规则 24depot/test-faultsmust stay dev/test-only and must not leak into production builds.故障注入能力是极强的后门——生产代码若被注入了DepotFaultController任何网络攻击者理论上都能让 depot 在任意故障点失败或停顿。因此该能力以 Cargo feature 门控engine/packages/depot/Cargo.toml 定义空 featuretest-faults []engine/packages/depot-client/Cargo.toml 仅在测试依赖中启用depot { workspace true, features [test-faults] }。从 depot/src/fault 模块的引用面看controller、compaction 工作流、conveyer 读写路径故障控制器只在启用test-faults特性时编译。这意味着测试构建cargo test -p depot-client拥有完整的故障注入能力生产构建不启用该特性不包含任何故障控制器代码编译产物中不存在该后门。这保证了故障测试可以在与生产完全一致的代码路径上运行同时生产二进制不携带测试专用设施。十四、编写新故障场景的清单综合 CLAUDE.md 全部规则新增一个故障场景时的自检清单如下传输走真实SqliteVfsDirectDepotTransport不引入 mock / envoy / 其他传输变体严格模式严格工作负载不得使用 mirror 兜底或依赖预置 VFS 缓存计数器断言兜底故障语义只使用fail/pause/delay/drop作用于 depot 产物不做字节损坏、UDB 驱动故障、Gasoline 正确性或全局故障注入验证oracle 对比含模糊提交的 Old/New 分类 不变式扫描非故障冷层视图且在故障断言之后重放断言精确到故障点、边界与Workload/Verification相位reload关闭数据库 驱逐Db句柄 重新打开happy-path 用非致命读故障宽读故障紧邻目标注册异步SQL 走block_in_placeFaultScenarioCtx不进tokio::spawn重叠测试用同任务 future join压缩经DepotCompactionTestDriver强制压缩入口断言ForceCompactionResult的attempted_job_kinds与terminal_error冷数据端到端场景的冷 ref 来自强制压缩工作流手工构造仅限命名清晰的 harness 回归测试负载跨分块/shard 边界的场景使用大体积伪随机 blob不变式细节压缩根以下的 hot shard 行只做形状检查冷 PIDX 页在解码后的冷对象中验证存在性验收每个预期故障必须触发未触发即失败门控确认test-faults特性只在 dev/test 构建启用。结语depot-client的 fault-injection 套件是一套纪律极其严格的分布式存储故障测试体系它用“真实 VFS 真实 depot 传输”保证故障打在真路径上用“原生 SQLite oracle depot 不变式扫描”双通道验证正确性用“精确故障点 相位标注”的重放断言保证故障确实发生且发生在正确位置并用test-faults特性门控把测试后门与生产构建彻底隔离。对任何以 SQLite 为状态存储、以分布式数据库为页面后端的系统而言这套从测试契约CLAUDE.md到实现scenario / simple / growth / oracle / verify的完整方法论都是极具参考价值的工程范本。【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

S905L3-B改Armbian服务器:E900V21D电视盒子从短接到SSH登录的完整实战避坑指南 2026/9/18 22:39:53

S905L3-B改Armbian服务器:E900V21D电视盒子从短接到SSH登录的完整实战避坑指南

S905L3-B改Armbian服务器:E900V21D电视盒子从短接到SSH登录的完整实战避坑指南 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w…

阅读更多 →
ESP-CSI 实战:用 Ping 触发路由器数据包采集 Wi-Fi CSI(csi_recv_router 示例深度解析) 2026/9/18 22:39:53

ESP-CSI 实战:用 Ping 触发路由器数据包采集 Wi-Fi CSI(csi_recv_router 示例深度解析)

ESP-CSI 实战:用 Ping 触发路由器数据包采集 Wi-Fi CSI(csi_recv_router 示例深度解析) 【免费下载链接】esp-csi Applications based on Wi-Fi CSI (Channel state information), such as indoor positioning, human detection 项目地址: …

阅读更多 →
Element(Vue 2 UI Toolkit)Radio 单选框组件完全指南:基础用法、分组、按钮样式与源码剖析 2026/9/18 22:39:53

Element(Vue 2 UI Toolkit)Radio 单选框组件完全指南:基础用法、分组、按钮样式与源码剖析

Element(Vue 2 UI Toolkit)Radio 单选框组件完全指南:基础用法、分组、按钮样式与源码剖析 【免费下载链接】element A Vue.js 2.0 UI Toolkit for Web 项目地址: https://gitcode.com/gh_mirrors/eleme/element 本指南以 examples/doc…

阅读更多 →
YimMenu Lua 命令系统指南:command.call 调用与全部 210 个命令详解 2026/9/18 22:39:53

YimMenu Lua 命令系统指南:command.call 调用与全部 210 个命令详解

YimMenu Lua 命令系统指南:command.call 调用与全部 210 个命令详解 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Tre…

阅读更多 →
免费开源视频防抖工具GyroFlow:3步用陀螺仪数据消除手持晃动 2026/9/18 22:39:53

免费开源视频防抖工具GyroFlow:3步用陀螺仪数据消除手持晃动

免费开源视频防抖工具GyroFlow:3步用陀螺仪数据消除手持晃动 【免费下载链接】gyroflow Video stabilization using gyroscope data 项目地址: https://gitcode.com/GitHub_Trending/gy/gyroflow 回放时你发现每个画面都跟着手在晃,地平线随脚步倾…

阅读更多 →
GateGeluQuant 算子深度解析:CANN ops-transformer 中 GeGLU 与 Per-Channel 量化融合 Kernel 的 Tiling 与实现原理 2026/9/18 22:36:53

GateGeluQuant 算子深度解析:CANN ops-transformer 中 GeGLU 与 Per-Channel 量化融合 Kernel 的 Tiling 与实现原理

GateGeluQuant 算子深度解析:CANN ops-transformer 中 GeGLU 与 Per-Channel 量化融合 Kernel 的 Tiling 与实现原理 【免费下载链接】ops-transformer 本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。 项目地址: https://git…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞