当前位置:
AIGC文章详情

为什么 Syncthing 显示“同步完成”,应用读到的还是旧文件?HN 昨天引爆的地雷,与三类真不该同步的文件

源自105位全网作者

03:55

8 月 23 日,开发者 Fernando Borretti 在自己的博客发了一篇短文,第二天被顶上了 Hacker News 首页。故事不复杂:他给自己写了一个记日记的小应用,数据存在 SQLite 数据库里,用 Syncthing 在笔记本台式机之间同步这个数据库文件。开发者博客某天他在笔记本上写完日记,回家确认 Syncthing 已经同步完成,台式机上打开应用却是旧内容。诡异的是:用 sqlite3 命令行打开同一个数据库,新内容明明白白在里面;把应用服务一重启,新内容又出来了。

这不是个别 bug,它戳中的是"同步工具和正在运行的程序共用一个文件"时的底层规则。我顺着这篇文章把 Syncthing 官方论坛、项目负责人的原话都翻了一遍,今天把这里的三个地雷讲透,顺带把哪些文件能同步、哪些不能,一次说清。

地雷一:"同步完成"和"应用读到新内容"是两回事

Borretti 踩中的机制在操作系统层面。Syncthing 替换已有文件时,不会直接覆盖目标文件,而是先把数据写进临时文件,再用 rename 系统调用把文件一次性换到位——这正是 POSIX 推荐的安全替换方式,能保证你不会读到写了一半的文件。

问题出在 rename 语义的另一半:它改的是"路径→文件"的映射,不是文件本身。如果一个程序已经打开了旧文件、手里攥着文件描述符,这个描述符指向的还是旧文件,旧文件成了"孤儿",等程序释放后才会被回收。开发者博客于是一连串别扭的现象出现了:Syncthing 这边,磁盘上的文件确实是最新的;应用那边,继续读着自己打开的旧 inode,看到的还是上回的内容;你把应用重启、文件重新打开,新内容又凭空出现。

如果你同步的是文档、照片、代码这类写完就关闭的文件,这个地雷终生不会触发。但如果你同步的是被程序长期打开的文件——数据库、常驻服务的配置、游戏存档——那就是另一回事了。HN 讨论里也有开发者给出检测办法:对自己持有的文件描述符做 fstat,引用计数掉到零就说明文件已被替换,该重新打开了。Hacker News

为什么 Syncthing 显示“同步完成”,应用读到的还是旧文件?HN 昨天引爆的地雷,与三类真不该同步的文件

地雷二:发送端可能根本没发现文件变了

HN 那篇文章只讲了接收端,而 Syncthing 用户同步 SQLite 数据库时,踩的往往是两个地雷,第二个在发送端。2023 年一个"sqlite 文件不停产生冲突副本"的论坛帖里,核心维护者 AudriusButkevicius 解释过:SQLite 用内存映射文件写入,mtime 不更新,所以"在我们真的去读它之前,我们认为它没变"。Syncthing 官方论坛翻译一下:就算接收端的问题解决了,发送端的 Syncthing 也可能压根不知道这个数据库改过,要等周期性的全量扫描才会发现。开了文件系统监视的情况下,默认全量扫描间隔是 1 小时。Syncthing 官方文档所以"我明明保存了,怎么没同步?"有时候不是网络问题,是变化从未被察觉。

地雷三:数据库被"热替换",可能直接损坏

前两个地雷顶多吓你一跳,这一个会丢数据。同一个帖子里,项目负责人 calmh 给出了结案陈词:SQLite 不容忍自己的数据库文件在打开状态下被别人换掉,Syncthing 用 rename 换文件这一下,对 SQLite 等于脚下抽凳子——轻则数据不一致,重则库损坏。唯一安全的用法,是保证使用这个数据库的程序同一时间最多只在一处打开。Syncthing 官方论坛程序关闭后保存会正常写入 mtime,同步自然开箱即用。

他同事的表述更直接:别指望能同步数据库,那是疯了。那个帖子的楼主同步的是一个小型 sqlite 键值库,一天写入十几次,每天产生 .sync-conflict 冲突副本。如果你的同步文件夹里冲突副本也在越攒越多,那其实是警报:说明两端都开着、都在写,离地雷三只差一步。

哪些文件能同步?用三类法判断

先给设备上的文件分三个篮子:

可以放心同步的,是"关闭态"文件。照片、文档、视频、代码、导出的 PDF,写完就是写完,同步工具随便折腾。

可以有条件同步的,是"为同步设计过"的应用。典型如 KeePassXC:官方 FAQ 明确说,把数据库放进共享的同步目录、让你选择的同步服务去处理,就能完成同步。KeePassXC 官网它敢这么说,是因为设计与同步相安无事:保存时原子落盘,文件被外部改动后能察觉并提示重载。那种"人走程序关、打开自动重载"的应用,才配跑在同步工具上。

别同步的,是两端都在运行的程序的数据文件。SQLite、LevelDB、浏览器 profile 目录、各种缓存、运行中容器的数据卷。真要跨设备共享这类数据,换专门的复制工具(比如做 SQLite 复制的 litestream),或者听听维护者那句听着像玩笑、实际是正经建议的话:文件系统本身就是最好的键值存储之一——文件名是 key,文件内容是 value。

为什么 Syncthing 显示“同步完成”,应用读到的还是旧文件?HN 昨天引爆的地雷,与三类真不该同步的文件

已经踩坑的,三个补救动作

第一,立刻把问题文件夹改成"仅发送 / 仅接收"的单向模式,先止住数据库被来回覆盖。

第二,开启文件版本控制,阶段式(Staggered)就挺好,真弄坏了至少能从 .stversions 里回滚。

第三,改好机制后清理文件夹里的陈旧 .sync-conflict 冲突副本,别让旧冲突被应用当成"另一个版本"读回去。

为什么 Syncthing 显示“同步完成”,应用读到的还是旧文件?HN 昨天引爆的地雷,与三类真不该同步的文件

顺带两条相关信息

Syncthing 2.x 已经把自己的索引数据库也换成了 SQLite。同理,永远不要把 Syncthing 自己的数据目录放进别的同步文件夹——那等于对自己踩一遍同样的地雷。

版本方面,当前稳定版是 v2.1.3(8 月 5 日发布),v2.1.4 已于 8 月 24 日进入 RC2,正式版预计就在几天内。如果你机器的同步文件夹里正躺着"应用正在用的文件",趁现在盘点一遍,比等出了事故再排查便宜得多。

欢迎在评论区聊聊你用 Syncthing 同步过什么、踩过哪些坑,说不定能帮另一个人保住他的数据库。

内容由AI生成
0
扫一下,分享更方便,购买更轻松
0评论

当前文章无评论,是时候发表评论了
提示信息

取消
确认
评论举报

最新文章 热门文章