refactor: 参数改注入,切断存储层与热路径对全局配置的依赖 - #288
Conversation
storage 此前在包内直接读 config.G:全局配置让同一进程无法并存两套不同配置的引擎, 也让测试之间经由全局变量互相影响——本包正因如此出现过偶发失败。它还迫使生产代码写 防御性代码:sstable.go 与 engine.go 都「构造时从 config 快照一份」,注释写明是为了避开 与测试中并发改配置形成的数据竞争,即生产代码在为测试的副作用打补丁。 新增 Options,由 NewEngine / NewSSTable 在构造时接收;DefaultOptions() 是全局配置进入 存储层的唯一入口,调用方(service、cmd/ban-ingest)读一次后引擎只认自己那份参数。 storage 包内除 options.go 外不再 import config。 顺带修掉两个包级变量:maxLevel 与 probability 此前在 import 时取自全局配置,既无法按 实例配置,也让测试里设 MaxMemTableLevel 成为静默的空操作。改为 SkipList 自带层高与 升层概率,构造时固定。 测试全部改为传入独立 Options:storage 测试中修改全局配置的地方由 74 处降为 0,用例之间 不再有隐式耦合。newBareMemTable 同样改为接收 Options(此前它漏设 opts,Flush 交换时会用 零值层高建表)。 全量 go test -race ./... 连跑 3 次通过。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
每读一帧都要访问两次可变全局状态:DataPack.UnPack 读两次 config.G.MaxPackageSize, StartReader 再读一次 config.G.WorkerPoolSize 决定投递方式。 帧长上限本就是策略而非编解码:移到连接侧、在读取负载之前执行,编解码器因而保持无状态。 worker 池的选择改为构造时快照到 Connection。bannet 剩余的配置读取全部位于构造期或每次 accept 时,每帧路径上已无全局状态。 移动执行点必须有测试守着,否则一个损坏或恶意的帧头就能让服务端按对端声称的长度分配内存。 新增用例:只发一个声称 64MiB 负载的帧头、不发负载,断言服务端在读取负载前即断开连接, 且该帧未被分派到业务处理。已变异验证——去掉上限判断后用例失败(服务端果然一直等着)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (24)
📝 WalkthroughWalkthroughThe PR adds per-instance storage options and updates storage constructors, production callers, tests, and benchmarks. It also snapshots connection settings and rejects oversized frames before payload allocation. ChangesStorage configuration isolation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🐯 BanGD 数据库内核评审整体风险:🟡 中 变更总结:这是一个结构性重构 PR,目标是把存储层与 bannet 网络层从对全局可变单例
架构问题(共 3 项)
普通问题(共 2 项)💡 [建议 · 测试可靠性]
💡 [建议 · 资源使用]
本次评审消耗 token:共 294198 tokens(输入 274985,输出 6029,缓存命中 13184,缓存写入 0)|维度 [concurrency, memory, lock, storage, performance]|补充阅读周边文件 [bannet/message.go]|对抗式复核 3 票/条,过滤疑似误报 1 条 |
最后一项结构问题:
config.G这个全局可变单例。为什么它是问题(证据,不是偏好)
生产代码已经在为它打补丁。
sstable.go与engine.go都写着「构造时从 config 快照一份」,注释直言是为了避开「与(测试中)并发修改全局配置形成的数据竞争」——生产代码在为测试的副作用做防御,这是最强的信号。它已经造成过故障。 本仓库此前的
TestEngine_*偶发失败,正是全局配置 + 各用例后台协程共享造成的。两个包级变量甚至是静默失效的:
maxLevel/probability在 import 时取自全局配置,于是测试里设MaxMemTableLevel一直是空操作。它还挡住了集成测试:同进程内无法并存两套不同配置的引擎,而多节点测试正需要。
改法
新增
storage.Options,由NewEngine/NewSSTable构造时接收。DefaultOptions()是全局配置进入存储层的唯一入口——调用方读一次,引擎此后只认自己那份参数。config.Goptions.goconfig.Gstorage包 import config跳表也不再依赖包级变量:
SkipList自带层高与升层概率,构造时固定。测试全部改为传入独立Options,连带删掉大量old := config.G.X; defer 还原的样板。热路径上的全局读取
bannet的测试一处都没改过全局配置,所以「测试污染」这条论据对它不成立。但它有另一个问题:每读一帧都在访问可变全局状态——UnPack读两次MaxPackageSize,StartReader再读一次WorkerPoolSize。帧长上限本就是策略而非编解码:移到连接侧、在读取负载之前执行,编解码器因而保持无状态;worker 池选择改为构造时快照。bannet 剩余的配置读取全部在构造期或每次 accept 时。
移动执行点必须有测试守着——否则一个损坏或恶意的帧头就能让服务端按对端声称的长度分配内存。新增用例:只发一个声称 64MiB 负载的帧头、不发负载,断言服务端在读取负载前即断开、且该帧未进入业务处理。已变异验证:去掉上限判断后用例失败(服务端果然一直等着那 64MiB)。
未改动的部分及理由
raft只有 2 处读取且都在构造期,测试也不改全局配置——收益不足以抵改动面。service有 24 处读取、其测试 34 处改配置。但它是编排层、紧邻程序入口,读取进程配置正是其职责;参数化它需要一路改到NewKVServer签名与全部集成测试,属独立一轮工作。验证
go build ./...(含-tags pprof)、go vet ./...、gofmt全绿;go test -race ./...连跑多次通过;scripts/bench.sh实跑通过。🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Improvements