Skip to content

🐯 [性能] ScanRange 每行全量 JSON 反序列化在热路径上 · memtable.go #140

Description

@github-actions

来自 #139 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。

⚠️ [重要 · 性能] ScanRange 每行全量 JSON 反序列化在热路径上 storage/zstorage/memtable.go:163

问题根因:KVServer.Scan (service/fsm.go:177) 在回调中调用 pred.Eval(value),后者对每条命中的 value 执行 json.Unmarshal(value, &obj) 全量反序列化。在扫描窗口大(如最近 10 秒数千帧)且谓词命中率高时,每条都做 JSON 解析,而整个扫描路径已持 MemTable 读锁,JSON 解析延迟会放大锁持有时间,降低写入并发。

为什么低级解法不够:将谓词改为先解析 JSON 再比较、或在回调外批量解析,都不改变「每行都做全量 JSON 反序列化」这一结构性开销。通用评审者可能会建议「缓存解析结果」,但在 MemTable 不做持久化、写入时不对 value 做 JSON 校验的设计下,缓存命中率极低。

架构级方案:在 storage 层引入可选字段索引 / 投影物化:若某设备常按 az 查询,可在写入时预提取 az 字段值存入固定的元数据槽(per-entry 的 sidecar float64),ScanRange 扫描时先比较 sidecar 再决定是否需要完整 JSON 反序列化。或者,在 Predicate.Eval 中采用惰性 JSON 解析:先尝试用 json.RawMessage 扫描目标字段(json.Decoder 的 Token API 可跳过非目标字段)而非 Unmarshal 整个对象,大幅降低 O(n) 开销。

代价/收益:代价:sidecar 方案增加每 entry 约 8 字节(float64)的存储开销 + 写入路径的额外提取逻辑;惰性解析方案引入稍复杂的 JSON 解析代码。收益:扫描热路径上 JSON 开销从 O(fields) 降到 O(1)(sidecar)或降到仅扫描目标字段(惰性 Token API),锁持有时间缩短,写入吞吐不受扫描拖累。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions