badger和rocksDB性能对比

article/2025/10/13 2:36:50

结论:

  1. 从最后一个表格来看,ssd只对batch_read和batch-write操作有优势,而且在多协程的情况下,这个优势也丢失了。
  2. 从第二和第三个表格来看,badger的write操作比rocksDB慢了一个数量级,而batch_write操作badger又非常快。所以如果你用的是go语言,如果write不是你的主要操作,推荐用badger。
参数不同、测试方法不同都会导致结论不同,以下是我的测试代码。

storage.go
package storageimport ("fmt"
)var storageOpenFunction = map[string]func(path string) (Storage, error){"badger":  OpenBadger,"rocksdb": OpenRocksdb,
}type Storage interface {Set(k, v []byte, expireAt int64) errorBatchSet(keys, values [][]byte, expireAts []int64) errorGet(k []byte) ([]byte, error)BatchGet(keys [][]byte) ([][]byte, error)Delete(k []byte) errorBatchDelete(keys [][]byte) errorHas(k []byte) boolIterDB(fn func(k, v []byte) error) int64IterKey(fn func(k []byte) error) int64Close() error
}func OpenStorage(storeName string, path string) (Storage, error) {if fc, exists := storageOpenFunction[storeName]; exists {return fc(path)} else {return nil, fmt.Errorf("unsupported storage engine: %v", storeName)}
}

rocks.go

