jayli / jayli/mc-lite

blockData vs IndexedDB_Memory_Storage 谁适合作为权威数据源

Open
#74 7 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.