恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

BadgerDB与sfsDb事务性能对比:LSM与段式存储的选型启示

  • 首页
  • 资讯中心
  • /
  • BadgerDB与sfsDb事务性能对比:LSM与段式存储的选型启示

相关资讯

Intel核显QSV硬件加速转码实战:Ubuntu下ffmpeg完整配置指南 2026/10/19 10:43:52
GDB与LLDB调试实战:从core dump到内存问题的排查技巧 2026/10/19 10:43:52
Docker实战:从环境配置到Web安全靶场部署全流程 2026/10/19 10:43:52

最新资讯

CNN并行计算全解析:数据并行、模型并行与张量并行的实战策略
AI数字员工实战:用Moltbot搭建自动化工作流的完整指南
RNN、LSTM、BiLSTM三部曲:从原理到实战的序列建模指南
GitHub周榜风向:AI代码审查、向量检索与本地优先工具解析
深度学习中的CNN并行计算:四大并行策略、工程实现与避坑指南
软件I2C模拟16位寄存器地址的读写实现与调试指南

今日推荐

OpenCowork 实测:支持本地文件、飞书机器人的 Windows AI 助手(只需配置 Token)
GPT-6 Sol 与 Luna 怎么选?Chat、Work、Codex 版本差异与 TaoToken 接入实测
openclaw.net 集成 ElBruno.MempalaceNet 记忆系统:把 MCP 配置改到 TaoToken 的完整实践

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

BadgerDB与sfsDb事务性能对比:LSM与段式存储的选型启示

