几个常用数据库性能分析
最近公司需要选型一款单机KV数据库来做业务承载,所以我对比了目前市面上比较流行的几个KV数据库并记录下来,包括boltdb,rocksdb,pebbledb,badgerdb四款,我将简单分析一下各数据库的特点,最后用自己的简单测试程序跑一下各数据库。对比一下性能差异
boltdb
github地址:https://github.com/boltdb/bolt Star:11.7k Fork:1.5K
最后 Merge pull request 时间是2018年3月,很久没有维护了。
代码量:
特点
- 直接使用API存取数据,没有查询语句;
- 支持完全可序列化的ACID事务,这个特性比LevelDB强;
- 数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收;
- 通过COW技术,可实现无锁的读写并发,但是无法实现无锁的写
- 写并发,这就注定了读性能超高,但写性能一般,适合与读多写少的场景
与其他数据对比
LevelDB,RocksDB
LevelDB及其派生类(RocksDB,HyperLevelDB)与Bolt类似,因为它们是捆绑到应用程序中的库,但是它们的底层结构是日志结构的合并树(LSM树)。LSM树通过使用预写日志和称为SSTables的多层排序文件来优化随机写入。Bolt在内部使用B +树,并且仅使用一个文件。两种方法都需要权衡。 如果您需要较高的随机写入吞吐量(> 10,000 w / sec),或者需要使用旋转磁盘,那么LevelDB可能是一个不错的选择。如果您的应用程序是大量读取或进行大量范围扫描,那么Bolt可能是一个不错的选择。 另一个重要的考虑因素是LevelDB没有事务。它支持键/值对的批量写入,并且支持读取快照,但不能使您安全地执行比较和交换操作。Bolt支持完全可序列化的ACID事务。
LMDB
Bolt最初是LMDB的端口,因此在架构上相似。两者都使用B +树,具有ACID语义和完全可序列化的事务,并支持使用单个写入器和多个读取器的无锁MVCC。 这两个项目有些分歧。LMDB专注于原始性能,而Bolt专注于简单性和易用性。例如,出于性能考虑,LMDB允许执行几种不安全的操作,例如直接写入。Bolt选择禁止可能使数据库处于损坏状态的操作。Bolt唯一的例外是DB.NoSync。 API也有一些区别。打开LMDB时需要最大的mmap大小,mdb_env而Bolt会自动处理增量mmap的大小。LMDB使用多个标志来重载getter和setter函数,而Bolt将这些特殊情况拆分为自己的函数。
注意事项和局限性
选择合适的工具来完成这项工作很重要,而Bolt也不例外。在评估和使用Bolt时,需要注意以下几点:
- Bolt非常适合读取密集型工作负载。顺序写入性能也很快,但是随机写入可能会很慢。您可以使用DB.Batch()或添加预写日志来帮助缓解此问题。
- Bolt在内部使用B + tree,因此可以有很多随机页面访问。与旋转磁盘相比,SSD可以显着提高性能。
- 尝试避免长时间运行的读取事务。Bolt使用写时复制功能,因此在旧事务使用旧页时无法回收这些旧页。
- 从Bolt返回的字节片仅在事务期间有效。一旦事务被提交或回滚,它们所指向的内存就可以被新页面重用,或者可以从虚拟内存中取消映射,unexpected fault address访问时会出现恐慌。
- Bolt在数据库文件上使用排他写锁定,因此不能被多个进程共享。
- 使用时要小心Bucket.FillPercent。为具有随机插入的存储桶设置较高的填充百分比将导致数据库的页面利用率非常差。
- 通常使用较大的水桶。较小的存储桶一旦超过页面大小(通常为4KB),就会导致页面利用率下降。
- 批量加载大量随机写入新的存储桶可能很慢,因为在提交事务之前页面不会拆分。建议不要在单个事务中将100,000个以上的键/值对随机插入到一个新的存储桶中。
- Bolt使用内存映射文件,因此底层操作系统可以处理数据的缓存。通常,操作系统将在内存中缓存尽可能多的文件,并根据需要将内存释放给其他进程。这意味着在使用大型数据库时,Bolt可能会显示很高的内存使用率。但是,这是预料之中的,操作系统将根据需要释放内存。只要Bolt的内存映射适合进程虚拟地址空间,它就可以处理比可用物理RAM大得多的数据库。在32位系统上可能会出现问题。
- Bolt数据库中的数据结构是内存映射的,因此数据文件将是特定于字节序的。这意味着您无法将Bolt文件从小字节序计算机复制到大字节序计算机并使其正常工作。对于大多数用户而言,这不是问题,因为大多数现代CPU的字节序都很少。
- 由于页面在磁盘上的布局方式,Bolt无法截断数据文件并将可用页面返回到磁盘。取而代之的是,Bolt会在其数据文件中维护未使用页面的空闲列表。这些空闲页面可以被以后的事务重用。由于数据库通常会增长,因此这在许多用例中效果很好。但是,请务必注意,删除大块数据将不允许您回收磁盘上的该空间。
RocksDB
github地址:https://github.com/facebook/rocksdb/ Star:16.9K Fork:4.4K
C++编写,现在还在维护
代码量:
特点
RocksDB最初的设计理念就是其应该在高速存储设备以及服务器压力下能有很好的性能表现。他应该能榨取Flash或者RAM子系统提供的所有读写速度潜能。他应该能支持高速的点查询和区间查询。可以通过配置支持很高的随机查询负荷,很高的更新负荷或者两者兼有。其架构应能很简单地对读放大,写放大和存储空间放大进行调优。go程序使用需要用到包装库gorocksdb
RocksDB文档很齐全,可以看官方文档介绍
PebbleDB
github地址:https://github.com/cockroachdb/pebble Star:1.9K Fork:124
纯go编写,现在还在维护
该项目是由cockroach工作室进行维护,pebbledb基础是levelsdb,然后把rocksdb的部分功能用go移植并对某些功能进行优化。
代码量
特点
此db可以堪称rocksdb的go语言版本,但只实现了rocksdb的部分功能,也增加了一些rocksdb没有的功能和优化,可以看下github上的项目介绍。
BadgerDB
github地址:https://github.com/dgraph-io/badger Star:9.1K Fork:756
纯go编写,现在还在维护
下面是作者对此的介绍
Badger was written with these design goals in mind:
- Write a key-value database in pure Go.
- Use latest research to build the fastest KV database for data sets spanning terabytes.
- Optimize for SSDs.
Badger’s design is based on a paper titled WiscKey: Separating Keys from Values in SSD-conscious Storage.
代码量
特点:
badgerdb对key,value进行了分别的处理,只把key存到了lms结构,value是用的Log文件进行处理,适合key小value大的情况。
Badgerdb官网提供了一个功能对比
Feature | Badger | RocksDB | BoltDB |
Design | LSM tree with value log | LSM tree only | B+ tree |
High Read throughput | Yes | No | Yes |
High Write throughput | Yes | Yes | No |
Designed for SSDs | Yes (with latest research 1) | Not specifically 2 | No |
Embeddable | Yes | Yes | Yes |
Sorted KV access | Yes | Yes | Yes |
Pure Go (no Cgo) | Yes | No | Yes |
Transactions | Yes, ACID, concurrent with SSI3 | Yes (but non-ACID) | Yes, ACID |
Snapshots | Yes | Yes | Yes |
TTL support | Yes | Yes | No |
3D access (key-value-version) | Yes4 | No | No |
数据测试
测试代码地址为:https://github.com/huxinhuxin/kvtest
测试说明:
设定假如设定N次(启动参数-a决定,默认为1000),将会依次执行后续操作:
- 顺序N次写
- 随机读N次
- 随机删N次
- 随机写N次
- 遍历所有Key
KV全都是默认配置,现在都是单线程运行,sync属性都设置为true
N=10000
顺序写 | 随机读 | 随机删 | 随机写 | 遍历key | 总耗时 | |
Boltdb | 2.498 s | 39.848 ms | 2.713 s | 2.881 s | 917.552µs | 8.137 s |
pebbledb | 1.142 s | 56.418 ms | 711.88 ms | 815.38ms | 34.94 ms | 2.763 s |
badgerdb | 2.833 s | 39.154 ms | 1.038 s | 2.880 s | 14.114 ms | 6.829 s |
Rocksdb | 1.501 s | 59.55 ms | 39.86 ms | 1.561 s | 60 ms | 3.23 s |
磁盘占用:
Boltdb 59M pebbled 80M badgerdb 80M
N=100000
顺序写 | 随机读 | 随机删 | 随机写 | 遍历key | 总耗时 | |
Boltdb | 26.459 s | 527.01 ms | 1m29.48 s | 1m29.35 s | 7.337 ms | 3m25.84 s |
pebbledb | 7.7904 s | 691.34 ms | 6.553 s | 8.202 s | 420.94 ms | 23.705 s |
badgerdb | 27.361 s | 443.42 ms | 10.182 s | 28.262 s | 98.491 ms | 1m6.51 s |
Rocksdb | 15.93 s | 1.63 s | 396.42 ms | 16.21 s | 739.81 ms | 34.92 s |
磁盘占用:
Boltdb 591M pebbled 392M badgerdb 793M Rocksdb 380M
如果sync设置为false的话就可以快很对,但可能会掉数据,应为不是及时写盘,下面给出sync设置为false的情况,但仅供参考,生产环境暂时不考虑这个参数。
N=100000
顺序写 | 随机读 | 随机删 | 随机写 | 遍历key | 总耗时 | |
Boltdb | 4.847 s | 489.82 ms | 50.370 s | 48.122 s | 7.030 ms | 1m43.85 s |
pebbledb | 1.517 s | 687.61 ms | 147.55 ms | 6.175 s | 293.21 ms | 8.84 s |
badgerdb | 1.880 s | 428.59 ms | 1.253 s | 2.151 s | 100.30 ms | 6.521 s |
Rocksdb | 1.74 s | 1.59 s | 416.88 s | 1.479 s | 1.514 s | 6.805s |
总结
BoltDB | RocksDB | PebbleDB | BadgerDB | |
存储结构 | B+TREE | LSM-TREE | LSM-TREE | Key是lms |
读速度 | 最快 | 快 | 快 | 快 |
写速度 | 较慢 | 快 | 最快 | 快 |
存储文件数量 | 1个 | 多个 | 多个 | 几个 |
代码量(包含测试代码)万行 | 0.9 | 29 | 27 | 2.8 |
是否还在维护 | 否 | 是 | 是 | 是 |
成熟度(使用的项目) | 高 | 高 | 少,但基于PebbleDB的CockroachDB使用还是挺多的 | 高 |
文档是否齐全 | 一般 | 最多 | 最少 | 较多 |
- boltdb适合读比重较大的项目
- badgerdb适合key小但value比较大的项目
- rocksdb和pebbledb适合对写性能要求比较高的项目,c++程序用rocksdb,golang的话用pebbledb比较合适,当然rocksdb有部分功能pebble没有实现,所以需要先搞清楚项目需求