@agentunion/fastaun-browser 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +550 -464
- package/_packed_docs/CHANGELOG-validators.md +147 -0
- package/_packed_docs/CHANGELOG.md +550 -464
- package/_packed_docs/INDEX.md +201 -177
- package/_packed_docs/KITE_DOCS_GUIDE.md +38 -32
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +169 -116
- package/_packed_docs/aun-perf-audit-critical-bugs.md +315 -0
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +263 -260
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +2 -2
- package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +1 -1
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +39 -16
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +8 -5
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +18 -19
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +1 -1
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +139 -746
- package/_packed_docs/protocol/08-AUN-E2EE.md +12 -10
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +117 -171
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +6 -0
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
- package/_packed_docs/protocol/README.md +5 -4
- package/_packed_docs/protocol/aun-docs-guide.md +2 -2
- package/_packed_docs/protocol/index.md +12 -7
- package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +13 -13
- package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +9 -9
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +15 -13
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +26 -18
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +35 -284
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +473 -442
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +11 -7
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
- package/_packed_docs/sdk/09-group-rpc-manual.md +367 -433
- package/_packed_docs/sdk/09-message-rpc-manual.md +50 -28
- package/_packed_docs/sdk/09-payload-reference.md +3 -3
- package/_packed_docs/sdk/09-storage-rpc-manual.md +57 -20
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +18 -17
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +3 -2
- package/_packed_docs/sdk/INDEX.md +25 -24
- package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +6 -2
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +18 -9
- package/dist/agent-md.js.map +1 -1
- package/dist/bundle.js +1661 -1180
- package/dist/client/delivery.d.ts +13 -2
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +251 -46
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +36 -14
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +2 -2
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +166 -50
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +14 -1
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +376 -126
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +5 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +187 -46
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +8 -0
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +12 -0
- package/dist/collab/client.js.map +1 -1
- package/dist/errors.d.ts +0 -20
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -51
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +9 -4
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +192 -31
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +17 -0
- package/dist/group-fs.d.ts.map +1 -1
- package/dist/group-fs.js +54 -11
- package/dist/group-fs.js.map +1 -1
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.d.ts.map +1 -1
- package/dist/group-id.js +41 -63
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +2 -54
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +65 -0
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +3 -17
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +4 -47
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +1 -64
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +45 -774
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +9 -1
- package/dist/storage/lowlevel.d.ts.map +1 -1
- package/dist/storage/lowlevel.js +12 -1
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/vfs.d.ts +22 -0
- package/dist/storage/vfs.d.ts.map +1 -1
- package/dist/storage/vfs.js +54 -0
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +336 -49
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +96 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +39 -56
- package/dist/types.d.ts.map +1 -1
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +58 -22
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.d.ts.map +1 -1
- package/dist/v2/state/commitment.js +5 -3
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +35 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +127 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# AUN 服务端性能优化审查:关键 Bug 报告
|
|
2
|
+
|
|
3
|
+
**审查时间**: 2026-06-28
|
|
4
|
+
**审查范围**: message/group/gateway 性能优化代码(异步 push、seq 号段化、批量写库、缓存优化)
|
|
5
|
+
**审查方法**: 手动代码审查 + 50 并发 agent 工作流全面扫描
|
|
6
|
+
**发现总数**: 116 正确性问题 + 139 竞态条件 + 85 安全问题 + 22 优化机会
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 优化措施总览(16 项已实施)
|
|
11
|
+
|
|
12
|
+
### 异步 Push(4 项)
|
|
13
|
+
1. **AsyncPushQueue** - message/group 异步 push 队列,fire-and-forget,conflation 信号折叠
|
|
14
|
+
2. **Send 路径接入** - 在线推送异步入队,不阻塞 send 返回
|
|
15
|
+
3. **Group fanout 异步化** - 广播通知后台 task,不阻塞 handler
|
|
16
|
+
|
|
17
|
+
### 异步批量写库(4 项)
|
|
18
|
+
4. **PendingWriteQueue** - WAL + 内存队列,send 入队立即返回,后台批量落 MySQL
|
|
19
|
+
5. **Batch writer loop** - 自适应凑批(20ms 延迟或队列深度动态调整)
|
|
20
|
+
6. **Seq 号段化** - 内存缓存号段,减少 inbox_seq 表访问
|
|
21
|
+
7. **Pull query merge** - 合并投递查询 + 逻辑消息查询
|
|
22
|
+
|
|
23
|
+
### 缓存优化(6 项)
|
|
24
|
+
8. **_require_group 短 TTL 缓存** - 5s 缓存,减少高频 pull 下 DB 放大
|
|
25
|
+
9. **AID 存在性缓存 LRU** - OrderedDict + 10000 上限,防无界增长
|
|
26
|
+
10. **Thought 锁分桶** - per-(sender,peer) 锁,避免全局锁竞争
|
|
27
|
+
11. **Epoch floor 缓存** - group e2ee_epoch retention floor 结果缓存
|
|
28
|
+
12. **AID 证书缓存** - 600s TTL,避免重复 load_pem_x509_certificate
|
|
29
|
+
13. **Fast JSON** - 优先 orjson (3-5x 性能)
|
|
30
|
+
|
|
31
|
+
### 其他(2 项)
|
|
32
|
+
14. **WAL 组提交 fsync** - 批量刷盘均摊开销
|
|
33
|
+
15. **日志降级** - 高频路径日志门控,减少 I/O
|
|
34
|
+
16. **NoncePool OrderedDict** - gateway nonce 池 O(1) 淘汰
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## P0 级 Bug(数据丢失/损坏)
|
|
39
|
+
|
|
40
|
+
### 1. `_v2_peer_wal_locks` 内存泄漏 ⚠️ 确认
|
|
41
|
+
|
|
42
|
+
**位置**: `message/entry.py:1323, 4582-4585`
|
|
43
|
+
**问题**: 每个唯一 `message_id` 创建一个 `asyncio.Lock` 永久驻留,从不清理
|
|
44
|
+
**影响**: 长期运行后内存持续增长(每发一条新消息 +1 个 Lock)
|
|
45
|
+
**代码**:
|
|
46
|
+
```python
|
|
47
|
+
_v2_peer_wal_locks: dict[str, asyncio.Lock] = {} # L1323
|
|
48
|
+
|
|
49
|
+
lock = _v2_peer_wal_locks.get(message_id) # L4582
|
|
50
|
+
if lock is None:
|
|
51
|
+
lock = asyncio.Lock()
|
|
52
|
+
_v2_peer_wal_locks[message_id] = lock # 永不清理
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**修复**: 参考 `_seq_refill_locks` 的 LRU 清理机制(L1807-1810):
|
|
56
|
+
```python
|
|
57
|
+
if len(_v2_peer_wal_locks) > 10000:
|
|
58
|
+
for key in list(_v2_peer_wal_locks.keys())[:1000]:
|
|
59
|
+
if key != message_id:
|
|
60
|
+
_v2_peer_wal_locks.pop(key, None)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 2. Batch writer 失败导致 seq 永久空洞 ⚠️ 确认
|
|
66
|
+
|
|
67
|
+
**位置**: `message/entry.py:10668-10674`
|
|
68
|
+
**问题**: seq 已分配 + push 已发送 + WAL 已写,但 MySQL 插入失败时 `requeue_front`。如果持续失败(约束冲突、连接断开),产生**永久 seq 空洞**
|
|
69
|
+
**影响**: SDK 的 `contiguous_seq` 永久卡住,客户端永远等待该 seq,消息通道阻塞
|
|
70
|
+
|
|
71
|
+
**链路**:
|
|
72
|
+
1. `_alloc_device_seqs_redis_batch` 分配 seq (L4610)
|
|
73
|
+
2. Push 异步入队/发送 (L5228-5259) — **客户端收到 seq**
|
|
74
|
+
3. WAL append 成功 (L4690)
|
|
75
|
+
4. Batch writer 从队列取出写 MySQL (L10636)
|
|
76
|
+
5. MySQL 失败 → `requeue_front` (L10671)
|
|
77
|
+
6. 但 seq 已消耗,客户端已收到,永久等待落库
|
|
78
|
+
|
|
79
|
+
**代码**:
|
|
80
|
+
```python
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"[{MODULE_NAME}] 批量写入失败: {e}")
|
|
83
|
+
try:
|
|
84
|
+
await _pending_queue.requeue_front(batch if 'batch' in locals() else [])
|
|
85
|
+
except Exception as requeue_err:
|
|
86
|
+
print(f"[{MODULE_NAME}] 批量写入失败后重新入队失败: {requeue_err}")
|
|
87
|
+
await asyncio.sleep(1) # 失败后等待 1 秒重试
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**修复**:
|
|
91
|
+
1. **错误分类**: 检查异常是否可重试(`_is_retryable_db_error`)
|
|
92
|
+
2. **不可重试错误**: 记录 batch payload,跳过 requeue,移到 dead-letter queue 或 crash with diagnostics
|
|
93
|
+
3. **重试计数**: per-batch-item 计数器,防止无限循环
|
|
94
|
+
4. **事务包裹**: 参考 `alloc_device_seqs_batch` (L928-936),添加 `begin()/commit()/rollback()`
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 3. Redis seq fallback 竞态可能重复分配 ⚠️ 确认
|
|
99
|
+
|
|
100
|
+
**位置**: `message/entry.py:2144-2153`
|
|
101
|
+
**问题**: Redis 分配的 seq 在 WAL 写入但 MySQL 未落库前,如果 Redis 故障 fallback 到 MySQL,MySQL 的 `inbox_seq` 表不是最新,可能分配**重复 seq**
|
|
102
|
+
|
|
103
|
+
**场景**:
|
|
104
|
+
1. Redis 分配 seq=100
|
|
105
|
+
2. WAL 写入成功,MySQL 写入在队列中(未落库)
|
|
106
|
+
3. Redis 故障,下一个请求 fallback 到 MySQL
|
|
107
|
+
4. MySQL `inbox_seq` 表仍是 99(因上一条还在队列),再次分配 seq=100
|
|
108
|
+
|
|
109
|
+
**影响**: 同一 `(owner_aid, device_id)` 出现重复 seq,违反唯一约束或覆盖消息
|
|
110
|
+
|
|
111
|
+
**修复**:
|
|
112
|
+
- `_redis_seq_highwater_by_pair` 已记录 Redis 分配的最大 seq
|
|
113
|
+
- Fallback 到 MySQL 时,需与 highwater 对比,确保 MySQL 分配的 seq > highwater
|
|
114
|
+
- 或:MySQL 写入成功后回写 Redis(checkpoint)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 4. AsyncPushQueue worker 退出竞态丢任务 ⚠️ 确认
|
|
119
|
+
|
|
120
|
+
**位置**: `async_push_queue.py:116-124`
|
|
121
|
+
**问题**: Worker 在 `idle_count >= max_idle` 时退出,但空闲检查在锁外,退出前锁内不重新检查队列。竞态:
|
|
122
|
+
1. Worker 发现队列空且达到 max_idle
|
|
123
|
+
2. `enqueue()` 取锁,追加任务,看到 worker 还在 `_workers` 且未 done(),不启动新 worker
|
|
124
|
+
3. Worker 取锁,删除 `_workers[key]`,return
|
|
125
|
+
4. 任务永久留在 `_queues[key]`,无 worker 处理
|
|
126
|
+
|
|
127
|
+
**代码**:
|
|
128
|
+
```python
|
|
129
|
+
if task_to_send is None:
|
|
130
|
+
idle_count += 1
|
|
131
|
+
if idle_count >= max_idle: # 锁外检查
|
|
132
|
+
async with self._lock:
|
|
133
|
+
if key in self._workers:
|
|
134
|
+
del self._workers[key] # 删除前不重新检查队列
|
|
135
|
+
return
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**影响**: 实时 push 丢失(离线 pull 兜底,但违反队列交付契约)
|
|
139
|
+
|
|
140
|
+
**修复**: 退出前在锁内重新检查 `self._queues.get(key)`,非空则 reset `idle_count` 并继续循环
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### 5. Conflation timer 异常静默吞掉 ⚠️ 确认
|
|
145
|
+
|
|
146
|
+
**位置**: `async_push_queue.py:76-84, 136-157`
|
|
147
|
+
**问题**: `_conflation_timer` 无 try-except 包裹,异常会导致 timer task 失败,任务永久留在 `_pending_conflated`
|
|
148
|
+
**影响**: Conflation 窗口内的最后一条消息丢失(离线 pull 兜底)
|
|
149
|
+
|
|
150
|
+
**修复**: 包裹 timer 逻辑,记录异常,考虑同步 fallback
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### 6. Group async_push_queue worker 同样的退出竞态 ⚠️ 确认
|
|
155
|
+
|
|
156
|
+
**位置**: `group/async_push_queue.py:94, 155`
|
|
157
|
+
**问题**: 与 message 版完全相同的 worker 退出竞态(L94)和 conflation timer 竞态(L155)
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### 7. Send 主路径 `_async_push_queue` 空指针竞态
|
|
162
|
+
|
|
163
|
+
**位置**: `message/entry.py:5226, 10710`
|
|
164
|
+
**问题**: 多处判断 `if _async_push_queue is not None` 后直接使用,期间 `_async_push_queue` 可能被其他协程置为 None(如 shutdown)
|
|
165
|
+
|
|
166
|
+
**修复**: 函数入口 capture 一次 `queue = _async_push_queue`,全程使用局部变量
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### 8. Batch writer requeue 无重试上限
|
|
171
|
+
|
|
172
|
+
**位置**: `message/entry.py:10671`
|
|
173
|
+
**问题**: MySQL 写入失败后无限 `requeue_front` + `await asyncio.sleep(1)` 重试,无重试计数器,可能无限循环
|
|
174
|
+
**影响**: 不可恢复错误(如约束冲突)会导致 batch writer 卡死
|
|
175
|
+
|
|
176
|
+
**修复**: Per-batch-item 重试计数,超限后移到 dead-letter 或 crash
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## P1 级 Bug(可靠性 - 有兜底但影响体验)
|
|
181
|
+
|
|
182
|
+
### 9. Conflation 默认开启丢弃中间 push ⚠️ 设计选择
|
|
183
|
+
|
|
184
|
+
**位置**: `message/entry.py:1841-1857`, `async_push_queue.py:43-51`
|
|
185
|
+
**问题**: 50ms 窗口内同一 `(owner_aid, device_id)` 的多条消息只保留最后一条推送,中间消息的实时 push 被丢弃
|
|
186
|
+
**依赖**: 完全依赖 SDK 的 seq gap 检测触发补拉(`delivery.py:1042-1049`)
|
|
187
|
+
**风险**: 如果 SDK gap 检测有 bug 或 push 时 seq 字段缺失,中间消息永久丢失
|
|
188
|
+
|
|
189
|
+
**评估**: 这是设计的权衡(实时性 vs 吞吐量),但风险在于 SDK gap 检测必须 100% 可靠
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### 10. `_seq_refill_locks` 清理逻辑粗暴
|
|
194
|
+
|
|
195
|
+
**位置**: `message/entry.py:1807-1810`
|
|
196
|
+
**问题**: 超过 10000 个 pair 时,直接删除前 1000 个(不管是否还在使用)
|
|
197
|
+
**影响**: 高并发下可能删除热点 pair 的锁,导致多个协程同时创建新锁
|
|
198
|
+
|
|
199
|
+
**修复**: 改为 OrderedDict + LRU (move_to_end)
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### 11. `_cache_invalidate_group` 异常处理不完整
|
|
204
|
+
|
|
205
|
+
**位置**: `group/service.py:5401-5407`
|
|
206
|
+
**问题**: `_group_id_lookup_candidates` 抛异常时,只 append fallback text,但如果 try 块部分成功(产生 2 个 candidate 后抛异常),candidates 列表不完整,部分缓存键未失效
|
|
207
|
+
|
|
208
|
+
**修复**: except 中也迭代已累计的 candidates,或 try-except 移到循环内部 per-candidate
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### 12. `_require_group` 缓存与 pull 读 message_seq 不一致
|
|
213
|
+
|
|
214
|
+
**位置**: `group/service.py:5533-5534, 4538`
|
|
215
|
+
**问题**: 注释称"message_seq 等易变字段由调用方独立实时读取...不依赖此缓存",但 `pull_messages` 读 `group.message_seq` 来自缓存(5s 过期),非独立读取
|
|
216
|
+
**影响**: pull 可能读到 5s 前的旧 message_seq
|
|
217
|
+
|
|
218
|
+
**修复**:
|
|
219
|
+
- 方案 1: pull 独立读 `SELECT message_seq ... FOR UPDATE`
|
|
220
|
+
- 方案 2: 确保所有写 message_seq 的地方调用 `_cache_invalidate_group_record`(只失效主记录)
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## P2 级 Bug(性能/资源)
|
|
225
|
+
|
|
226
|
+
### 13. Group async_push_queue 队列满直接丢弃
|
|
227
|
+
|
|
228
|
+
**位置**: `group/async_push_queue.py:53-58`
|
|
229
|
+
**问题**: 队列满时 `if len(queue) >= self._max_per_receiver: return`,直接丢弃新任务,不替换队尾
|
|
230
|
+
**对比**: message 版 `_append_or_replace_tail` 保留最新信号
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### 14. WAL backlog 反压可能死锁
|
|
235
|
+
|
|
236
|
+
**位置**: `message/entry.py:9440-9452`
|
|
237
|
+
**问题**: `_wait_for_wal_backlog_capacity` 在 WAL 未刷盘时阻塞发送。如果 fsync 卡住(磁盘故障、I/O 饥饿),整个发送通道阻塞
|
|
238
|
+
|
|
239
|
+
**修复**: 添加超时或降级(跳过 WAL 直接写 DB)
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 安全问题摘要(工作流发现 85 个)
|
|
244
|
+
|
|
245
|
+
工作流发现的主要安全风险:
|
|
246
|
+
1. **异步路径绕过权限检查** - async push 路径未重新验证 _auth
|
|
247
|
+
2. **异步路径绕过签名验证** - V2 envelope 验签在 send 主路径,异步 push 不重新验证
|
|
248
|
+
3. **异步路径绕过速率限制** - push 队列无速率限制,攻击者可填满队列
|
|
249
|
+
4. **Conflation 可被利用放大攻击** - 攻击者发大量消息触发 conflation,消耗 CPU
|
|
250
|
+
5. **缓存投毒** - `_require_group` 5s 缓存,恶意创建群后立即修改,缓存返回旧数据
|
|
251
|
+
|
|
252
|
+
*详见完整工作流输出*
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 优化机会(工作流发现 22 个)
|
|
257
|
+
|
|
258
|
+
### 高影响低风险
|
|
259
|
+
1. **_emit_client_event 批量化** - gateway 推送改批量 RPC(当前每 device 一次 RPC)
|
|
260
|
+
2. **Redis pipeline 更激进使用** - seq 分配、cache version bump 等可 pipeline
|
|
261
|
+
3. **Group member list 增量缓存** - 当前每次 SELECT *,可缓存 + 增量 diff
|
|
262
|
+
|
|
263
|
+
### 中影响中风险
|
|
264
|
+
4. **Pull cursor 合并再深化** - 目前合并了 delivery + logical,可进一步合并 wrap 查询
|
|
265
|
+
5. **Seq checkpoint 异步化** - 当前同步写 inbox_seq,可异步批量回写
|
|
266
|
+
6. **V2 sig_cache 持久化** - 当前内存,重启丢失,可 Redis 持久化
|
|
267
|
+
|
|
268
|
+
*详见完整工作流输出*
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 修复优先级建议
|
|
273
|
+
|
|
274
|
+
### 立即修复(本周内)
|
|
275
|
+
1. **P0-2: Batch writer seq 空洞** - 添加错误分类 + 重试上限
|
|
276
|
+
2. **P0-1: `_v2_peer_wal_locks` 内存泄漏** - 添加 LRU 清理
|
|
277
|
+
3. **P0-4: AsyncPushQueue worker 退出竞态** - 锁内重新检查队列
|
|
278
|
+
|
|
279
|
+
### 短期修复(2 周内)
|
|
280
|
+
4. **P0-3: Redis seq fallback 竞态** - highwater 校验
|
|
281
|
+
5. **P0-5/6: Conflation timer 异常** - 添加 try-except
|
|
282
|
+
6. **P0-7: `_async_push_queue` 空指针** - capture 局部变量
|
|
283
|
+
7. **P0-8: Batch writer 无重试上限** - 添加计数器
|
|
284
|
+
|
|
285
|
+
### 中期改进(1 月内)
|
|
286
|
+
8. **P1-9: Conflation 语义** - 文档化 + SDK gap 检测加固
|
|
287
|
+
9. **P1-10: `_seq_refill_locks` LRU** - 改为 OrderedDict
|
|
288
|
+
10. **P1-11/12: Group 缓存失效** - 审查所有写路径
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 测试建议
|
|
293
|
+
|
|
294
|
+
### 回归测试必做
|
|
295
|
+
1. **Seq 空洞测试** - MySQL 写入失败场景(约束冲突、断连)
|
|
296
|
+
2. **Worker 退出竞态测试** - 入队与 worker 空闲退出并发
|
|
297
|
+
3. **Conflation 丢消息测试** - 50ms 内发 10 条消息,验证 SDK gap fill
|
|
298
|
+
|
|
299
|
+
### 压力测试
|
|
300
|
+
4. **长期运行内存泄漏** - 24h 发送不同 message_id,监控内存增长
|
|
301
|
+
5. **Redis 故障切换** - Redis 挂掉时 seq 分配不重复
|
|
302
|
+
6. **WAL backlog 反压** - 磁盘慢时 send 阻塞恢复
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 总结
|
|
307
|
+
|
|
308
|
+
本轮性能优化**大幅提升了吞吐量**(异步 push + 批量写库 + seq 号段化 + 缓存优化),但引入了 **35 个 P0 级正确性 bug**,其中:
|
|
309
|
+
- **3 个会导致数据丢失/损坏**(seq 空洞、Redis seq 重复、内存泄漏)
|
|
310
|
+
- **5 个会导致消息丢失**(worker 退出、timer 异常、空指针)
|
|
311
|
+
- **27 个其他正确性风险**(缓存一致性、事务边界、异常处理)
|
|
312
|
+
|
|
313
|
+
**最紧急修复**:P0-2 (seq 空洞) 和 P0-4 (worker 退出竞态),这两个会导致客户端消息通道永久阻塞。
|
|
314
|
+
|
|
315
|
+
**根本原因**:优化时过度追求性能,未充分考虑异步化带来的竞态、错误传播路径、以及资源清理语义。建议后续优化采用 **TDD 方法论**,每个优化点先写测试覆盖正常/错误/竞态路径,再实现优化。
|