报告 010 · SQLite、本地文件与维护边界
日期:2026-09-19。设计阶段的独立机制实验,非产品实现、模型实验或掉电测试。
1. 条件与执行
本实验在单机、SQLite 与本地文件条件下,预登记 22 例,编写独立脚本并执行一次。未在查看结果后修订条件或重跑。
环境为 Linux 6.18.52、XFS、Python 3.14.7、SQLite 3.53.4;实验临时目录位于仓库所在文件系统,未使用 /tmp 的 tmpfs。SQLite 使用 WAL、FULL;自动 checkpoint 关闭、busy timeout 为 0,以确定观察竞争和 WAL 行为。这些是实验参数,不能直接当产品配置。
夹具包含主体计数、输入消费、唯一提交键、文件引用及 queued 操作。固定文件 65,536 字节,经临时写、文件同步、排他 hard link 发布、目录同步,再提交 SQLite 引用与业务状态。子进程只模拟宿主在不同阶段终止,线程模拟进程内发布、读取与维护,并不重新引入多进程产品架构。
22 例符合预登记判据,174 项显式检查通过,13 个子进程、8 次有意 SIGKILL,失败检查和异常兜底强杀均为 0。 其中包含预期会失败的错误设计对照;“通过”表示观察符合预登记,不表示所有被测方案均正确。检查数不是独立样本数或故障概率。原始 JSON 保存条件、屏障、退出码、SQL 终态、文件摘要与错误码。
本实验没有调用模型、推进 go-mini、执行现实工具动作或测量真实业务吞吐;它覆盖 S1 的存储与维护子集,没有完成整个认知工作闭环。
2. 对照结果
| 场景 | 实际观察 | 设计含义 |
|---|---|---|
| L1 同一锁对象 | 活跃持锁者使第二实例拒绝;SIGKILL 后锁路径仍存在,第三实例取得同一 inode 的锁 | 依赖实际文件锁状态,不按文件是否存在判定存活 |
| L2 删除锁路径 | 原进程活跃时重建路径,两个进程分别锁住不同 inode | 不能通过删除所谓残留锁文件解锁;锁对象在正常启停间保持稳定 |
| F1/F2 临时写/文件同步后终止 | 无已提交状态、待办或引用;保留临时残留 | 文件存在不代表业务提交 |
| F3/F4 最终文件发布/目录同步后终止 | 文件可见但仍无业务提交,形成孤立文件 | 文件先发布允许留下孤立内容,不应留下已提交悬空引用 |
| F5 COMMIT 前终止 | 计数、输入消费、引用、待办、回执全部回滚 | 必要业务记录共同提交;仅执行过 SQL 不算成功 |
| F6 COMMIT 后丢确认 | 原回执与 queued 待办恢复,文件摘要正确;重送不新增意图 | 查询原提交键解决确认丢失 |
| F1~F5 后续进展 | 恢复后原请求均能完成,最终计数和待办各 1 | 并非用永久拒绝所有工作获得正确性 |
| F7 引用先提交 | 状态和待办已保存,但文件不存在,悬空引用 1 | 否决先提交引用、之后再生成文件的顺序 |
| F8 重送/冲突 | 相同内容返回原回执,同键不同内容拒绝,计数和待办各 1 | 幂等键必须绑定内容 |
| F9/F10 注入 EIO/ENOSPC | 错误显式返回,业务计数、引用与待办均未提交 | 文件 I/O 错误不能降级成成功;未真实填满磁盘或破坏设备 |
| B1 只复制 db 与 backup API | 原数据为 9,只复制 db 读到 0,backup API 副本读到 9 | 活跃 WAL 的最新提交可能不在主 db 文件内 |
| B2 快照与回收竞争 | 数据库快照保留引用,原库删除并回收后,复制文件得到 ENOENT | SQLite 快照不自动保留外部文件 |
| B3 受控完整备份 | 维护期间拒绝新修改;副本与文件清单校验成功,恢复的计数/输入/待办/回执一致 | 先固定维护边界,再备份数据库和所引用文件;恢复不自动执行待办 |
| B4 复制失败/伪完成标记 | 注入复制错误后无完成标记;伪造标记仍因文件缺失被拒 | 标记是校验入口,不是完整性的充分证据 |
| G1 只查数据库做回收 | 发布者暂停在文件已发布、引用待提交处,GC 删除文件,恢复发布后产生悬空引用 1 | 回收必须覆盖在途发布责任 |
| G2 排空完整发布事务 | 维护关闭新准入并等待发布者完成引用事务,再删除孤立文件;正式引用有效 | 发布阶段从开始准备一直延续到引用事务终结 |
| G3 排空文件读者 | 新读者拒绝,GC 等待旧读者结束后才删除 | 维护边界也覆盖本地文件读取生命周期 |
| T1 旧快照升级 | 得到 SQLITE_BUSY_SNAPSHOT(517);旧事务仍读到旧值;回滚重新读取后可提交 | 不能在旧快照上无限重试写入 |
| T2 两个写者 | 第二个 BEGIN IMMEDIATE 得到 SQLITE_BUSY(5);前者结束后第二个能推进 | 写入通路应串行调度,有界处理竞争 |
| T3 长读与 checkpoint | 32 次更新后 checkpoint 为 [0,64,0],WAL 263,712 字节;读事务结束后为 [0,0,0],WAL 截断至 0 | 不跨模型等待持有读事务;观察 WAL 增长及未结束读者 |
F3 的文件在 SIGKILL 后可见不证明目录同步可省略:内核和磁盘环境未重启。T3 的页数、文件大小和 32 次更新是本实验夹具参数,不是吞吐或容量阈值。
3. 据此明确的方案
- 固定锁文件对象。 Linux 单实例候选使用稳定路径上的非阻塞独占 flock;正常退出关闭持有描述符,不 unlink 锁文件。目录锁不是对恶意写入或遗留外部工作的隔离保证。
- 文件先发布,引用后提交。 文件就绪后以短 SQLite 事务共同保存必要状态和待办;提交后才确认,确认丢失查原键。文件失败不提交引用,孤立文件由维护处理。
- 维护统一关闭准入再排空。 发布者持有维护资格直至引用事务成功或明确放弃;清理等待相关读者结束。备份、删除与回收共用此边界,不能只锁文件 rename 或只查数据库引用。
- 备份必须校验闭包。 使用 backup API,固定其引用的文件集合,复制并校验数据库和文件后发布完成清单;恢复重新检查清单及内容,不能只看一个标记。
- 明确 SQLite 事务生命周期。 上下文读取结束就释放快照;计算完成后以 BEGIN IMMEDIATE 开始短写事务并复核条件。BUSY_SNAPSHOT 需回滚重读;普通写入竞争有界重试,仍用原业务键。不从本实验 busy=0 或关闭自动 checkpoint 推出产品调优参数。
Go 驱动、跨平台发布方式、吞吐曲线、真实磁盘错误与备份停顿仍未确定。
4. 一手资料及未验证边界
以下资料能支持契约或排除错误保证,不能证明本项目端到端可靠性。
| 未完全实测的命题 | 一手依据 | 设计推论与剩余验证 |
|---|---|---|
| 进程终止后锁是否一定释放 | Linux flock | 锁关联打开的文件描述,fork/dup 的副本会影响释放;避免继承锁描述符。L1 未测试完整子进程继承树或恶意忽略 advisory lock |
| fsync/发布能否保证掉电后文件可恢复 | Linux fsync、link、SQLite atomic commit | 文件、目录及存储刷新契约均重要;本实验未掉电,也未验证新建数据/备份目录的全部祖先项持久化 |
| 长读快照能否等待后继续升级 | SQLite isolation、事务文档 | 结束旧快照并重建写事务;T1/T2 支持局部行为,Go 驱动中的错误与事务状态处理仍待测 |
| WAL 和备份文件能否任意复制或移动 | SQLite WAL、backup API、文件损坏原因 | 不单独操作活跃数据库文件或其日志;完整备份须包括外部内容,副本恢复步骤另行校验 |
维护入口依赖所有相关读写遵守;本实验只实现并测试夹具中的入口。没有验证直接绕开入口的库、符号链接/路径攻击、所有并发次序、长时间饥饿或维护等待超时后的降级。临时文件残留被记录,尚未实现完整删除、权限撤销与备份保留期流程。
现有记录只提供完整备份与指定故障点的行为,不能保证旧备份之后发生的外部动作可判定;原有 unknown 处理继续适用。真实认知闭环、Go 驱动与构建依赖、FTS5 中文检索质量、CPU/GPU 干扰仍属后续研究。
运行命令见预登记,输出排他创建,复跑须更换路径。原 E0~E8e 记录未修改。
| 文件 | SHA-256 |
|---|---|
| 预登记 README.md | 9c79ab6a8a63a16d770aa146ad1411983780f6ec8a8f024bd6c3c0b5ea129c77 |
| experiment.py | 039e149ae1c9b1d92fff65e6263628149cfd534372b5cef1000a091c63db5569 |
| local-storage.json | be742366fb84ee31c070c6899637bdc09936a2999111fa82af7b6430f6d9bd9a |
数据可用性
本报告公开实验条件、汇总统计和失败分析。原始输入、脚本及逐次运行记录未随报告发布,因此不能仅凭本文独立复现实验。