1.

在开发 Pencel 的时候,一直以来,我都是使用 Bundle id 来区别 Debug 和 Release,虽然半懂不懂,但:

  • Bundle id 的区分碰巧能让本地的数据库隔离
  • Debug 构建使用 development entitlement,即使是同一个 CloudKit container 也能走 development 环境

所以完全没问题。

今天鬼使神差,想通过独立的 Scheme 来做这件事,于是就拆成了 PencelmacOSPencelmacOSDev 两个 Target。

PencelmacOSDev 仍然使用了独立的 Bundle id,PencelmacOS 则草率地沿用了 Release Bundle id (天真地以为开发环境下只要不编辑数据就行了),表面上看起来把 dev identity 显式定义得更清楚了,满足了我的强迫症。

2.

到这里的时候其实也无事发生,直到我 Build 了 PencelmacOS:生产环境和开发环境的本地缓存数据同时出现了 app 内。

原因是本地数据库是通过 Bundle id 来做路径上的隔离,所以此时相当于共用了数据库。

然后进一步的,由于 PencelmacOSPencelmacOSDev 没有做 iCloud container 隔离,都指向 dev CloudKit,所以 PencelmacOSDev 的数据又被同步到了 PencelmacOS 下的 debug 构建。

出于本能,我马上在 PencelmacOS 的 debug 构建下删除了混入的 PencelmacOSDev app 数据,而此时 PencelmacOSDev 构建下的数据也因为同步行为而被清空。

所以在我此刻的视角下,PencelmacOSDev 的数据直接被「转移」到了 PencelmacOS 下,没有意识到是同步行为,令我非常困惑。

3.

不知所措的我又去 CloudKit 下把 Dev 环境 Reset 了。即使还不知道这意味着什么。

不过马上就知道了,PencelmacOS 的 debug app 下立刻出现了同步失败,原因是:

  • 本地以为远端 record 还存在
  • 本地保存着 CKRecord system fields / changeTag
  • 上行时仍走旧的 record 路径
  • 但是远端为空,没有任何 record 所以无法上传

理论上,清掉 stale system fields 后,以原 UUID / recordName 重新 create / upsert,就能把本地数据重新发布到空的 dev 环境。但这件事涉及太多字段以及依赖顺序,手动触发并不可靠,所以最后让 Agent 补了「自愈」逻辑:

  • 把这类变更重新排队或改成可重新创建的上行
  • 给开发期 reset CloudKit 后恢复本地数据提供工具路径

4.

重新触发上传后,又暴露了一个设计缺陷:createdAt 没有作为业务字段同步到 CloudKit。

于是从云端重建本地库时,只能 fallback 到 CKRecord 的系统 creationDate;如果远端被清空后重新上传,这个时间就会变成重新创建 record 的时间。 好在还没有手贱到去 Reset 生产环境,现在补充了 createdAt 基本也不会带来风险:

  • 下行对旧 record 缺 createdAt 做了 fallback,不会 decode 失败
  • 新字段只影响后续上行 record 更完整

题外话,修复过程中,又顺手修复了大量的文件夹软删除记录没被清理的问题,增加了自动清理 30 天以上已经被软删除的文件夹的 GC。

5.

再次为本地构建用的 PencelmacOS 补上了独立的 Bundle id,这让我之前的所作所为看起来是纯粹的没事找事,不过能暴露出问题并修复,也不算没意义。

但是事件还没结束,在我推送到 TF 后,尝试测试清库后的回填行为,不知为何只能回填部分文件,但同时利用 cktool query-records 又能确认远端确实没有丢数据。 更无语的是,大概率是因为 dev 环境的数据 / 依赖状态和 Production 不完全一致,所以这在 debug 下无法复现。

最后通过 Archive 的产物来监测回填时的行为才发现:Document 先于 Folder 落库,Document 带着 parentFolderId,但本地还没有对应 Folder,于是 apply 失败。

这又是一个旧的实现缺陷,搞清楚后修复也不困难:

  • 下行顺序固定为 FolderAssetDocument
  • Folder 内部还要确保父文件夹先于子文件夹
  • Cold restore apply failure 不能静默当成功
  • 针对回填后部分失败的状态需要 Repair,再跑一次全量恢复

这算是一个 P0 问题,虽然日常使用大概率不会出现清库的行为。除此以外我也只能祈祷用户 (如果有的话) 在跨设备全量同步时没有遇到这个情况。

6.

作为门外汉,数据库相关的问题相比前端总是更苦手。

除了祈祷 AI 变得更强以外,多学习一些哪怕是常识,也是一个长期课题。