RxDB Cleanup 插件完全指南:自动清理已删除文档,优化存储与查询性能

发布时间:2026/9/20 14:21:26
RxDB Cleanup 插件完全指南:自动清理已删除文档,优化存储与查询性能 数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载导读本文围绕 RxDB 官方文档 cleanup.md 展开深入讲解 Cleanup 插件的安装、清理策略配置、手动触发方式与内部实现原理。你将掌握如何让 RxDB 在保证复制replication正确性的前提下自动安全地清除已删除文档墓碑 tombstone从而节省磁盘空间、加速查询并理解清理循环与复制状态、多实例领导者选举之间的协作机制。为什么需要 Cleanup删除状态必须先保留RxDB 采用 local-first离线优先架构为了支撑复制replication它必须把已删除的文档继续留在存储中以保留其删除状态。这样当客户端处于离线状态时删除这一变更仍然被记录在本地待客户端重新联网后才能把删除状态同步到后端避免已删除的文档在后端“复活”。这些带_deleted: true标记的文档在数据库中被称为墓碑tombstone。墓碑是复制协议正确性的基石但若不加控制地无限累积会带来两个明显问题查询变慢墓碑越多查询需要扫描和过滤的数据就越多磁盘占用膨胀大量墓碑会白白占用存储空间。Cleanup 插件正是为解决这一矛盾而生的它在“能安全删除的时候”才运行清理循环自动清除满足条件的墓碑同时绝不破坏复制所需的删除状态。安装 Cleanup 插件Cleanup 是一个独立的 RxDB 插件需要先注册到 RxDB 中才能使用import { addRxPlugin } from rxdb; import { RxDBCleanupPlugin } from rxdb/plugins/cleanup; addRxPlugin(RxDBCleanupPlugin);从源码看插件的注册逻辑位于 src/plugins/cleanup/index.ts。该插件名为cleanup它通过两个关键机制接入 RxDB 生命周期原型扩展给RxCollection原型注入cleanup(minimumDeletedTime?)方法供手动调用Hook 钩子注册createRxCollection.after与createRxState.after两个钩子每当创建集合或创建 RxState 时自动启动对应的后台清理循环。export const RxDBCleanupPlugin: RxPlugin { name: cleanup, rxdb: true, prototypes: { RxCollection: (proto: any) { proto.cleanup async function (this: RxCollection, minimumDeletedTime?: number): Promiseboolean { // ... return cleanupRxCollection(this, cleanupPolicy); }; } }, hooks: { createRxCollection: { after: (i) { startCleanupForRxCollection(i.collection); } }, createRxState: { after: (i) { startCleanupForRxState(i.state); } } } };因此只要注册了插件之后创建的每个 RxCollection 都会自动获得后台清理能力无需额外配置即可使用默认策略。创建带清理策略的数据库清理策略cleanup policy在创建 RxDatabase 时通过cleanupPolicy字段指定。对于大多数使用场景使用默认值即可但理解每个参数能帮助你针对特殊场景调优import { createRxDatabase } from rxdb; import { getRxStorageLocalstorage } from rxdb/plugins/storage-localstorage; const db await createRxDatabase({ name: heroesdb, storage: getRxStorageLocalstorage(), cleanupPolicy: { /** * 文档被删除后至少要经过多少毫秒才会被清理清除。 * 该值应大于你预期用户离线的最长时间 * 否则删除状态可能来不及复制到后端。 * [默认值31天] */ minimumDeletedTime: 1000 * 60 * 60 * 24 * 31, // 一个月 /** * RxCollection 至少已存在多久后才会启动清理。 * 这确保了页面初次加载时清理进程不会拖慢更重要的任务。 * [默认值60秒] */ minimumCollectionAge: 1000 * 60, // 60 秒 /** * 首次清理完成后每隔 [runEach] 毫秒启动一轮新的清理。 * [默认值5分钟] */ runEach: 1000 * 60 * 5, // 5 分钟 /** * 若为 trueRxDB 会等待所有正在运行的复制 * 没有进行中的复制周期才执行清理。 * 这确保我们不会在删除状态可能尚未复制完成时 * 清除已删除的文档。 * [默认值true] */ awaitReplicationsInSync: true, /** * 若为 true只有当当前实例是 leader领导者时 * 才会启动清理。 * 这确保在 multiInstance 多实例模式下 * 只有一个实例会执行清理。 * [默认值true] */ waitForLeadership: true } });策略参数速查表参数默认值含义调优建议minimumDeletedTime31 天1000*60*60*24*31文档删除后至少保留多久才可被清理应大于用户最长离线时长过小会导致删除状态来不及复制minimumCollectionAge60 秒1000*60集合创建后至少等待多久才启动清理避免首屏加载被后台清理拖慢runEach5 分钟1000*60*5相邻两轮清理之间的间隔数据变更频繁时可适当调小awaitReplicationsInSynctrue清理前是否等待复制处于同步状态关闭会提升清理频率但有丢失删除同步的风险waitForLeadershiptrue仅 leader 实例执行清理单实例场景可关闭多实例场景务必开启这些默认值在源码 src/plugins/cleanup/cleanup-helper.ts 中集中定义export const DEFAULT_CLEANUP_POLICY: RxCleanupPolicy { minimumDeletedTime: 1000 * 60 * 60 * 24 * 31, // one month minimumCollectionAge: 1000 * 60, // 60 seconds runEach: 1000 * 60 * 5, // 5 minutes awaitReplicationsInSync: true, waitForLeadership: true };cleanupPolicy的类型定义位于 src/types/plugins/cleanup.d.ts在 RxDatabase 配置 中被声明为PartialRxCleanupPolicy——也就是说所有字段都是可选的你只需覆盖想调整的项其余自动回落到默认值。这一合并逻辑体现在源码中const cleanupPolicy Object.assign( {}, DEFAULT_CLEANUP_POLICY, rxDatabase.cleanupPolicy ? rxDatabase.cleanupPolicy : {} );参数之间如何协作源码级解析minimumDeletedTime直接决定墓碑的“安全期”。它最终会传递给底层存储的清理接口RxStorageInstance.cleanup(minimumDeletedTime)该接口的契约在 src/types/rx-storage.interface.d.ts 中有明确规定移除所有_deleted为 true、且删除时间距今超过minimumDeletedTime的墓碑返回true表示所有可清理文档已全部清除返回false表示还有更多可清理文档但为避免长时间阻塞存储本轮未全部清除即“分批清理”。minimumDeletedTime的语义直接决定了清理的安全性它必须大于复制延迟的上限。若设置过小用户离线期间产生的删除还没来得及复制就可能在重新联网前被物理清除导致后端无法获知删除事件。清理循环的内部原理注册插件后每个新建集合都会自动启动startCleanupForRxCollection其完整流程在 src/plugins/cleanup/cleanup.ts 中实现export async function startCleanupForRxCollection(rxCollection: RxCollection) { const rxDatabase rxCollection.database; const cleanupPolicy Object.assign({}, DEFAULT_CLEANUP_POLICY, rxDatabase.cleanupPolicy ?? {}); await initialCleanupWait(rxCollection, cleanupPolicy); // ① 初始等待 if (rxCollection.closed) return; await cleanupRxCollection(rxCollection, cleanupPolicy); // ② 首次清理 await runCleanupAfterDelete(rxCollection, cleanupPolicy); // ③ 监听删除循环清理 }整个循环由三个阶段构成① 初始等待initialCleanupWait见 cleanup.ts。先通过collection.promiseWait(cleanupPolicy.minimumCollectionAge)等待minimumCollectionAge毫秒确保页面首屏加载不被清理任务拖慢若数据库处于多实例模式且策略要求waitForLeadership则进一步等待当前实例成为 leader保证同一时刻只有一个实例执行清理。② 首次清理cleanupRxCollection核心清理循环见 cleanup.ts。它有一个关键设计——全局串行队列。RXSTORAGE_CLEANUP_QUEUE是一个模块级 Promise所有清理调用都被串联起来即使在多个数据库上运行对RxStorage().cleanup()的调用也永远不会并行因为清理是后台任务不应影响其他更重要的任务性能。每次真正调用存储清理前还会等待rxDatabase.requestIdlePromise()——这是 RxDB 对浏览器requestIdleCallback()的封装确保清理只在主线程空闲时执行。③ 删除后循环runCleanupAfterDelete见 cleanup.ts。清理并不会按固定节拍盲目空转而是先监听rxCollection.eventBulks$事件流一旦出现任何写入事件再等待runEach毫秒后启动下一轮清理。这样既保证删除发生后墓碑能及时被清除又避免了在无变更时白白消耗资源。复制状态与清理的协同当awaitReplicationsInSync: true时清理循环会通过REPLICATION_STATE_BY_COLLECTION见 src/plugins/replication/index.ts 导出的集合复制状态表找到该集合关联的所有复制状态逐一调用awaitInSync()等待复制同步然后才执行存储清理。同时它还会对每个复制状态的metaInstance复制元数据实例执行meta.cleanup(minimumDeletedTime)——因为部分存储如 memory-mapped 存储会对元数据做额外的数据整理即使元数据本身是 append-only 的也需要清理。每轮清理完成后会触发postCleanup插件钩子钩子声明见 src/hooks.ts传入{ collectionName, databaseName }便于其他插件在清理结束后做后续处理。分批清理与循环退出条件某些存储如 FoundationDB采用分批清理策略单次cleanup()返回false表示“还没清完”。因此cleanupRxCollection使用while (!isDone !rxCollection.closed)循环直到存储返回true或集合被关闭才退出。这一行为在测试用例 “should correctly loop cleanup when storage cleanup returns false (batched cleanup)” 中被显式验证见 test/unit/cleanup.test.ts模拟第一次调用返回false、第二次才真正清理最终断言墓碑确实被移除。手动调用清理除自动循环外你也可以对某个集合手动执行清理通过调用 RxCollection 的.cleanup()方法/** * 使用 cleanupPolicy 中的 minimumDeletedTime 手动执行清理。 */ await myRxCollection.cleanup(); /** * 显式覆盖 minimumDeletedTime单位为毫秒。 */ await myRxCollection.cleanup(1000); /** * 将 minimumDeletedTime 设为 0清除所有已删除文档 * 无论它们是什么时候被删除的。 */ await myRxCollection.cleanup(0);手动调用时插件会把传入的参数合并进策略并立即执行cleanupRxCollection见 src/plugins/cleanup/index.ts返回Promisebooleantrue表示所有可清理文档已被移除。手动清理的典型验证方式在测试 test/unit/cleanup.test.ts 中展示了手动清理的标准验证手法插入文档 → 删除其中一条 → 调用collection.cleanup(0)→ 通过collection.storageInstance.findDocumentsById([primary], true)检查底层存储断言被删文档的墓碑已不存在、而未删除的文档仍然保留。另一个值得关注的细节是参数透传。测试 “minimumDeletedTime not respected”test/unit/cleanup.test.ts通过包装底层存储实例的cleanup()方法断言传入的参数分别为0、5和默认值DEFAULT_CLEANUP_POLICY.minimumDeletedTime证明手动传入的数值会原样透传给存储层而省略参数时则使用策略默认值。用 Cleanup 插件清空一个集合当你有一个包含文档的集合想要通过“清除全部文档”的方式将其清空时官方推荐的做法是调用myRxCollection.remove()。但需要注意这会销毁集合的 JavaScript 类停止所有监听器和 observable。在某些场景下更合适的做法是先用查询删除所有文档再借助 Cleanup 插件把产生的墓碑一并清除// 删除所有文档软删除产生墓碑 await myRxCollection.find().remove(); // 清除所有已删除文档物理清除墓碑 await myRxCollection.cleanup(0);这种方式保留了集合实例本身监听器和 observable 不受影响适用于需要“清空后继续使用同一集合”的业务场景。清理墓碑后重新插入的边界情况清理墓碑会切断文档的修订历史链。测试 “#8948 must find a document that was re-inserted after the cleanup purged its tombstone”test/unit/cleanup.test.ts验证了一个重要行为删除文档 →cleanup(0)清除墓碑 → 重新插入相同主键的文档此时写入会开启一条全新的修订链且findOne()按主键查询、find()按 selector 查询都能正确返回重新插入的文档文档缓存中的_deleted标志为false。这说明 Cleanup 清掉的只是墓碑本身不会破坏文档重新写入的正确性。Cleanup 与 RxState 的配合除了集合Cleanup 插件还覆盖了 RxDB 的 RxState状态存储。createRxState.after钩子会调用startCleanupForRxState见 src/plugins/cleanup/cleanup-state.ts流程与集合清理类似先等待minimumCollectionAge执行首次清理随后监听写入事件并按runEach间隔循环清理。与集合版本的区别在于它只对状态本身调用state._cleanup()并同样受全局RXSTATE_CLEANUP_QUEUE串行队列和requestIdlePromise()空闲时机的约束。同时该文件也实现了“仅在写入发生后启动计时器”的优化避免无写入时按固定间隔空转见 cleanup-state.ts。常见问题FAQ清理在什么时候运行清理循环经过精心设计只会在数据库空闲、且不太可能影响其他数据库操作性能的时候运行。具体来说默认情况下集合创建后的前 60 秒内不执行清理受minimumCollectionAge控制确保网站首屏加载不会被拖慢清理真正调用存储前会等待requestIdleCallback()机制RxDB 内部封装为requestIdlePromise()给出的空闲窗口清理调用被全局串行队列约束绝不与其他清理并发执行多实例模式下只有 leader 实例执行清理受waitForLeadership控制当存在未同步完成的复制时清理会等待复制同步受awaitReplicationsInSync控制。实战调优建议综合文档与源码这里给出几条可落地的调优建议minimumDeletedTime必须大于预期最大离线时长。这是唯一会影响数据正确性的参数——设置过小删除状态可能在复制前被清除。默认 31 天对绝大多数消费级应用是安全值。不要随意关闭awaitReplicationsInSync。复制中的删除事件是清理最大的“危险来源”保留默认值true可确保删除状态先复制、后清除。多实例场景务必保留waitForLeadership: true。否则多个标签页/进程会同时清理同一存储造成不必要的性能开销。首屏性能敏感的应用可调大minimumCollectionAge。默认 60 秒已能覆盖绝大多数首屏场景若有更重的初始化任务可继续调大。清空集合但保留集合实例时用find().remove()cleanup(0)组合若不再需要该集合直接myRxCollection.remove()更彻底。验证清理是否生效可参考测试用例中的手法用collection.storageInstance.findDocumentsById([primary], true)直接检查底层存储中墓碑是否存在。总结RxDB Cleanup 插件通过“保留墓碑以支撑复制、安全时机下清除墓碑以释放空间”的双重设计解决了 local-first 架构中删除状态与存储膨胀之间的矛盾。理解minimumDeletedTime、minimumCollectionAge、runEach、awaitReplicationsInSync、waitForLeadership五个策略参数及其默认值配合手动cleanup()调用你就能在保证复制正确性的前提下精细控制数据库的存储占用与查询性能。其底层实现全局串行队列、requestIdleCallback 空闲调度、复制同步等待、leader 选举约束、分批清理循环均可在 src/plugins/cleanup 目录下逐一核对测试覆盖可参考 test/unit/cleanup.test.ts。赞分享数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载相关推荐RxDB RxDocument 完全指南掌握文档的插入、查询、更新、删除与响应式观察RxDB RxDocument 完全指南掌握文档的插入、查询、更新、删除与响应式观察 RxDocument 是 RxDB 中代表单条文档数据的核心对象可以类数据库NoSQL嵌入式数据库实时数据库FlutterFire批量删除优化高效清理Firebase存储中的文件FlutterFire批量删除优化高效清理Firebase存储中的文件 你是否还在为Flutter应用中Firebase存储文件的批量删除效率低下而烦恼当用后端移动开发laf云存储生命周期管理自动归档与删除过期文件laf云存储生命周期管理自动归档与删除过期文件 痛点与挑战失控的云存储成本 你是否正面临这些问题云存储账单持续增长但利用率不足30%旧日志和备份文件占用后端Serverless前端云原生上一篇Deep-Live-Cam实时换脸3个简单技巧告别卡顿流畅体验AI换脸魅力下一篇CANN ops-nn 中 aclnnSwigluGroupQuant 算子详解SwiGLU 激活与分组量化融合计算创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询