Kudu本地数据层原理:清理回执、删除日志与存储历史为什么用文件而不是数据库
【免费下载链接】kuduFree Windows, Mac and Linux cleaner, scanner, and more.项目地址: https://gitcode.com/gh_mirrors/kudu1/kudu
Kudu(免费开源的 Windows / macOS / Linux 系统清理与扫描工具)的本地数据层并没有依赖数据库,而是用 JSON、JSONL 与原子文件操作实现清理回执、删除日志和存储历史三大存储,better-sqlite3 则只用于优化用户的 SQLite 数据库文件。本文带你快速看懂这套设计。
先澄清一个常见误解:better-sqlite3 存什么、不存什么
很多读者以为 Kudu 的所有记录都存在 SQLite 里,实际并非如此:
- 清理回执(Cleanup Receipts)、删除日志(Deletion Log)、存储历史(Storage History)都以普通文件形式存放在用户数据目录(
userData)下; - better-sqlite3 是 Kudu 的运行时依赖,但它只服务于数据库优化器:对用户应用遗留的 SQLite 文件执行
VACUUM压缩,而不是存放 Kudu 自身的记录。
这个选择很务实:清理工具的数据写入频率低、单条记录小,且需要跨进程(GUI、守护进程、CLI 共用同一数据目录)安全共享,文件 + 原子重命名 + 文件锁就足够可靠,还便于人工查看与排查。
三大本地数据存储一览
| 数据 | 位置(userData 下) | 格式 | 关键机制 |
|---|---|---|---|
| 清理回执 | cleanup-receipts/ | receipts.json索引 + 单回执 JSON | 跨进程文件锁 + 写队列 |
| 删除日志 | deleted-files.jsonl | JSONL 追加写 | 8 MB 滚动轮转 |
| 存储历史 | storage-history/ | index.json索引 + 快照 JSON | 完整性校验 + 损坏隔离 |
| 数据库优化 | 目标 SQLite 文件 | - | better-sqlite3 子进程 VACUUM |
清理回执:一次清理的“收据”
回执记录了每次清理删了什么、为什么有的文件没删掉。核心实现在 cleanup-receipts.ts,几个值得学习的细节:
- 跨进程文件锁:打包版 GUI、后台守护进程和
--cli模式各自有独立的内存写队列,共享同一数据目录。代码用一个receipts.lock文件互斥,锁里写入持有者的随机 token(只释放自己的锁),30 秒未刷新的“僵尸锁”可被原子重命名方式安全回收,运行中每 10 秒心跳续期,慢任务不会被误判为崩溃; - 失败原因只存原因码:例如
permission-denied、in-use、recently-modified(见 cleanup-receipts.ts 中的REASON_CODES),而不是原始异常信息,避免把含隐私的绝对路径写进日志; - 同进程串行写队列:所有索引更新排进同一条 Promise 链,配合“写临时文件 + 原子 rename”落盘,任何时刻磁盘上都是完整数据。
回执的展示逻辑在 cleanup-receipts.ipc.ts,共享类型定义在 cleanup-receipts.ts。
删除日志:六位数文件量级下的追加写设计
删除日志的目标是:任何一次清理都能事后审计。实现见 deletion-log-store.ts,设计非常克制:
- JSONL 而非 JSON:一次深度清理可能删除几十万甚至上百万个文件,每批删除追加一行,内存与写开销保持恒定;
- 8 MB 滚动轮转:超限后重命名为
deleted-files.old.jsonl,空间占用有上限; - 按需读取:启动时不加载任何日志,查询时才读取,默认返回 200 条、上限 1000 条;
- 最佳努力(best-effort):写日志失败绝不让清理本身失败——记录系统可以为审计服务,但不能拖累主流程。
存储历史:带完整性校验的磁盘快照
存储历史保存每次磁盘扫描的快照,支撑容量趋势图与增长告警,实现在 storage-history-store.ts:
- 索引 + 明细两级结构:
index.json只存摘要(最多 10 个作用域、900 条快照),每个快照的完整数据单独存为 UUID 命名的 JSON 文件; - 防篡改校验:快照携带 64 位十六进制校验和,索引加载时逐字段验证类型、范围与格式;
- 损坏自愈:索引解析失败时整体隔离为
index.corrupt-<时间戳>并重新建空索引,一次损坏不会导致功能永久不可用; - 孤儿文件清理:每次变更前顺带删除未被索引引用的旧明细文件,磁盘不膨胀。
better-sqlite3 的真正岗位:数据库优化器
Kudu 能扫描出用户系统里散落的 SQLite 文件(浏览器、应用缓存等)并压缩它们,这一步才用到了 better-sqlite3:
- database-vacuum.ts 中打开数据库设置了
timeout: 250——忙库直接跳过,不去反复争抢正在使用它的应用的写锁;随后执行VACUUM,并统计主文件与-wal文件的大小差作为“回收空间”; - 由于 better-sqlite3 是同步 API,卡死的原生命令无法用 Promise 超时打断,Kudu 把它放进独立子进程 database-worker.ts 运行,父进程可通过 database-optimizer.ts 设置 30 秒总时限并直接杀掉卡死进程;
- 打包后的可用性由 package-smoke-test.js 冒烟测试兜底,确保 better-sqlite3 原生模块随安装包正确构建。
小结
- 用文件而非数据库承载回执、日志、历史,换来跨进程简单互斥、人工可查、零外部依赖;
- 三条通用防线贯穿所有存储:原子 rename 落盘、文件锁/写队列串行化、损坏时隔离自愈;
- better-sqlite3 以“子进程 + 短超时 + 总时限”的方式被安全地用在数据库优化器上,做到优化别人的库时绝不影响 Kudu 自身。
想深入了解完整实现,可以获取源码后从 src/main/services/ 目录读起:
git clone https://gitcode.com/gh_mirrors/kudu1/kudu【免费下载链接】kuduFree Windows, Mac and Linux cleaner, scanner, and more.项目地址: https://gitcode.com/gh_mirrors/kudu1/kudu
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考