来自 #112 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
⚠️ [重要 · 资源] storage/zstorage/memtable.go:267
问题根因:Close() 现在仅执行 close(m.stopCh) 并返回 nil,不再负责关闭任何资源(原 WAL 的 Close 已删除)。但 MemTable 持有 sst *SSTable(SSTable 内部有文件/元数据),以及 goroutine FlushWorker 和 ListenCompactCh 通过 stopCh 退出——这些 goroutine 的退出由 Close() 触发,但 Close() 不等待它们完成就返回了。若调用方在 Close 后立即退出进程或复用 MemTable 对象,可能存在 goroutine 仍在运行中而 SSTable 未释放的情况。
为什么低级解法不够:简单地补一个 m.sst.Close() 调用可以关闭 SSTable 文件句柄,但没有解决 goroutine 优雅退出的等待问题——FlushWorker 可能还在执行 Flush,此时关闭 SSTable 文件会导致 panic 或数据截断。
架构级方案:为 MemTable 定义清晰的生命周期状态机:Close() 应变为阻塞的 Shutdown() 或 Close(ctx) 方法,依次执行:(1) close(stopCh) 通知所有 goroutine 退出;(2) 等待它们退出(通过 sync.WaitGroup 追踪已启动的 goroutine);(3) 关闭 SSTable。当前实现中 FlushWorker 收到 stopCh 信号后直接 return,但正在执行的 Flush 中 m.sst.WriteToSSTable 可能被中断,这本身也是问题(SSTable 写入中途中断可能留下半写文件)。更彻底的方案:Shutdown 先 Drain FlushChan,完成最后一次 Flush,再关 stopCh。
代价/收益:代价是实现 Shutdown 需要引入 WaitGroup 和 drain 逻辑,代码复杂度上升;收益是 MemTable 生命周期边界清晰,不会在关闭后出现 goroutine 竞态或文件截断。考虑到当前项目以测试和构建验证为主,此问题在实际运行中由 Raft 层管理生命周期,短期影响有限,但作为架构问题应被记录。
storage/zstorage/memtable.go:267问题根因:
Close()现在仅执行close(m.stopCh)并返回nil,不再负责关闭任何资源(原 WAL 的 Close 已删除)。但 MemTable 持有sst *SSTable(SSTable 内部有文件/元数据),以及 goroutineFlushWorker和ListenCompactCh通过stopCh退出——这些 goroutine 的退出由Close()触发,但Close()不等待它们完成就返回了。若调用方在 Close 后立即退出进程或复用 MemTable 对象,可能存在 goroutine 仍在运行中而 SSTable 未释放的情况。为什么低级解法不够:简单地补一个
m.sst.Close()调用可以关闭 SSTable 文件句柄,但没有解决 goroutine 优雅退出的等待问题——FlushWorker 可能还在执行 Flush,此时关闭 SSTable 文件会导致 panic 或数据截断。架构级方案:为 MemTable 定义清晰的生命周期状态机:
Close()应变为阻塞的Shutdown()或Close(ctx)方法,依次执行:(1) close(stopCh) 通知所有 goroutine 退出;(2) 等待它们退出(通过 sync.WaitGroup 追踪已启动的 goroutine);(3) 关闭 SSTable。当前实现中 FlushWorker 收到 stopCh 信号后直接 return,但正在执行的 Flush 中m.sst.WriteToSSTable可能被中断,这本身也是问题(SSTable 写入中途中断可能留下半写文件)。更彻底的方案:Shutdown 先 Drain FlushChan,完成最后一次 Flush,再关 stopCh。代价/收益:代价是实现 Shutdown 需要引入 WaitGroup 和 drain 逻辑,代码复杂度上升;收益是 MemTable 生命周期边界清晰,不会在关闭后出现 goroutine 竞态或文件截断。考虑到当前项目以测试和构建验证为主,此问题在实际运行中由 Raft 层管理生命周期,短期影响有限,但作为架构问题应被记录。