V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
xujiajun001
V2EX  ›  Go 编程语言

开源一个纯 Go 编写的高性能内嵌型 KV 数据库 NutsDB,支持事务以及多种数据结构。

  •  2
     
  •   xujiajun001 ·
    xujiajun · 2019-03-06 15:32:33 +08:00 · 5302 次点击
    这是一个创建于 2118 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好,分享一个最近开源的 KV 数据库项目 NutsDB。是我对 nosql 一个阶段性实践吧。

    NutsDB 是纯 Go 语言编写一个简单、高性能、内嵌型、持久化的 key-value 数据库。

    NutsDB 支持 ACID 事务,所有的操作都在事务中执行,保证了数据的完整性。NutsDB 从 v0.2.0 版本开始支持多种数据结构,如列表(list)、集合(set)、有序集合(sorted set)。

    项目地址

    https://github.com/xujiajun/nutsdb

    项目特性

    • 高性能
    • 支持 ACID 事务
    • 支持基本的 Put、Delete、Get 操作
    • 支持前缀扫描
    • 支持范围扫描
    • 除了基本的 String,还支持多种数据结构如列表(list)、集合(set)、有序集合(sorted set)

    项目背景

    对于现状或多或少的不满

    我想找一个用纯 go 编写,尽量简单(方便二次开发、研究)、高性能(读写都能快一点)、内嵌型的(减少网络开销)数据库,最好支持事务。因为我觉得对于数据库而言,数据完整性很重要。如果能像 Redis 一样支持多种数据结构就更好了。 而像 Redis 一般用作缓存,对于事务支持也很弱。

    找到几个备选项:

    BoltDB BoltDB 是一个基于 B+ tree,有着非常好的读性能,还支持很实用的特性:范围扫描和按照前缀进行扫描。有很多项目采用了他。虽然现在官方不维护,由 etcd 团队在维护 他也支持 ACID 事务,但是他的写性能不是很好。如果对写性能要求不高也值得尝试。

    GoLevelDB GoLevelDB 是 google 开源的 leveldb 的 go 语言版本的实现。他的性能很高,特别是写性能,据官方 c++版本说可以到 40w+次写 /秒,他基于 LSM tree 实现。他不支持事务。

    Badger Badger 同样是基于 LSM tree,不同的是他把 key/value 分离。据他官网描述是基于为 SSD 优化。同是他也支持事务。但是我简单写了 benchmark 发现他的写性能没我想象中高。

    好奇心的驱使

    对于如何实现 kv 数据库的好奇心吧。数据库可以说是系统的核心,了解数据库的内核或者自己有实现,对更好的用轮子或者下次根据业务定制轮子都很有帮助。

    基于以上两点,我决定尝试开发一个简单的 kv 数据库,性能要好,功能也要强大(至少他们好的功能特性都要继承)。

    如上面的选项,我发现大致基于存储引擎的模型分:B+ tree 和 LSM tree。基于 B+ tree 的模型相对后者成熟。一般使用覆盖页的方式和 WAL (预写日志)来作崩溃恢复。而 LSM tree 的模型他是先写 log 文件,然后在写入 MemTable 内存中,当一定的时候写回 SSTable,文件会越来越多,于是他一般作法是在后台进行合并和压缩操作。 一般来说,基于 B+ tree 的模型写性能不如 LSM tree 的模型。而在读性能上比 LSM tree 的模型要来得好。当然 LSM tree 的模型也可以优化,比如引入 BloomFilter。 但是这些模型还是太复杂了。我喜欢简单,简单意味着好实现,好维护,相对不容易出错。

    直到我找到 bitcask 这种模型,他其实本质上也算 LSM tree 的范畴吧。 他模型非常简单很好理解和实现,很快我就实现了一个版本。但是他的缺点是不支持范围扫描。我尝试去优化他,又开发一个版本,基于 B+ tree 作为索引,满足了范围扫描的问题 ,读性能是够了,写性能很一般,又用 mmap 和对原模型作了精简,这样又实现了一版。写性能又提高了几十倍。现在这个版本基本上都实现上面提到的数据库的一些有用的特性,包括支持范围扫描和前缀扫描、包括支持 bucket、事务等,还支持了更多的数据结构( list、set、sorted set )。从 benchmark 来看,NutsDB 性能只高不低, 这是 example 里面的代码 https://github.com/xujiajun/nutsdb/blob/master/examples/batch/put/main.go ,100w 条数据,我本机基本上 2s 跑完 ,写性能可达到 40~50W+/秒。

    天下没有银弹,NutsDB 也有他的局限,比如随着数据量的增大,索引变大,启动会慢。 只想说 NutsDB 还有很多优化和提高的空间,由于本人精力以及能力有限。所以把这个项目开源出来。更重要的是我认为一个项目需要有人去使用,有人提意见才会成长。

    希望一起来参与贡献,欢迎 Star、提 issues、提交 PR !

    第 1 条附言  ·  2019-03-11 20:40:35 +08:00
    0.3.0 版本刚发布,支持数据持久化,一旦 commit 作强同步。
    第 2 条附言  ·  2019-03-11 20:47:53 +08:00

    抛弃了mmap的方式。从0.3.0开始返璞归真 用嘴基础的读写api,发现benchmark 也还能接受。找了2款 BadgerDBh和BoltDB,他们都是内嵌型的数据库,支持持久化和事务,不过不支持多种数据结构。以基础的put和get作比较,结果如下,如有问题,希望帮我指正。我及时改 :

    badger 2019/03/11 18:06:05 INFO: All 0 tables opened in 0s
    goos: darwin
    goarch: amd64
    pkg: github.com/xujiajun/kvstore-bench
    BenchmarkBadgerDBPutValue64B-8    	   10000	    112382 ns/op	    2374 B/op	      74 allocs/op
    BenchmarkBadgerDBPutValue128B-8   	   20000	     94110 ns/op	    2503 B/op	      74 allocs/op
    BenchmarkBadgerDBPutValue256B-8   	   20000	     93480 ns/op	    2759 B/op	      74 allocs/op
    BenchmarkBadgerDBPutValue512B-8   	   10000	    101407 ns/op	    3271 B/op	      74 allocs/op
    BenchmarkBadgerDBGet-8            	 1000000	      1552 ns/op	     416 B/op	       9 allocs/op
    BenchmarkBoltDBPutValue64B-8      	   10000	    203128 ns/op	   21231 B/op	      62 allocs/op
    BenchmarkBoltDBPutValue128B-8     	    5000	    229568 ns/op	   13716 B/op	      64 allocs/op
    BenchmarkBoltDBPutValue256B-8     	   10000	    196513 ns/op	   17974 B/op	      64 allocs/op
    BenchmarkBoltDBPutValue512B-8     	   10000	    199805 ns/op	   17064 B/op	      64 allocs/op
    BenchmarkBoltDBGet-8              	 1000000	      1122 ns/op	     592 B/op	      10 allocs/op
    BenchmarkNutsDBPutValue64B-8      	   30000	     53614 ns/op	     626 B/op	      14 allocs/op
    BenchmarkNutsDBPutValue128B-8     	   30000	     51998 ns/op	     664 B/op	      13 allocs/op
    BenchmarkNutsDBPutValue256B-8     	   30000	     53958 ns/op	     920 B/op	      13 allocs/op
    BenchmarkNutsDBPutValue512B-8     	   30000	     55787 ns/op	    1432 B/op	      13 allocs/op
    BenchmarkNutsDBGet-8              	 2000000	       661 ns/op	      88 B/op	       3 allocs/op
    BenchmarkNutsDBGetByHintKey-8     	   50000	     27255 ns/op	     840 B/op	      16 allocs/op
    PASS
    ok  	github.com/xujiajun/kvstore-bench	83.856s
    
    

    详细移步: https://github.com/xujiajun/nutsdb/blob/master/README-CN.md#benchmarks

    第 3 条附言  ·  2019-03-15 10:03:20 +08:00
    nutsdb v0.4.0 发布。

    * 修复事务提交的时候的 bug
    * 支持了自定义的启动加载的方式
    * 增加了新的参数配置:RWMode, SyncEnable and StartFileLoadingMode
    * 支持了自定义的读写文件的方式
    * 支持了自定义是否开启 sync 的方式
    第 4 条附言  ·  2019-04-11 11:39:58 +08:00

    有人提出要建微信群。如果有人想加群交流,可以去点下赞。如果点赞人数够,我觉得可以开通下,大家交流一下。不然意义不大。地址:https://github.com/xujiajun/nutsdb/issues/29

    38 条回复    2019-03-15 23:10:31 +08:00
    KgM4gLtF0shViDH3
        1
    KgM4gLtF0shViDH3  
       2019-03-06 15:52:41 +08:00 via iPhone
    支持
    falcon05
        2
    falcon05  
       2019-03-06 15:55:34 +08:00
    学习了
    whoisghost
        3
    whoisghost  
       2019-03-06 15:59:29 +08:00
    我最近在看 BoltDB 的实现,快读完了,打算用 C 重写其核心部分练练手,了解如何实现持久化 k/v 数据库。先 star 之,之后我拜读下你写的。
    reus
        4
    reus  
       2019-03-06 16:12:30 +08:00   ❤️ 1
    CockroachDB 的作者也写了个叫 pebble https://github.com/petermattis/pebble,最近才开始受到注意

    还不够成熟,不过以作者的实力,如果投入足够,应该会是不错的。CockroachDB 现在用的是 RocksDB,我想他们可能也想用一个纯 go 实现的 kv 来代替吧。
    1892
        5
    1892  
       2019-03-06 16:41:58 +08:00
    能否提供类似 select db 的函数,每次都要指定 bucket 有点繁琐
    solupro
        6
    solupro  
       2019-03-06 16:49:18 +08:00
    star 为敬
    misaka19000
        7
    misaka19000  
       2019-03-06 16:54:44 +08:00
    额,这是个 production 还是一个 toy ?
    Damnever
        8
    Damnever  
       2019-03-06 17:12:47 +08:00
    想法很好,但有个大问题 https://github.com/xujiajun/nutsdb/issues/10
    server
        9
    server  
       2019-03-06 17:27:46 +08:00
    star
    JohnSmith
        10
    JohnSmith  
       2019-03-06 18:17:26 +08:00
    @Damnever #8 意思是东西都在内存里的嘛~
    wbrobot
        11
    wbrobot  
       2019-03-06 18:25:55 +08:00
    bitcask 如果不 flush 的话,会一直增大到无法忍受吧,以前豆瓣有个 beansdb
    Damnever
        12
    Damnever  
       2019-03-06 18:27:09 +08:00 via iPhone
    @JohnSmith 也不能这么说,操作系统会有一些策略把脏数据刷到磁盘,依赖操作系统正常情况下都没啥大问题,但是… 我只能说这个 benchmark 太不不公平了…
    fuyufjh
        13
    fuyufjh  
       2019-03-06 18:30:27 +08:00
    先 star 为敬
    azh7138m
        14
    azh7138m  
       2019-03-06 18:33:59 +08:00 via Android
    @JohnSmith 要么是我对持久化有什么误解,要么是作者对持久化有什么误解 :)
    liprais
        15
    liprais  
       2019-03-06 18:58:28 +08:00
    @Damnever
    这不跟 mongodb 当年的套路一样么,不做 flush / fsync 啥玩意都能跑的飞快........
    runningman
        16
    runningman  
       2019-03-06 19:10:55 +08:00 via iPhone
    咋都在喷 不如想办法改进
    xujiajun001
        17
    xujiajun001  
    OP
       2019-03-07 09:13:36 +08:00
    @Damnever 你好 你在我项目中提交的 issue 我已经回复你了,不好意思现在才回你。我其实在代码中使用了 unmmap,我的依据是 https://linux.cn/man2/mmap.2.html 提到这样一句话 The file may not actually be updated until msync(2) or munmap(2) are called,按照他这么说,只调用 unmap 实测发现数据是更新的, 难道我理解错了。
    xujiajun001
        18
    xujiajun001  
    OP
       2019-03-07 09:14:43 +08:00
    xujiajun001
        19
    xujiajun001  
    OP
       2019-03-07 09:14:59 +08:00
    @runningman 谢谢 你说的很中肯哈
    xujiajun001
        20
    xujiajun001  
    OP
       2019-03-07 09:16:34 +08:00
    @JohnSmith 我发现数据还是会更新到磁盘的。我是调用了 unmap,可能再加个 flush 的话更保障一点。
    Damnever
        21
    Damnever  
       2019-03-07 09:36:57 +08:00 via iPhone
    @xujiajun001 文档说的没错,你用错了,要保证 nutsdb 宣称的 ACID 等高大上的特性,每次 commit 你都必须的 flush/sync,而 ACID 也不是说你简单加个锁就保证
    srt180
        22
    srt180  
       2019-03-07 09:48:21 +08:00
    想听一下作者对 redis 的看法。
    虽说 redis 的事务不具备原子性,但是 redis 可以通过 lua 脚本支持事务。
    BBCCBB
        23
    BBCCBB  
       2019-03-07 10:21:11 +08:00
    =_= 楼主,我是来求资料的, Go 的 mmap 相关的貌似每个操作系统的 api 都不一样? 这方面的博客好少, 楼主有 Go 的 mmap 相关的资料吗, 弱弱求一份, 我大 Java 里的 mmap api 是统一的, Go 的这个搞的我很头疼 ..

    先 star.
    xujiajun001
        24
    xujiajun001  
    OP
       2019-03-07 10:42:48 +08:00
    @Damnever 谢谢你的意见建议。为了保证 ACID 的 D 特性 每次 commit 你都必须的 flush/sync,这个我理解的,强一致性。nutsdb 的实现为了写性能更高,现在对 nutsdb 的做法有点缓冲的味道,到 unmap 的时候才能保证数据一定落盘。
    ” ACID 也不是说你简单加个锁就保证“,能多说一点吗?
    Damnever
        25
    Damnever  
       2019-03-07 10:59:18 +08:00 via iPhone
    @xujiajun001 为了性能高这样做是没问题,但这个权衡牺牲了其它的特性就不能再说 nutsdb 有 ACID 等等特性,没有贬低的意思,简单的从 benchmark 来看这个惊人的结果就值得仔细思考下了,毕竟其它数据库生产环境验证优化了多年

    就简单的说下 A 吧,毕竟是落盘的,简单的 unlock 来 rollback 就说不过去了
    Damnever
        26
    Damnever  
       2019-03-07 11:09:05 +08:00 via iPhone
    @xujiajun001 代码没细看,可能我说的不对,但 ACID 这一块我觉得你还是得仔细考量下,简单并不一定正确
    xujiajun001
        27
    xujiajun001  
    OP
       2019-03-07 15:11:26 +08:00
    @srt180 redis 我一般当做缓存来用,不会去当 db 使用,事务支持也很弱。不过他的支持丰富的数据结构真的很棒。nutsdb 的数据结构 api 就是模仿他的名字命名。至于 lua 脚本的方式 ,我没试过,不过我在想在代码里写脚本 ,优点是灵活 缺点有点像直接写原生 sql 的感觉,不好维护 也不好看吧。最好 Redis 原生支持最好。
    xujiajun001
        28
    xujiajun001  
    OP
       2019-03-07 15:15:47 +08:00
    @Damnever 好的 我再想想。谢谢你。
    xujiajun001
        29
    xujiajun001  
    OP
       2019-03-07 15:18:45 +08:00
    rebill
        30
    rebill  
       2019-03-07 16:52:34 +08:00
    如果抛开事务支持这一特性,NutsDB 可以秒杀其他了呢?
    Damnever
        31
    Damnever  
       2019-03-07 23:04:51 +08:00
    @rebill 这就是一种可能存在的误解了.. 所以作者这个项目如果作为自身学习是没问题的,大家也很支持,问题在于文案写的“越来越认真”导致了一些严重错误的东西被掩盖对于其它学习或者使用者来说是很误导人的
    xujiajun001
        32
    xujiajun001  
    OP
       2019-03-08 10:19:03 +08:00
    @Damnever 认同 “导致了一些严重错误的东西被掩盖对于其它学习”, 已经更新了 README 在警告说明处做了阐述,我还会继续更新文档的,让我好好整理下。谢谢 Damnever 关注。有问题 我继续改。
    另外大家希望多多提 issue。
    @1892 有问题直接去项目提 issue 哦。
    xujiajun001
        33
    xujiajun001  
    OP
       2019-03-11 20:43:06 +08:00
    @Damnever 你好 我已在最新的 master 和当前最新的版本 v0.3.0 支持了 强同步。真正支持了 D (持久化),性能报告也重新更新了。欢迎帮我 review,如有问题帮我指正。thank ;)
    Damnever
        34
    Damnever  
       2019-03-14 13:11:24 +08:00 via iPhone
    @xujiajun001 review 就谈不上了,benchmark 结果很不错,但要做全做好也是个学问,我不太懂就不瞎说了
    Damnever
        35
    Damnever  
       2019-03-14 13:17:47 +08:00 via iPhone
    @xujiajun001 就测试的规模来看,顶多几十兆的数据为啥还要搞个嵌入式的 kv 这么麻烦呢?
    xujiajun001
        36
    xujiajun001  
    OP
       2019-03-15 09:56:43 +08:00
    @Damnever 是的,做全做好也是个学问,我认可的。 我也在探索中,一有空就在改进优化中。后面有精力打算做成分布式的,这是后话。数据量的话, 当前版本 nutsdb 的数据存储上限取决于你的配置,如果是默认的全内存索引模型,瓶颈是内存,还有一种是内存+磁盘索引的模式,会放下比内存大的数据量。nutsdb 的场景数据,几十 M 肯定不止的。
    Damnever
        37
    Damnever  
       2019-03-15 10:35:21 +08:00 via iPhone
    @xujiajun001 我的意思是 benchmark 数据量可能太小了
    xujiajun001
        38
    xujiajun001  
    OP
       2019-03-15 23:10:31 +08:00
    @Damnever 嗯 。后面有空去弄下大一点的数据量的性能表现。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3613 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 04:42 · PVG 12:42 · LAX 20:42 · JFK 23:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.