第 11 章:冷启动快照与持久化
1. 为什么 native Home 需要 display snapshot
钱包 Home 的实时数据很多:Portfolio、Market、Banner、History、NFT、DeFi、Perps、能力拓扑。冷启动时如果等所有 API 重新完成,native container 只能显示 loading skeleton,首屏会慢且几何不稳定。
项目选择把“可展示的最小投影”持久化下来:它不是完整业务状态,也不是可以执行旧 command 的缓存,而是按 owner 分区的 display snapshot。
2. 存储结构
快照协议在 homeDisplaySnapshotTypes.ts 中定义:
route(ownerScopeKey)
├── currentGeneration
├── previousGeneration
└── partitionId
manifest(partitionId, generation)
├── critical chunk
├── banner chunk
├── portfolio chunk
├── market chunk
└── optional source chunks关键常量:
| 常量 | 值 | 意义 |
|---|---|---|
HOME_DISPLAY_SNAPSHOT_SCHEMA_VERSION | 1 | snapshot 数据版本 |
HOME_DISPLAY_SNAPSHOT_MAX_CHUNK_BYTES | 1 MiB | 单 source chunk 上限 |
HOME_DISPLAY_SNAPSHOT_MAX_CRITICAL_BYTES | 128 KiB | 首屏 critical chunk 上限 |
HOME_DISPLAY_SNAPSHOT_MAX_ROUTES | 16 | route index 的边界 |
| native storage max record | 2 MiB | native 存储工厂限制 |
3. 为什么拆成 critical + source chunks
critical 保存 shell/navigation 等能够尽快决定页面形状的内容;source chunks 保存 banner、portfolio、market 等按来源可独立读取的数据。
loadPreparedHomeDisplaySnapshot.native.ts 还会拒绝仅有 navigation、没有可渲染 portfolio data 的 snapshot;shell.kind === 'loading' 也会被当作 cache miss。缓存命中不是“有 JSON 就算成功”,而是必须足够构造可见首屏。
4. Projection 与 restore 的边界
homeDisplaySnapshotData.ts 有两套方向:
projectHomeDisplaySnapshotRecord:从运行时 payload 提取允许持久化的字段,删掉不应落盘的运行时/隐藏细节;restoreHomeDisplaySnapshotRecord:从磁盘恢复默认结构,再叠加 persisted 字段。
Portfolio 和 Market 还会重新计算 row ids;banner/DeFi/NFT/History/Perps 通过 source-specific snapshot keys 做 allowlist。这是“持久化的是展示投影,不是把整个 Store JSON dump 到磁盘”。
5. generation 与 previous generation
route 先指向新 generation,manifest 再引用 chunks。若新 generation 写到一半失败,读取 route 后可以尝试 previous generation;只有完整 manifest、字节长度、schema、owner、partition、generation 和 content signature 都匹配时才接受。
这种双 generation 设计把“部分写入”降级为旧缓存命中,而不是半个 snapshot 被当作新数据。
6. persist queue 的取舍
homeDisplaySnapshotPersistQueue.ts 测试覆盖了:
- 快速连续 commit 合并成一次物理 generation;
- unchanged chunks 通过 content signature 复用;
- 仅 selected tab 改变时不重新写一整代数据;
- owner partition 相互隔离;
- 并发 owner write 会重试 route index;
- confirmed header 不被 pending/unavailable snapshot 覆盖;
- superseded chunk 即使原 manifest 已退休也会清理。
它本质上是一个“小型 append/commit protocol”,目标不是数据库级事务,而是让 Home 首屏在 crash、切账户、快速刷新和频繁状态变化下保持可恢复。
7. 缓存不能提供什么
快照不提供最新余额、不提供授权、不提供旧 command 的可执行性。Renderer 必须把缓存当 display seed,实时 Store 和当前 owner 才是最终真相。