package storageimport ("github.com/tecbot/gorocksdb""os""path""sync/atomic"
)var (rocksOptions = gorocksdb.NewDefaultOptions()readOptions  = gorocksdb.NewDefaultReadOptions()writeOptions = gorocksdb.NewDefaultWriteOptions()
)type Rocksdb struct {db *gorocksdb.DB
}func OpenRocksdb(dbPath string) (Storage, error) {if err := os.MkdirAll(path.Dir(dbPath), os.ModePerm); err != nil { //如果dbPath对应的文件夹已存在则什么都不做,如果dbPath对应的文件已存在则返回错误return nil, err}rocksOptions.SetCreateIfMissing(true)rocksOptions.SetCompression(gorocksdb.NoCompression)rocksOptions.SetWriteBufferSize(1000000)db, err := gorocksdb.OpenDb(rocksOptions, dbPath)if err != nil {panic(err)}return &Rocksdb{db: db}, err
}func (s *Rocksdb) Set(k, v []byte, expireAt int64) error {return s.db.Put(writeOptions, k, v)
}func (s *Rocksdb) BatchSet(keys, values [][]byte, expireAts []int64) error {wb := gorocksdb.NewWriteBatch()defer wb.Destroy()for i, key := range keys {value := values[i]wb.Put(key, value)}s.db.Write(writeOptions, wb)return nil
}func (s *Rocksdb) Get(k []byte) ([]byte, error) {return s.db.GetBytes(readOptions, k)
}func (s *Rocksdb) BatchGet(keys [][]byte) ([][]byte, error) {var slices gorocksdb.Slicesvar err errorslices, err = s.db.MultiGet(readOptions, keys...)if err == nil {values := make([][]byte, 0, len(slices))for _, slice := range slices {values = append(values, slice.Data())}return values, nil}return nil, err
}func (s *Rocksdb) Delete(k []byte) error {return s.db.Delete(writeOptions, k)
}func (s *Rocksdb) BatchDelete(keys [][]byte) error {wb := gorocksdb.NewWriteBatch()defer wb.Destroy()for _, key := range keys {wb.Delete(key)}s.db.Write(writeOptions, wb)return nil
}//Has
func (s *Rocksdb) Has(k []byte) bool {values, err := s.db.GetBytes(readOptions, k)if err == nil && len(values) > 0 {return true}return false
}func (s *Rocksdb) IterDB(fn func(k, v []byte) error) int64 {var total int64iter := s.db.NewIterator(readOptions)defer iter.Close()for iter.SeekToFirst(); iter.Valid(); iter.Next() {//k := make([]byte, 4)//copy(k, iter.Key().Data())//value := iter.Value().Data()//v := make([]byte, len(value))//copy(v, value)//fn(k, v)if err := fn(iter.Key().Data(), iter.Value().Data()); err == nil {atomic.AddInt64(&total, 1)}}return atomic.LoadInt64(&total)
}func (s *Rocksdb) IterKey(fn func(k []byte) error) int64 {var total int64iter := s.db.NewIterator(readOptions)defer iter.Close()for iter.SeekToFirst(); iter.Valid(); iter.Next() {//k := make([]byte, 4)//copy(k, iter.Key().Data())//fn(k)if err := fn(iter.Key().Data()); err == nil {atomic.AddInt64(&total, 1)}}return atomic.LoadInt64(&total)
}func (s *Rocksdb) Close() error {s.db.Close()return nil
}

badger.go

package storageimport ("github.com/dgraph-io/badger""os""path""time""github.com/pkg/errors""fmt""sync/atomic""github.com/dgraph-io/badger/options"
)type Badger struct {db *badger.DB
}var badgerOptions = badger.Options{DoNotCompact:        false,    //LSM tree最主要的性能消耗在于 compaction 过程:多个文件需要读进内存,排序,然后再写回磁盘LevelOneSize:        64 << 20, //第一层大小LevelSizeMultiplier: 10,       //下一层是上一层的多少倍MaxLevels:           7,        //LSM tree最多几层//key存在内存中,values(实际上value指针)存在磁盘中--称为vlog fileTableLoadingMode:        options.MemoryMap, //LSM tree完全载入内存ValueLogLoadingMode:     options.FileIO,    //使用FileIO而非MemoryMap可以节省大量内存MaxTableSize:            4 << 20,           //4MNumCompactors:           8,                 //compaction线程数NumLevelZeroTables:      4,NumLevelZeroTablesStall: 10,NumMemtables:            4,     //写操作立即反应在MemTable上,当MemTable达到一定的大小时,它被刷新到磁盘,作为一个不可变的SSTableSyncWrites:              false, //异步写磁盘。即实时地去写内存中的LSM tree,当数据量达到MaxTableSize时,才对数据进行compaction然后写入磁盘。当调用Close时也会把内存中的数据flush到磁盘NumVersionsToKeep:       1,ValueLogFileSize:        64 << 20, //单位:字节。vlog文件超过这么大时就分裂文件。64MValueLogMaxEntries:      100000,ValueThreshold:          32,Truncate:                false,
}//var badgerOptions = badger.DefaultOptionsfunc OpenBadger(dbPath string) (Storage, error) {if err := os.MkdirAll(path.Dir(dbPath), os.ModePerm); err != nil { //如果dbPath对应的文件夹已存在则什么都不做,如果dbPath对应的文件已存在则返回错误return nil, err}badgerOptions.Dir = dbPathbadgerOptions.ValueDir = dbPathdb, err := badger.Open(badgerOptions) //文件只能被一个进程使用,如果不调用Close则下次无法Open。手动释放锁的办法:把LOCK文件删掉if err != nil {panic(err)}return &Badger{db}, err
}func (s *Badger) CheckAndGC() {lsmSize1, vlogSize1 := s.db.Size()for {if err := s.db.RunValueLogGC(0.5); err == badger.ErrNoRewrite || err == badger.ErrRejected {break}}lsmSize2, vlogSize2 := s.db.Size()if vlogSize2 < vlogSize1 {fmt.Printf("badger before GC, LSM %d, vlog %d. after GC, LSM %d, vlog %d\n", lsmSize1, vlogSize1, lsmSize2, vlogSize2)} else {fmt.Println("collect zero garbage")}
}//Set 为单个写操作开一个事务
func (s *Badger) Set(k, v []byte, expireAt int64) error {err := s.db.Update(func(txn *badger.Txn) error { //db.Update相当于打开了一个读写事务:db.NewTransaction(true)。用db.Update的好处在于不用显式调用Txn.Commit()了duration := time.Duration(expireAt-time.Now().Unix()) * time.Secondreturn txn.SetWithTTL(k, v, duration) //duration是能存活的时长})return err
}//BatchSet 多个写操作使用一个事务
func (s *Badger) BatchSet(keys, values [][]byte, expireAts []int64) error {if len(keys) != len(values) {return errors.New("key value not the same length")}var err errortxn := s.db.NewTransaction(true)for i, key := range keys {value := values[i]duration := time.Duration(expireAts[i]-time.Now().Unix()) * time.Second//fmt.Println("duration",duration)if err = txn.SetWithTTL(key, value, duration); err != nil {_ = txn.Commit(nil) //发生异常时就提交老事务,然后开一个新事务,重试settxn = s.db.NewTransaction(true)_ = txn.SetWithTTL(key, value, duration)}}txn.Commit(nil)return err
}//Get 如果key不存在会返回error:Key not found
func (s *Badger) Get(k []byte) ([]byte, error) {var ival []byteerr := s.db.View(func(txn *badger.Txn) error { //db.View相当于打开了一个读写事务:db.NewTransaction(true)。用db.Update的好处在于不用显式调用Txn.Discard()了item, err := txn.Get(k)if err != nil {return err}//buffer := make([]byte, badgerOptions.ValueLogMaxEntries)//ival, err = item.ValueCopy(buffer) //item只能在事务内部使用,如果要在事务外部使用需要通过ValueCopyival, err = item.Value()return err})return ival, err
}//BatchGet 返回的values与传入的keys顺序保持一致。如果key不存在或读取失败则对应的value是空数组
func (s *Badger) BatchGet(keys [][]byte) ([][]byte, error) {var err errortxn := s.db.NewTransaction(false) //只读事务values := make([][]byte, len(keys))for i, key := range keys {var item *badger.Itemitem, err = txn.Get(key)if err == nil {//buffer := make([]byte, badgerOptions.ValueLogMaxEntries)var ival []byte//ival, err = item.ValueCopy(buffer)ival, err = item.Value()if err == nil {values[i] = ival} else { //拷贝失败values[i] = []byte{} //拷贝失败就把value设为空数组}} else { //读取失败values[i] = []byte{} //读取失败就把value设为空数组if err != badger.ErrKeyNotFound { //如果真的发生异常,则开一个新事务继续读后面的keytxn.Discard()txn = s.db.NewTransaction(false)}}}txn.Discard() //只读事务调Discard就可以了,不需要调Commit。Commit内部也会调Discardreturn values, err
}//Delete
func (s *Badger) Delete(k []byte) error {err := s.db.Update(func(txn *badger.Txn) error {return txn.Delete(k)})return err
}//BatchDelete
func (s *Badger) BatchDelete(keys [][]byte) error {var err errortxn := s.db.NewTransaction(true)for _, key := range keys {if err = txn.Delete(key); err != nil {_ = txn.Commit(nil) //发生异常时就提交老事务,然后开一个新事务,重试deletetxn = s.db.NewTransaction(true)_ = txn.Delete(key)}}txn.Commit(nil)return err
}//Has 判断某个key是否存在
func (s *Badger) Has(k []byte) bool {var exists bool = falses.db.View(func(txn *badger.Txn) error { //db.View相当于打开了一个读写事务:db.NewTransaction(true)。用db.Update的好处在于不用显式调用Txn.Discard()了_, err := txn.Get(k)if err != nil {return err} else {exists = true //没有任何异常发生,则认为k存在。如果k不存在会发生ErrKeyNotFound}return err})return exists
}//IterDB 遍历整个DB
func (s *Badger) IterDB(fn func(k, v []byte) error) int64 {var total int64s.db.View(func(txn *badger.Txn) error {opts := badger.DefaultIteratorOptionsit := txn.NewIterator(opts)defer it.Close()for it.Rewind(); it.Valid(); it.Next() {item := it.Item()key := item.Key()val, err := item.Value()if err != nil {continue}if err := fn(key, val); err == nil {atomic.AddInt64(&total, 1)}}return nil})return atomic.LoadInt64(&total)
}//IterKey 只遍历key。key是全部存在LSM tree上的,只需要读内存,所以很快
func (s *Badger) IterKey(fn func(k []byte) error) int64 {var total int64s.db.View(func(txn *badger.Txn) error {opts := badger.DefaultIteratorOptionsopts.PrefetchValues = false //只需要读key,所以把PrefetchValues设为falseit := txn.NewIterator(opts)defer it.Close()for it.Rewind(); it.Valid(); it.Next() {item := it.Item()k := item.Key()if err := fn(k); err == nil {atomic.AddInt64(&total, 1)}}return nil})return atomic.LoadInt64(&total)
}func (s *Badger) Size() (int64, int64) {return s.db.Size()
}//Close 把内存中的数据flush到磁盘,同时释放文件锁
func (s *Badger) Close() error {return s.db.Close()
}

compare.go

package mainimport ("crypto/md5""encoding/hex""math/rand""pkg/radic/storage""time""fmt""sync""sync/atomic"
)const (KEY_LEN   = 30VALUE_LEN = 1000
)func checksum(data []byte) string {checksum := md5.Sum(data)return hex.EncodeToString(checksum[:])
}func Bytes(n int) []byte {d := make([]byte, n)rand.Read(d)return d
}type src struct {Data     []byteChecksum string
}func prepareData(n int) src {data := Bytes(n)checksum := md5.Sum(data)return src{Data: data, Checksum: hex.EncodeToString(checksum[:])}
}func TestWriteAndGet(db storage.Storage, parallel int) {var writeTime int64var readTime int64var writeCount int64var readCount int64wg := sync.WaitGroup{}wg.Add(parallel)for r := 0; r < parallel; r++ {go func() {defer wg.Done()EXPIRE_AT := time.Now().Add(100 * time.Minute).Unix()keys := [][]byte{}values := [][]byte{}validations := []string{}const loop = 100000for i := 0; i < loop; i++ {key := prepareData(KEY_LEN).Datakeys = append(keys, key)value := prepareData(VALUE_LEN)values = append(values, value.Data)validations = append(validations, value.Checksum)}begin := time.Now()for i, key := range keys {value := values[i]db.Set(key, value, EXPIRE_AT)}atomic.AddInt64(&writeTime, time.Since(begin).Nanoseconds())atomic.AddInt64(&writeCount, int64(len(keys)))begin = time.Now()for _, key := range keys {db.Get(key)}atomic.AddInt64(&readTime, time.Since(begin).Nanoseconds())atomic.AddInt64(&readCount, int64(len(keys)))}()}wg.Wait()fmt.Printf("write %d op/ns, read %d op/ns\n", atomic.LoadInt64(&writeTime)/atomic.LoadInt64(&writeCount), atomic.LoadInt64(&readTime)/atomic.LoadInt64(&readCount))
}func TestBatchWriteAndGet(db storage.Storage, parallel int) {var writeTime int64var readTime int64var writeCount int64var readCount int64loop := 100wg := sync.WaitGroup{}wg.Add(parallel)for r := 0; r < parallel; r++ {go func() {defer wg.Done()for i := 0; i < loop; i++ {EXPIRE_AT := time.Now().Add(100 * time.Minute).Unix()keys := [][]byte{}values := [][]byte{}expire_ats := []int64{}for j := 0; j < 1000; j++ {key := prepareData(KEY_LEN).Datakeys = append(keys, key)value := prepareData(VALUE_LEN).Datavalues = append(values, value)expire_ats = append(expire_ats, EXPIRE_AT)}begin := time.Now()db.BatchSet(keys, values, expire_ats)atomic.AddInt64(&writeTime, time.Since(begin).Nanoseconds())atomic.AddInt64(&writeCount, 1)begin = time.Now()db.BatchGet(keys)atomic.AddInt64(&readTime, time.Since(begin).Nanoseconds())atomic.AddInt64(&readCount, 1)}}()}wg.Wait()fmt.Printf("batch write %d op/ns, batch read %d op/ns\n", atomic.LoadInt64(&writeTime)/atomic.LoadInt64(&writeCount), atomic.LoadInt64(&readTime)/atomic.LoadInt64(&readCount))
}func main() {badger, _ := storage.OpenStorage("badger", "badgerdb")rocks, _ := storage.OpenStorage("rocksdb", "rocksdb")TestWriteAndGet(badger, 1)TestWriteAndGet(rocks, 1)TestBatchWriteAndGet(badger, 1)TestBatchWriteAndGet(rocks, 1)fmt.Println("parallel test")TestWriteAndGet(badger, 10)TestWriteAndGet(rocks, 10)TestBatchWriteAndGet(badger, 10)TestBatchWriteAndGet(rocks, 10)fmt.Println("please watch the memory")fmt.Println("rocksdb......")rocks.IterDB(func(k, v []byte) error {return nil})fmt.Println("badger......")badger.IterDB(func(k, v []byte) error {return nil})
}

  


http://chatgpt.dhexx.cn/article/uWvHidtV.shtml

相关文章

Honey Badger BFT(异步共识算法)笔记

最近一直在看Honey Badger BFT共识协议&#xff0c;看了很多博客和一些相关的论文&#xff0c;但是发现有些博客存在着部分理解错误的地方&#xff0c;或者就是直接翻译2016年的那一篇论文&#xff0c;在经过半个多月的细读之后&#xff0c;打算整理出这篇博客&#xff0c;方便…

Badger、Leveldb

BadgerDB v2 介绍 2017年发行 来自DGraph实验室 开源 纯go语言编写 https://github.com/dgraph-io/badger https://godoc.org/github.com/dgraph-io/badger 内存模式 &#xff08;所有数据存在内存&#xff0c;可能丢失数据&#xff09;SSD优化键值分离 Key(00000*.sst) Valu…

badger 一个高性能的LSM K/V store

大家好&#xff0c;给大家介绍一下&#xff0c; 新晋的高性能的 K/V数据库: badger。 这是 dgraph.io开发的一款基于 log structured merge (LSM) tree 的 key-value 本地数据库&#xff0c; 使用 Go 开发。 事实上&#xff0c;市面上已经有一些知名的基于LSM tree的k/v数据库…

badger框架学习 (一)

1.badger是什么&#xff1f; badger是一种高性能的 K/V数据库。 这是 dgraph.io开发的一款基于 log structured merge (LSM) tree 的 key-value 本地数据库&#xff0c; 使用 Go 开发。 2.badger有什么优势&#xff1f; 事实上&#xff0c;市面上已经有一些知名的基于LSM tre…

No Free Lunch定理

Stanford大学Wolpert和Macready教授提出了NFL定理&#xff0c;它是优化领域中的一个重要理论研究成果&#xff0c;意义较为深远。现将其结论概括如下&#xff1a; 定理1 假设有A、B两种任意&#xff08;随机或确定&#xff09;算法&#xff0c;对于所有问题集&#xff0c;它们…

少样本学习原理快速入门,并翻译《Free Lunch for Few-Shot Learning: Distribution Calibration》

ICLR2021 Oral《Free Lunch for Few-Shot Learning: Distribution Calibration》 利用一个样本估计类别数据分布 9行代码提高少样本学习泛化能力 原论文&#xff1a;https://openreview.net/forum?idJWOiYxMG92s 源码&#xff1a;https://github.com/ShuoYang-1998/ICLR202…

Android lunch分析以及产品分支构建

转自&#xff1a;http://blog.csdn.net/generalizesoft/article/details/7253901 Android lunch分析以及产品分支构建 一、背景 随着Android应用范围越来越广泛&#xff0c;用户对Android的需求也越来越趋于复杂&#xff0c;在开发Android应用以及底层产品驱动时&#xff0…

Free Lunch is Over (免费午餐已经结束)

原文链接&#xff1a;The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software 免费的午餐结束了 软件并行计算的基本转折点 继OO之后软件发展的又一重大变革——并行计算 你的免费午餐即将即将结束。我们能做什么&#xff1f;我们又将做什么&#xff1f…

Free Lunch for Few-Shot Learning: Distribution Calibration(ICLR 2021)

论文笔记 FSL 7】Free Lunch for Few-Shot Learning: Distribution Calibration&#xff08;ICLR 2021&#xff09; 下载地址 | 论文源码

2022-11-16 AndroidS 新建产品lunch

一、新建lunch方法 二、实际操作&#xff0c;可以lunch新的菜单。

6-3 There is No Free Lunch (40分)

One day, CYLL found an interesting piece of commercial from newspaper: the Cyber-restaurant was offering a kind of “Lunch Special” which was said that one could “buy one get two for free”. That is, if you buy one of the dishes on their menu, denoted by…

android编译系统分析一:source build/envsetup.sh与lunch

Android编译系统分析系列文章&#xff1a; android编译系统分析一<source build/envsetup.sh与lunch> Android编译系统<二>-mm编译单个模块 android编译系统分析&#xff08;三&#xff09;-make android编译系统&#xff08;四&#xff09;-实战&#xff1a;新增一…

Lesson 2 Breakfast or lunch? 早餐还是午餐?

1.原文 2. 参考译文 3. New words and expressions ★until prep.直到 until用于表示动作、状态等的持续&#xff0c;可译为“一直到……为止”或“在……以前”。在肯定句中&#xff0c;它与表示持续性状态的动词连用&#xff0c;表示持续到某一时刻&#xff1a; I’ll wait…

Android 10 添加 lunch

需求&#xff08;当然这只是其中一个&#xff09;&#xff1a;多个产品用同一个核心板&#xff0c;外设驱动不一样&#xff0c;设备树不一样&#xff0c;开机画面等不一样&#xff0c;如果不添加&#xff0c;就会每次要生成哪个板子就覆盖对应的文件&#xff0c;麻烦不说还容易…

没有免费午餐定理No Free Lunch Theorem

不得不说&#xff0c;网上博客千千万&#xff0c;在技术方面&#xff0c;我承认这些博客的重要性。然而&#xff0c;只要和机器学习理论挂钩&#xff0c;似乎都讲得不清不楚&#xff0c;大家都是各自地抄&#xff0c;抄书籍&#xff0c;抄论文&#xff0c;抄别人的博客或者直接…

没有免费午餐定理(No Free Lunch Theorem)

当我们拿到数据之后&#xff0c;构建机器学习算法的第一步应当是&#xff1a;观察数据&#xff0c;总结规律。 目前由于大数据和深度学习的发展&#xff0c;很多人会认为&#xff0c;只要收集足够多的数据&#xff0c;从网上的开源算法模型中随便找一个&#xff0c;直接将数据丢…

[TIST 2022] No Free Lunch Theorem for Security and Utility in Federated Learning

联邦学习中的安全性和实用性没有免费午餐定理 No Free Lunch Theorem for Security and Utility in Federated Learning 目录 摘要简介2 相关文献2.1 隐私测量2.2 联邦学习2.2.1 FL 中的威胁模型。2.2.2 FL 中的保护机制。 2.3 隐私-实用权衡 3 一般设置和框架3.1 符号3.2 一般…

Andriod中如何新建lunch项

Andriod编译过程一般为&#xff1a; 1.source build/envsetup.sh //加载命令&#xff0c;在项目根目录下&#xff08;~/purple/code/a/A_code20211126/sdm660&#xff09;目录 备注&#xff1a;在envsetup.sh里将执行vendor和device目录及各自子目录下所有的vendorsetup.sh&a…

VS中创建自定义控件

第一步&#xff1a;创建一个ASP.NET WEB应用程序 第二步&#xff1a;在同一个解决方案中创建一个服务控件项目 2.1 再次创建一个asp.net web应用程序。如图&#xff1a; 2.2 然后在这个项目下创建一个Web窗体服务器控件 第三步&#xff1a;编辑为我想要的控件 在这个我这个…

C#自定义控件的设计与调用

在C#下建立自己的控件库&#xff0c;需用到自定义控件的设计与调用。 一、自定义控件的设计 自定义控件&#xff0c;步骤如下&#xff1a; 1.点击文件&#xff0d;&#xff1e;新建项目&#xff0d;&#xff1e;选择Windows控件库2.编辑控件3.点击生成&#xff0d;&#xff1…