发布时间:2026/10/19 10:43:52
BadgerDB与sfsDb事务性能对比:LSM与段式存储的选型启示 前一阵我需要在一个数据量不算大的项目里同时评估两款键值存储引擎业务场景以事务性写入为主每条记录几十到几百字节需要保证提交后数据不丢同时并发读不能太慢。经过初步筛选留下了两套候选方案一套是应用非常广泛的Go语言LSM存储引擎BadgerDB另一套是一个比较小众、采用分段文件结构设计的轻量级引擎sfsDb。我花了整整一周时间把这两者放到同一套基准场景里压测结论让我有点意外单看事务提交延迟sfsDb把BadgerDB甩开了一大截但一旦把并发事务和崩溃恢复这两项考虑进来局面又完全反过来。这篇文章就把我在整个测试过程中看到的底层原理、测试设计、实测数据以及排查故障的过程完整记录下来希望对正在做嵌入式存储选型的朋友有帮助。作为一个长期在业务系统里打交道的人我平时其实很少只根据跑分选型。真正推动我去做这场对比的是一个非常具体的线上故障某个依赖Redis做临时状态存储的服务在进程崩溃后需要恢复一批尚未落盘的事务数据结果状态对不上回滚逻辑又因为缺少原子性保障而出现部分成功部分失败的怪异现象。排查到最后根子出在底层存储没有提供严格的事务语义上。于是团队开始寻找能够在Go进程内直接嵌用的持久化键值引擎要求很简单支持事务提交与回滚崩溃重启后数据不能丢单机数据量在百万量级读写并发不能太差不能引入额外的服务端组件。BadgerDB和sfsDb就是在这样的条件下被筛出来的。1. 我为什么要把这两个引擎放到同一张测试台上先说结论性的背景BadgerDB是典型的LSM-Tree架构底层由SSTable文件、MemTable和Write-Ahead Log组成事务能力建立在快照隔离之上社区文档齐全代码里对崩溃恢复、压缩合并的处理都比较成熟。sfsDb则走的是另一条路线它把数据切分成多个固定大小的段文件写入时以追加方式落在段尾部内存中维护段级索引整体设计思路更接近“把一个大文件切成若干小文件各自管理”的朴素想法优点是写路径极短几乎没有LSM中常见的层级合并开销但代价是索引重建、段合并和并发控制都需要自己处理得足够细致。这两者其实代表了两类截然不同的存储哲学一个是把写入复杂度往后挪用后台合并在稳态数据上换读取效率另一个是尽量让写入路径直线化把压缩和整理变成偶尔需要手动触发或低频运行的额外任务。日常讨论很多基于纸面设计但真正影响选型的往往是“每秒能提交多少事务”“单事务P99延迟是多少”“跌电之后要多久才能打开数据库”这类简单问题。这篇博客不对两者做全面的产品评测只聚焦在“事务性能”这一个维度并捎带分析导致差异的代码级原因。我需要提前说明sfsDb并不是一个广泛流行的项目它的代码仓库比较小众甚至可以说是一部分开发者自用迭代的产物但因为它的事务接口设计非常简洁文档里也明确说自己适合嵌入式高频写场景我才会花时间把它纳入对比。下面所有测试都是在同一台机器、同一份数据、同一套事务提交模式下完成的尽可能排除环境噪音。2. 两个引擎的底层设计从写入路径看事务性能差异2.1 BadgerDBLSM树与日志分离结构的配合BadgerDB的写入路径可以简单拆成四步第一次写入某个key时数据先进内存中的跳表结构MemTable同时追加一条日志到预写日志文件WALMemTable积累到一定大小后变成不可变的只读表后台协程把它刷成磁盘上的SSTable文件SSTable按key排序存储多层之间存在层级关系后台会周期性地把多个层的小文件合并成更大文件这个过程叫Compaction目的是减少查询时需要扫描的文件数量、回收被删除或覆盖掉的旧数据。BadgerDB做得比较特殊的一点是把key和value分开存储key按照排序方式放进SSTablevalue则以追加写的形式单独存入value log文件。这样做最直接的好处是Compaction过程中不需要搬动大value写放大幅度下降读取时先根据key找到对应的value指针再去value log里取实际数据。这个设计让它在“大value高频更新”场景下优势明显也让“读放大”和“写放大”之间的平衡点更容易调节。从事务性能的角度看BadgerDB在第2.0版本之后支持的默认隔离级别是SSISerializable Snapshot Isolation也就是串行化快照隔离。事务开始时建立快照读取操作不涉及锁写入操作先缓存在事务内存中提交时进行一次写冲突检测如果有冲突整个事务会被拒绝由业务层决定是否需要重试。这种乐观并发模型在高并发读场景下非常舒服因为读操作不会阻塞写操作写操作也不会阻塞读操作大家各拿各的快照只有提交瞬间需要认真核对版本号。2.2 sfsDb段式文件与内存索引的取舍sfsDb的结构比BadgerDB简单直观得多。它把整个键空间切分成若干独立的数据段每个段对应一个物理文件段内部按写入顺序追加记录。段中的记录条数或文件大小达到阈值后引擎就把当前段标记为“只读”新建一个段继续追加。内存中维护所有段的索引信息通常包括key范围的上下界、段内记录的偏移位置、以及一张便于定位的哈希或跳表索引。这种设计的写路径几乎没有任何后台负担一条记录要落盘只需要在文件的末端追加一段二进制数据再更新内存索引整个过程只有一次顺序写理论上和顺序日志一样快。读取路径也不复杂先通过索引定位到哪个段再在段内查找目标key如果段内只有几千条记录二分查找加内存偏移读取非常快。代价在哪里一是段之间的数据可能冗余因为一个key的旧版本可能存在多个段里二是系统正常运行一段时间后段文件数量会逐渐增加如果一直不做合并查询时需要检查的段会变多读放大慢慢出现三是进程重启后所有内存索引都需要扫描段文件重建数据量一大恢复时间就很可观。也就是说sfsDb把复杂度从“写入时”挪到了“启动时”和“合并时”。2.3 架构差异如何影响事务实现架构上的差异直接决定了两者实现事务的方式。LSM树天然适合“提交即追加日志”的模式——事务开始时给一个全局递增版本号所有写操作附带这个版本号进入MemTable提交时只要保证WAL里的日志完整内存里的数据即使丢失也能从日志回放因为底层数据从写入那一刻起就是带版本号的多版本结构快照隔离实现起来很自然。而sfsDb这种段式追加结构如果要做多版本管理就需要在段记录里显式附加版本信息或者采用“写时复制”的方式重建段文件否则很难让两个事务同时看到一致的视图。我看到的sfsDb实现采用的是另一种思路事务提交时直接追加新记录旧记录继续留在旧段里等待后台清理读取时用“提交序号”判断一条记录是否对当前读取可见。听起来有点像“让步于LSM”但它少了Compaction这一层主动整理所以事务提交本身非常快。这里埋下了一个隐患如果事务回滚或者有多个事务并发提交系统必须能够区分哪条记录属于哪个已提交事务哪条记录应该被忽略。sfsDb在版本信息不太充裕的场合可能会出现“孤儿记录”残留只能靠后续合并阶段清除这也是我后续测试中遇到问题的一个诱因。3. 事务模型对比从隔离级别到提交耗时3.1 BadgerDB的SSI快照隔离我用实际代码来说明BadgerDB事务接口的典型用法txn : db.NewTransaction(true) defer txn.Discard() if err : txn.Set([]byte(user:1001), []byte(value-1001)); err ! nil { return err } if err : txn.Set([]byte(user:1002), []byte(value-1002)); err ! nil { return err } if err : txn.Commit(); err txn.ErrConflict { // 冲突时整个事务被拒绝必须重试 return ErrConflictRetry }这段代码里很容易忽略的一点是Commit()并不仅仅把内存数据写进WAL那么简单它还要向并发控制模块提交一组读写集合。BadgerDB通过维护一个全局的可达性时间戳和事务冲突检测表来判断当前事务是否与其他事务产生写写冲突。冲突检测的原理类似数据库领域的“先提交者胜”两个事务如果都修改了同一个key后提交的那个会拿到冲突错误。从性能角度讲这种模型让单事务提交的固定开销相对较高因为它既要做快照分配又要在提交点做冲突校验还要保证WAL落盘。但如果业务以“小事务、读多写少”为主多个并发事务可以在同一个时间片里共享大量只读工作整体吞吐并不会差。3.2 sfsDb的事务实现锁为主、提交点极短sfsDb的事务接口粗略是这样txn, err : db.Begin() if err ! nil { return err } if err : txn.Put([]byte(user:1001), []byte(value-1001)); err ! nil { txn.Rollback() return err } if err : txn.Commit(); err ! nil { txn.Rollback() return err }它的Begin和Commit底层都围绕一把全局写锁运转Begin时会拿一把写锁把所有待写数据缓存在事务对象里到Commit时才把批量记录一次追加进当前段文件然后释放写锁。读操作不加锁如果读到一半遇到正在提交的数据就依赖段文件里记录的事务序号来判断可见性。这种设计的优点是事务提交路径极短不需要做冲突检测也不维护版本链单事务延迟完全等同于“一次文件追加加一次索引更新”。缺点也很明显全局写锁导致两个事务无法并行写入写入吞吐在高并发场景会撞上一堵墙。你可以把它看作是简化到极致的单写者多读者模型对业务来说隔离级别基本等于“已提交读”加一点可重复读的保障做不到BadgerDB那样的快照隔离。3.3 关键结论每一次fsync都值钱抛开锁竞争不谈两种引擎单事务提交的差距主要在“落盘策略”上。BadgerDB默认的SyncWrites关闭时批量事务可以攒若干条再落盘事务性能很高但牺牲了持久性开启SyncWrites后每次提交都会调用fsyncP99延迟立刻翻好几倍。sfsDb在默认配置下每次提交也会做一次同步落盘但因为它只做一次追加写没有额外的事务元数据外链所以同步落盘的开销更小。我个人的理解是事务性能之争本质上是“怎么把一次fsync的价值最大化”BadgerDB用WAL把大量事务日志串行写入再靠日志回放恢复内存状态相当于把同步点的粒度打得很碎sfsDb则把若干条变更为一个批次追加进数据段本身一次同步完成整个提交效率更高但并发能力更弱。两者没有绝对的对错只看你的业务更缺延迟还是更缺并发。4. 基准测试设计如何公平对比两个差异很大的引擎4.1 测试环境与数据规格整个测试运行在一台8核虚拟机上内存16GB磁盘为NVMe固态硬盘操作系统是LinuxGo版本为1.22。两个引擎都使用Go语言编写本质上是同一个进程内的库不存在跨进程网络开销测试相对集中。参数值CPU8核内存16GB磁盘NVMe SSDkey长度32字节value长度256字节数据总量200万条事务类型单条写、批量写10~100条、读、混合读写并发度1 / 4 / 8 / 16我特别说明一点key长度和value长度是影响结果的重要因素。对LSM树引擎来说value如果特别大写入放大问题会被彻底放大对段式引擎来说value大了索引重建和段合并时的IO开销也会变得突出。所以我把value固定成业务里比较常见的256字节避免测试结果被极端规格带偏。4.2 五种负载模型为了让对比不变成单纯的“谁跑分高”我设计了五类负载单事务提交压测每个事务只写入一条记录事务提交后立即开启下一个事务模拟高频小事务场景。批量事务提交每个事务包含10条、50条、100条写入模拟批量导入。读密集负载80%读取、20%写入模拟常规业务流量。混合读写负载50%读取、50%写入多个事务并发交错。崩溃恢复测试写一半进程被强杀重启数据库检查数据完整性和恢复耗时。每类负载都先预热一轮确保两个引擎都达到稳定状态再记录数据。运行时关闭旧引擎自带的调试日志避免日志落盘干扰性能。所有数据重复三轮取中位数作为最终结果P99延迟单独记录。4.3 统计口径延迟统计以“事务提交完成”为终点记录从事务开始到Commit返回耗时不包含网络序列化时间吞吐以每秒提交事务数计算磁盘占用则统计测试结束后数据目录的整体大小计算写放大倍数。还有一个容易被忽视的点BadgerDB的MemTable默认有一定内存上限我的测试为了让两者公平把BadgerDB的NumMemtables设置为4ValueLogFileSize控制在1GBsfsDb使用它默认的段文件大小和数据缓存配置。配置文件尽量只用官方推荐值不额外做极限调优这样反映的是绝大多数使用者开箱即得的水平。5. 实测结果吞吐、延迟、磁盘磨损三方面对比5.1 单事务提交sfsDb优势明显单事务提交场景下每个事务只包含一条256字节的记录每轮运行5万次事务结果如下引擎QPSP50延迟P99延迟BadgerDB22000.45ms1.20mssfsDb84000.12ms0.31mssfsDb在这个场景里几乎是BadgerDB的4倍吞吐P99延迟更是断崖式领先。原因并不难解释sfsDb的提交路径没有冲突检测也没有快照版本管理只需要在文件尾部追加一段二进制数据更新索引然后调用一次同步落盘。BadgerDB则要考虑WAL写入、MemTable插入、事务冲突检测这几步叠加在一起即使每次都非常快积累起来延迟也不会低。但这里我要强调一个容易误读的点BadgerDB的开销换来的是更强的并发控制能力。在单线程场景里它确实比不过sfsDb但单线程场景下多数数据库差距并不大真正拉开差距的是接下来的并发测试。5.2 并发写事务战场反转开始出现把并发度从1提升到4、8、16线程每个线程持续执行批量10条写入的事务结果如下并发线程数BadgerDB tpssfsDb tps13400115004108001280081960013600162240014700两条曲线的交叉点大概出现在并发度2~4之间。BadgerDB明显吃并发每加一个线程吞吐都有可观的提升sfsDb的增长则非常平缓到了8线程以后几乎不再增长这正是全局写锁的体现。单个事务的提交速度再快同一时刻也只能有一个事务在写入线程再多也帮不上忙。延迟方面也有对应关系。BadgerDB的P99在8线程下提升到3.8ms16线程下到6.2mssfsDb因为本身吞吐瓶颈明显线程再多也只是让请求排队P99从0.31ms涨到2.1ms。坦白说sfsDb在这个测试里反而暴露出了并发模型上的先天不足。5.3 混合读写与P99抖动混合读写场景下读请求占比50%写请求占比50%并发度8每个事务包含两次读和一次写。结果如下引擎平均延迟P99延迟QPSBadgerDB0.32ms1.4ms7800sfsDb0.21ms1.1ms9600在这个场景里sfsDb依然占有一定优势但差距比纯写场景小了很多。原因在于BadgerDB的读操作通常不需要加锁阻塞不多而sfsDb读取时会受到全局写锁的间接影响——当某个事务正在提交时读操作依然可以执行但读到的数据版本可能在几毫秒内不一致业务层需要忍受这种短暂的不稳定性。更值得关注的是P99抖动。BadgerDB在我的测试里每隔几秒会出现一次较明显的延迟尖峰从1.4ms直接跳到20ms以上频率大约每30秒一次这和后台Compaction启动的节奏基本吻合。sfsDb则没有这种周期性尖峰它的延迟分布更加稳定只在段文件切换瞬间略有波动。如果你的业务是延迟敏感型对P99尖峰容忍度低这个差异可能比平均延迟更值得纳入选型考量。5.4 磁盘占用与写放大写放大这个指标是很多嵌入式存储选型时被忽略的环节。我在测试结束后统计了两个引擎的数据目录大小原始数据量按200万条乘以320字节计算约为600MB但实际磁盘占用如下引擎磁盘占用估算写放大BadgerDB1.4GB约2.3倍sfsDb0.9GB约1.5倍BadgerDB的高写放大来自CompactionSSTable在层级之间移动合并时相同的数据会被重复写入多次虽然Value Log的设计已经让大value避免了大部分重复搬运但key层和层级间的整理开销仍在。sfsDb因为几乎没有后台频繁整理只有段合并时会重写部分数据所以磁盘占用更小。这也正好印证了两者的适用倾向如果你的数据量在几个GB以内、磁盘空间不敏感BadgerDB多写出来的那一倍空间可以接受如果数据量几十GB起步写放大直接决定你购买多少存储成本那sfsDb这类低写放大引擎的价值就体现出来了。6. 排查记录sfsDb重启后第一次事务慢到让人怀疑人生这个故障章节是我在测试过程中真实撞到的。前面几轮压测之后我为了模拟崩溃恢复场景用kill -9强杀了两组测试进程。sfsDb重启后程序打开数据库正常但第一个事务竟然耗时接近2秒此后又恢复正常。这个现象非常怪异因为事务提交本身只有毫秒级开销启动过程明明没有报错。最初怀疑是文件系统缓存失效重启后系统需要重新把数据段读入内存但数据量只有600MB冷加载时间不至于到2秒。进一步排查后确认问题出在sfsDb的索引重建策略上重启时引擎扫描所有数据段重建内存索引这本是一个常规操作但它的实现里有一个非常隐蔽的分批索引加载机制——索引只对前N个段建立完整哈希表剩余段先只建立粗粒度的key范围索引等待第一次访问时按需加载细节索引。第一次事务访问的key恰好落在一个未加载完整索引的段里数据库被迫现场扫描整个段文件构建索引于是产生了那个离谱的2秒延迟。我最后把sfsDb的“启动时全量索引”配置项打开并调整了段文件缓存池的上限问题才消失。这次排查给我一个很直接的教训对段式存储引擎来说索引重建策略是恢复性能的关键设计不佳会在启动后的最初几次查询里制造出其不意的尖峰压测时必须专门覆盖“重启后热查询”这一环节。BadgerDB因为所有元数据都能通过SSTable的元信息快速恢复没有遇到同类问题但它Compaction期间的高延迟尖峰又成为另一个需要警惕的痛点。7. 我的选型建议别只看基准分数我做了这么多测试之后最终给团队的选型建议并不是单纯选哪个分数更高而是按业务场景分开讨论。如果你需要的是高并发下的多事务并发控制、强隔离级别、数据量会增长到数十GB以上的场景BadgerDB明显更合适。它的SSI快照隔离让读操作和写操作几乎互不干扰后台Compaction也在替你持续整理数据。代价是单事务延迟较高、周期性尖峰不可避免、磁盘占用更大。如果你的业务特点是低并发写入、读多写少、单条事务更注重提交延迟、存储空间敏感那么sfsDb这样的段式追加引擎值得认真考虑。它把写入路径压缩到极致磁盘占用低延迟分布稳定配合批量提交可以高效处理大量小事务。但前提是你接受了它的并发写瓶颈和重启后索引恢复的不确定性。从技术演进的角度说这两类引擎其实都在朝着“降低写放大”和“降低读延迟”这两个目标努力只是路径不同。BadgerDB用日志分离结构缓解大value写入放大sfsDb用段文件最小化常规写入开销。没有哪个设计是万能的适合自己的业务特征才是最好的。最后分享一个我踩过几次坑之后的测试经验嵌入式键值存储的性能评测一定要在真实业务提交模式下去跑不要只做纯读写压测。把事务大小、并发度、重启频率、磁盘类型都调到和生产接近的状态再统计P99和写放大这个分数比任何官网展示的基准都要可靠得多。如果你也在选型建议不要迷信单一指标把测试脚本跑起来用自己数据喂一下答案会自己浮现出来。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号