来自 #139 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
⚠️ [重要 · 内存] Scan 结果拷贝堆分配无上限管控 service/fsm.go:178
问题根因:Scan 在 out slice 中累积结果,每个命中条目做两次 append([]byte(nil), ...) 深拷贝。当扫描返回接近 maxScanResults=10000 条时,单次 Scan 的堆分配可达数百 KB 到数 MB(取决于 value 大小),且这些分配在 HTTP 协程/worker 中完成,没有背压或上下文感知的预算控制。
为什么低级解法不够:加个 maxScanResults 常量只是截断条数,不控制总字节。大 value(如相机帧 256KB)场景下 100 条就撑爆 25MB。
架构级方案:字节级预算 + 流式分页:(1) 给 Scan 响应也加上 MemTableMaxInflightBytes 类似的字节预算,累计命中字节超限即截断并返回部分结果+游标(cursor)让客户端续扫;(2) 或改为流式返回——边扫边发,不全部缓存在服务端。
代价/收益:游标/分页增加客户端复杂度;流式返回需要改写网络层为异步/分块。但这是边缘设备内存受限场景下的必要防御。
service/fsm.go:178问题根因:
Scan在outslice 中累积结果,每个命中条目做两次append([]byte(nil), ...)深拷贝。当扫描返回接近maxScanResults=10000条时,单次 Scan 的堆分配可达数百 KB 到数 MB(取决于 value 大小),且这些分配在 HTTP 协程/worker 中完成,没有背压或上下文感知的预算控制。为什么低级解法不够:加个
maxScanResults常量只是截断条数,不控制总字节。大 value(如相机帧 256KB)场景下 100 条就撑爆 25MB。架构级方案:字节级预算 + 流式分页:(1) 给 Scan 响应也加上
MemTableMaxInflightBytes类似的字节预算,累计命中字节超限即截断并返回部分结果+游标(cursor)让客户端续扫;(2) 或改为流式返回——边扫边发,不全部缓存在服务端。代价/收益:游标/分页增加客户端复杂度;流式返回需要改写网络层为异步/分块。但这是边缘设备内存受限场景下的必要防御。