嵌入式键值db与每个键只存储一个文件?

时间:2018-04-10 03:23:56

标签: sqlite go embedded-database rocksdb

我对嵌入式键值数据库的优势感到困惑,而不是仅仅在每个键上将一个文件存储在磁盘上的天真解决方案。例如,RocksDB,Badger,SQLite等数据库使用奇特的数据结构,如B +树和LSM,但似乎与这个简单的解决方案大致相同。

例如,Badger(最快的Go嵌入式数据库)需要about 800 microseconds来写入条目。相比之下,从头开始创建一个新文件并将一些数据写入其中需要150个麦克风而没有优化。

编辑:澄清一下,这里是键值存储的简单实现,我与现有的嵌入式dbs进行了比较。只需将每个键散列为字符串文件名,并将相关值存储为该文件名的字节数组。读取和写入每个约为150个麦克风,这比单个操作的Badger更快,并且与批量操作相当。此外,磁盘空间很小,因为除了实际值之外我们不会存储任何额外的结构。

我必须在这里遗漏一些东西,因为人们实际使用的解决方案非常花哨,并使用布隆过滤器和B +树等优化。

3 个答案:

答案 0 :(得分:2)

但是Badger并不是要撰写“一个”条目:

  

我的写作非常慢。为什么?

     

您是否为每个密钥更新创建了一个新事务?这将导致非常低的吞吐量。

     

要获得最佳写入性能,请使用单个DB.Update()调用批量处理事务内的多个写入。
  您还可以从多个goroutine同时进行多个此类DB.Update()调用。

这会导致issue 396

  

我在Go中寻找快速存储,所以我的第一次尝试是BoltDB。我需要很多单写事务。博尔特能够做到大约240 rq / s。

     

我刚刚测试了Badger,我得到了一个疯狂的10k rq / s。我感到困惑

那是因为:

  

在写入时,LSM树与B +树相比具有优势   此外,值分别存储在值日志文件中,因此写入速度更快。

     

你可以read more about the design here

其中一个要点(难以通过简单的文件读/写复制)是:

  

键值分离

     

LSM树的主要性能成本是压实过程。在压缩期间,多个文件被读入内存,排序和写回。对于有效的检索,排序对于密钥查找和范围迭代都是必不可少的。通过排序,密钥查找只需要访问每个级别最多一个文件(不包括零级,我们需要检查所有文件)。迭代将导致对多个文件的顺序访问。

     

每个文件都是固定大小的,以增强缓存。值往往大于键。将值与键一起存储时,需要压缩的数据量会显着增加。

     

在Badger中,只有指向值日志中值的指针与键一起存储。 Badger对键使用delta编码以进一步减小有效大小。假设每个密钥16个字节,每个值指针16个字节,单个64MB文件可以存储200万个键值对。

答案 1 :(得分:0)

您的问题假设唯一需要的操作是单个随机读取和写入。对于像Badger或RocksDB这样的日志结构合并(LSM)方法,这些是最坏的情况。返回范围中的所有键或键-值对的范围查询利用顺序读取(由于文件中排序的kv的邻接)以非常高的速度读取数据。对于Badger,如果只执行键或较小值范围的查询,则大多数都会受益,因为它们存储在LSM中,而较大的值则附加在不必要排序的日志文件中。对于RocksDB,您将获得快速的kv对范围查询。

上一个答案在某种程度上解决了写入方面的优势-使用缓冲。如果您编写许多kv对,而不是将每个kv对存储在单独的文件中,则LSM方法会将其保存在内存中,并最终在文件写入中将其刷新。没有免费的午餐,因此必须进行异步压缩以删除覆盖的数据并防止检查太多文件以进行查询。

答案 2 :(得分:0)

以前回答过 here。与此处提供的其他答案大致相似,但提出了一个重要的附加点:文件系统中的文件不能占用磁盘上的同一块。如果您的记录平均显着小于典型的磁盘块大小 (4-16 KiB),将它们存储为单独的文件会产生大量的存储开销。