【问题标题】:Embedded key-value db vs. just storing one file per key?嵌入式键值数据库与每个键仅存储一个文件?
【发布时间】:2018-04-10 03:23:56
【问题描述】:

我对嵌入式键值数据库相对于每个键仅在磁盘上存储一个文件的简单解决方案的优势感到困惑。例如,RocksDB、Badger、SQLite 等数据库使用 B+ 树和 LSM 等花哨的数据结构,但似乎获得与这个简单解决方案大致相同的性能。

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

编辑:澄清一下,这是我与最先进的嵌入式数据库进行比较的键值存储的简单实现。只需将每个键散列到字符串文件名,并将关联的值存储为该文件名的字节数组。读取和写入每个大约 150 个麦克风,对于单个操作来说比 Badger 快,对于批量操作来说是可比的。此外,磁盘空间非常小,因为除了实际值之外,我们不存储任何额外的结构。

我一定在这里遗漏了一些东西,因为人们实际使用的解决方案非常精美,并且使用了布隆过滤器和 B+ 树之类的东西进行了优化。

【问题讨论】:

  • 数据库不仅仅是将内容写入磁盘,而是如何管理它们(事务、查询、更新、排序等)。如果您将内容存储到原始文件(例如每个密钥一个磁盘),您将不会拥有这些优势,除非您自己实现这些功能。此外,每个文件都有占用磁盘空间的元数据。如果您的内容大小小于元数据大小,那么“每个密钥一个文件”的解决方案将是低效的。
  • 这里只是一个疯狂的猜测,我认为随着越来越多的小文件的添加,您最终会遇到文件系统瓶颈。此外,随着每批中键数量的增加,批量写入和读取可能会超过单个文件的打开/写入/读取操作?

标签: sqlite go embedded-database rocksdb


【解决方案1】:

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

我的写作真的很慢。为什么?

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

要获得最佳写入性能,请使用单个 DB.Update() 调用在一个事务中批量写入多个写入。
您还可以从多个 goroutine 同时进行多个这样的 DB.Update() 调用。

这导致issue 396:

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

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

那是因为:

在写入方面,LSM 树与 B+ 树相比具有优势。
此外,值单独存储在值日志文件中,因此写入速度要快得多。

你可以read more about the design here。


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

键值分离

LSM 树的主要性能成本是压缩过程。在压缩期间,多个文件被读入内存、排序和写回。对于键查找和范围迭代,排序对于有效检索至关重要。使用排序,键查找每个级别最多只需要访问一个文件(不包括零级,我们需要检查所有文件)。迭代会导致对多个文件的顺序访问。

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

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

【讨论】:

  • 感谢您的回复!批处理磁盘写入可以提高速度是有道理的,但即使是这些批处理写入也比我想象的要慢。您引用的 10k/sec 仍然是每次写入 100mics,这与为每次写入打开单独的文件一样慢。
  • @rampatowl 设计文档显示了除了写作之外的其他优势。
  • 不过,读/写时间似乎应该是比较嵌入式键值数据库的主要方式。令我惊讶的是,最先进的数据结构大约与为每个键打开您自己的文件一样快(每次读/写 100 个麦克风)。我错过了什么吗?
  • @rampatowl 这不是关于读/写文件,而是关于条目查找优化。
  • 我编辑了我的原始帖子并进行了澄清。我将 Badger 的操作时间与文件读/写时间进行比较的原因是,您可以实现一个最小的键值存储,每个键一个文件,其中每个条目查找所需的时间与单个文件访问一样长(无需额外的数据结构)。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-12
      • 2013-02-28
      • 2017-12-20
      • 2012-04-04
      • 1970-01-01
      • 2014-11-27
      • 2013-07-24
      • 2010-10-13
      相关资源
      最近更新 更多