blockData vs IndexedDB_Memory_Storage 谁适合作为权威数据源
- Dominant language
- JavaScript
- Stars
- 1
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
从consolidation 的时候 blockData 被多次深拷贝带来的卡顿的问题分析到了权威数据源的多次全量拷贝带来的设计问题,接下来是深入的分析。
代码基线:
1ed3544cc5bb23f4e66693539686df2a0f21b949
## Trace 分析结论:consolidation 触发的持久化深拷贝是卡顿的主因
通过 Chrome Trace 分析,定位到一次删除方块后触发 consolidation 的完整卡顿链路:
### 单帧卡顿 1422ms
一个动画帧耗时 **1422ms**(正常 ≤16ms),FunctionCall 事件分解如下:
| 耗时 | 位置 | 说明 |
|------|------|------|
| 1422ms | `Game.js:425` | 游戏主循环帧(总) |
| 392ms | `IndexedDBUtils.js:58` `request.onsuccess` | IndexedDB 事务提交 + 内部 structuredClone |
| 376ms | `IndexedDBUtils.js:58` `request.onsuccess` | 同上 |
| 358ms | `WorkerRpcClient.js:15` `worker.onmessage` | Worker 返回 consolidation 结果后主线程处理 |
| 358ms | `PersistenceWorker.js:343` `self.onmessage` | PersistenceWorker 处理 saveRegionRecord |
| 172ms | `PersistenceWorker.js:343` `self.onmessage` | 同上 |
### 根因:blockData 被多次 structuredClone
在 consolidation → 持久化链路中,同一份 blockData 被克隆了 **3~4 次**:
```
主线程 WorldRuntime._flushDirtyChunks()
│
├─ ① _serializeBlockData() ← Map → 普通对象转换
│
└─ postMessage('applyRegionPatch')
│
└─ ② 浏览器内部 structuredClone ← postMessage 跨 Worker 传递
│
PersistenceWorker.self.onmessage (358ms)
│
├─ ③ structuredClone(region) ← PersistenceWorker.js:235 显式深克隆
│ 整份 region 对象,含所有 chunk 的 blockData
│
└─ saveRegionRecord → store.put({ data: record })
│
└─ ④ IndexedDB 内部 structuredClone ← 规范要求存储隔离
```
### 为什么明明是内存存储还这么慢?
**不是磁盘 I/O,是 structuredClone 的 CPU 开销**。structuredClone 对大型嵌套对象是 O(n) 的遍历和复制操作。一个 region 包含多个 chunk,每个 chunk 的 `blockData` 有几百到上千个坐标映射,clone 时每个 key-value 都要递归处理。
IndexedDB 规范要求在存储前对数据进行结构化克隆以保障数据隔离,所以即使你把数据存在内存里(无磁盘延迟),`store.put(largeObject)` 仍然会触发一次完整的 structuredClone。
### 优化方向
1. **去掉 `PersistenceWorker.js:235` 的显式 `structuredClone`**(P0)—— IndexedDB 内部本身就会 clone,显式 clone 是纯浪费。改为原地修改 region 缓存,写入失败再从 IndexedDB 回读恢复
2. **将 `_flushDirtyChunks` 的刷写与 consolidation 错峰**(P0)—— consolidation 完成后延迟再触发持久化,避免两者抢 CPU
3. **考虑使用 Transferable 传递 blockData**(P1)—— postMessage 时用 transfer list 转移 ArrayBuffer 所有权,避免跨线程 clone
4. **将大 region 粒度拆分**(P2)—— 减少单次 `store.put` 的数据量
-----------
为此我想将权威数据源统一到 blockData 身上,写了一个方案:
https://gist.github.com/jayli/da68916e68272032abe7cc7e79b5a146
还没有详细review这个方案的合理性
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the consolidation path in WorldRuntime._flushDirtyChunks(), IndexedDBUtils.js:58, PersistenceWorker.js:235 and :343, and the Chrome Trace findings at baseline 1ed3544cc5bb23f4e66693539686df2a0f21b949. Review the linked gist’s proposal for choosing blockData or IndexedDB_Memory_Storage as the authority, then validate the design and its performance impact before defining the resulting implementation scope.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- databases, performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100