背景

​ 之前在知乎上看到一个问题:作为一个KV数据库,levelDB为什么使用LSM树实现,而不是hash索引?当时就想作答一番。不过看到问题下方已经有大佬作答了,而我也说不出什么新东西来。于是选择作罢。

​ 但是最近有nutsdb用户提了一个issue。大致描述的场景是,他们每天大概有1亿的数据写入,用badgerdb是可以cover住的,但是用nutsdb每次都会因为内存耗尽而提前终止。这个问题正好也涉及到LSM和Hash索引这两种存储架构。也让我联想起在那个知乎上的问题,觉得可以写一篇文章分析这种情况。**为什么同样是一亿数据,nutsdb内存会不够用,而badgerdb却能cover得住呢?**顺便我们也可以从内存的角度来看,在大规模数据的背景下这两种架构的对应实现的表现如何。

​ 我们知道,论文和实际落地有时候是有比较大的出入的,所以这篇文章主要会关注LSM和Bitcask(Hash索引的典型代表)的对应实现,也就是leveldb和nutsdb,为什么是leveldb呢,因为badgerdb是在leveldb的基础上实现了kv分离以达到减少读写放大,这个feature并不会对这篇文章的讨论结果有多少影响,所以为了降低文章的复杂度,本文决定选取leveldb作为观察对象。分析现实世界中这两者的异同。本人水平有限,分析过程中有不当之处还请批评指正。

前置知识

​ 这篇文章不会深入到源码级别,重点是理论分析。很多地方不会讲述技术细节,而是一笔带过。所以需要朋友们对一些前置的知识有一定的了解。我找了一些个人认为讲的比较好的文章,让大家对LSM,Bitcask,leveldb,nutsdb有整体的了解。大家如果对以上的知识没有太多的了解的话,可以翻到文章最下方看延伸阅读上的材料哦~

1. nutsdb

  1. nutsdb写入和读取数据的流程相对比较简单,写入数据的时候会直接将数据encode成二进制数据,直接写入到磁盘中。通过构建一个索引对象插入到内存索引数据结构(Bitcask中使用的是hashmap,而nutsdb实现是使用B+Tree)的方式记录数据在磁盘的位置(文件名+数据在文件中的偏移)。

  2. 转过来看,读取数据的时候可以在内存的索引结构中得到数据的的位置,然后用一次系统调用将数据取出。

​ 所以我们可以知道的是,nutsdb每新增一条数据,都要在内存中构建一个索引对象去标识数据的位置,以方便我们可以根据这个位置读取出来。所以当数据量很大的时候,索引对象会持续增多,内存迟早是会扛不住的。当然我们可以使用一些压缩算法,比如在论文《Optimistically Compressed Hash Tables & Strings in the USSR》中提到的对hash的压缩,将内存中的索引数据进行一定程度的压缩。不过这样是治标不治本的,只能延缓内存爆满的发生,随着数据的增长内存该炸还是会炸的。

2. goleveldb

​ 相比而言leveldb的情况就复杂很多。Leveldb存储的数据分为两部分,一是存储在WAL和与之对应的MemTable的数据,另外是以SST形式组织起来数据,也就是经过Minor Compaction和Major Compaction之后形成的一层层数据文件。

​ 所以归结起来在leveldb中,内存里会有一下三样东西。1. Memtable 2. LRU缓存(用来缓存在SST中读取出来的block,其中有索引内容,也有数据内容) 3. SST的内存索引。

​ Memtable和LRU缓存大小是配置可控的,所以在这一块上,可谓穷有穷的玩法,富有富的玩法。也就说如果内存充足可以在Memtable和LRU放置更多更多,内存预算不是很多的话就可以分配少一点,memtable放的东西少会频繁触发minor compaction, LRU少会频繁发生IO系统调用读取磁盘数据,不过无论内存情况怎么样,memTable和LRU缓存始终是能玩的。还有剩下的一个,SST的索引,是什么呢?

​ 我们之前讲到,程序是运行在内存中的,对于持久化存储引擎而言,数据是会落到磁盘上保存的,但是磁盘不知道你存了什么进来,所以需要存储引擎设计一些算法精准找到磁盘中的数据。Bitcask模型比较简单,也就是一条数据对应一个索引。这样直接可以读取数据了。但是在leveldb中,他能够以很小的内存管理一大片磁盘中的数据,让我们看看他是怎么做到的。

​ 我们可以在上图中看到leveldb会有因为一次次compaction操作所形成的一层层的SST文件,当然我们要知道的是,这里所谓的一层层,是我们的逻辑概念,磁盘是不会帮你维护这个逻辑的。所以在内存中我们需要维护这个关系。那么他是怎么做的呢,下面我们看看goleveldb代码实现。

var levels []tFiles// tFiles hold multiple tFile.
type tFiles []*tFile// tFile holds basic information about a table.
type tFile struct {fd         storage.FileDescseekLeft   int32size       int64imin, imax internalKey
}

​ 我们可以看到,levels代表所有层次SST文件的所有索引信息。而tFiles代表一层的SST文件的索引信息。tFile结构维护的是一个SST文件的索引信息,其中最重要的内容其实是imin和imax,标识了这个文件中存储的数据Key的范围。查询数据的时候可以依次遍历每一层,因为上层的数据总是比下层的新,如果在当层找到了就不需要往下找了,找不到就继续往下层找,直到遍历完最后一层。在同层中可以通过比对Key与tFile中的存储的imin和imax看下key是否存在于这个文件中。如果存在的话就可以读取这个文件来找到这条数据。得益于SST的优秀设计,找寻数据就像开启一个又一个的盲盒,在即有的提示之下不断的迫近数据所在,直到最后豁然开朗找到数据。而这一个个盲盒很大一部分又都在磁盘中。不会像nutsdb那样所有数据都需要一个索引对象存在与内存中。如下图所示的SST文件的组织结构。

​ 让我们看看要在SST中读取一条数据需要打开几个盲盒。首先我们会在内存中判断这个要读取的key是不是在imin和imax之间,如果是的话,就读SST的Footer,Footer会提示我们找到SST数据模块的索引信息(Index Block)以及索引信息的描述信息(Meta Index Block,布隆过滤器是可选的,所以这个实际上可能不一定有)。根据Index Block我们可以找到数据所在。当然这里不会展开SST中每个Block是怎么设计的,感兴趣的朋友可以看延伸阅读哦。

​ 我们可以看到,通过高效的数据组织形式。只需要很小一部分内存,也就是levels这个对象里面包含的东西,存储少量的索引对象在内存中,也可以管理很大量的磁盘数据,也就是大量的数据。这样虽然会引入多次的系统调用才能最终找到数据,但是这些读取过的block都可以被缓存起来,后面也不需要在重复发生系统调用了。

​ 综上所讲述,leveldb在内存的使用上,无论数据量的多少可以达到一个比较健康的状态。数据量多的时候,可能会出现零散读取数据的情况,会频繁的在缓存中进行换入换出,性能上会变慢,但是不至于不可用,换一个角度来说,数据量如果很大,系统变慢,从感官上来说,应该是属于正常的。但是不可用,很难接受的。

​ 说到这里你可能会问了,nutsdb也可以这样组织数据嘛,这不就解决内存问题了?这个问题问的很好,但是理论上来说,是不太可能的。为什么呢?

3. nutsdb可以换一个数据组织方式吗?

​ 为什么说不太可能呢?问题的关键在于,nutsdb的数据是乱序存储的,而leveldb是有序存储的。正是这一字之差造成了这样的情况。为什么这么说呢?

​ 在我们之前的文章中,讲过在nutsdb中一条数据在磁盘里是这样存在的。数据是以下图的形式一条条的追加写入到磁盘之中。可以理解为,在磁盘中相邻数据之间没有任何关系,所以需要在内存中记录每一条数据在什么位置。不然就不知道怎么找数据了。

​ 那么leveldb呢?为什么数据有序就可以做到呢?得益于Memtable这样的内存数据结构,在leveldb中他的实现是跳表。在跳表的最下面一层就是一条有序的链表,可以通过一次遍历就写入这些数据到SST的Data Block中。也就是说在SST中数据之间其实是有序排列的。这样就可以通过不断的缩小范围找到数据啦。

总结

​ 说到这, 其实并不是bitcask的设计不行,正所谓架构没有最好和最坏,只有最合适的。bticask读写性能都比较稳定,点查场景中速度较快,但是缺点就是内存占用会随着数据的增长而增长。leveldb读会稍微差一点,因为需要多次io才能找到数据,并且有时候会跨越多个层级读取SST。不过bitcask的设计会存在一个问题,就是随着数据的不断增长,内存也会水涨船高,如果您有比较大量的数据,不是很建议使用bitcask实现的存储引擎去存储哦。在这里我要由衷赞叹的是,SST的设计确实精美绝伦。

延伸阅读

  1. 知乎上的问题:https://www.zhihu.com/question/27256968/answer/35881582
  2. nutsdb issue:https://github.com/nutsdb/nutsdb/issues/239
  3. LSM论文翻译:https://xonlab.com/2021/01/10/%E8%AE%BA%E6%96%87%E7%BF%BB%E8%AF%91The%20Log-Structured%20Merge-Tree%EF%BC%88LSM-Tree%EF%BC%89/
  4. leveldb架构解析:https://zhuanlan.zhihu.com/p/436037845
  5. SST组织结构详解:https://leveldb-handbook.readthedocs.io/zh/latest/sstable.html

同样是1亿数据,为什么nutsdb扛不住,而badgerdb可以?相关推荐

  1. 入职第一天,老板竟然让我优化5亿数据量,要凉凉?

    jsoncat:https://github.com/Snailclimb/jsoncat[1] (仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架) 前段 ...

  2. 耗时3天,上亿数据如何做到秒级查询?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:sohu.gg/jIp59N 最近在忙着优化集团公司的一个报 ...

  3. 6亿数据秒级查询,ClickHouse太快了!

    " ClickHouse 在数据分析技术领域早已声名远扬,最近由于项目需求使用到了 ClickHouse 做分析数据库,于是用测试环境做了一个单表 6 亿数据量的性能测试. 图片来自 Pex ...

  4. 100亿数据1万属性数据架构设计

    一分钟系列之<啥,又要为表增加一列属性?>分享了两种数据库属性扩展思路,被喷得厉害.第二天补充了一篇<这才是真正的表扩展方案>,分享了互联网大数据高并发情况下,数据库属性扩容的 ...

  5. 判断数组中某个元素除自身外是否和其他数据不同_布隆过滤器,我也是个处理过 10 亿数据的人...

    ❝ 文章收录在 GitHub JavaKeeper ,N线互联网开发必备技能兵器谱 什么是 BloomFilter 布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的.它实际上是 ...

  6. 查询a表有但是b表没有的数据_牛逼!一个上亿数据的报表竟然能做到秒查~

    数据背景 首先项目是西门子中国在我司实施部署的MES项目,由于项目是在产线上运作(3 years+),数据累积很大.在项目的数据库中,大概上亿条数据的表有5个以上,千万级数据的表10个以上,百万级数据 ...

  7. Redis 10亿数据量只需要100MB内存,为什么这么牛?

    作者:java架构设计   来源:toutiao.com/i6767642839267410445 本文主要和大家分享一下redis的高级特性:bit位操作. 力求让大家彻底学会使用redis的bit ...

  8. 耗时 3 天,上亿数据如何做到秒级查询?

    最近在忙着优化集团公司的一个报表.优化完成后,报表查询速度由从半小时以上(甚至查不出)到秒查的质变.从修改 SQL 查询语句逻辑到决定创建存储过程实现,花了我 3 天多的时间,在此总结一下,希望对朋友 ...

  9. 1万属性,100亿数据,每秒10万吞吐,架构如何设计?

    有一类业务场景,没有固定的schema存储,却有着海量的数据行数,架构上如何来实现这类业务的存储与检索呢?58最核心的数据"帖子"的架构实现技术细节,今天和大家聊一聊. 一.背景描 ...

最新文章

  1. Windows mobile UI
  2. c语言定时器_分享10个值得关注的C语言开源项目
  3. Scala模式匹配:对规则进行匹配
  4. 研发大佬组团带玩生成对抗网络(GAN),B站直播教学
  5. ExceptionLess新玩法 — 记日志
  6. 计算机二级web考点,2018年计算机二级考试WEB考点:web应用程序状态管理方式
  7. hive 如果表不存在则创建_从零开始学习大数据系列(四十七) Hive中数据的加载与导出...
  8. Java中的java.util包
  9. C++课后习题第五章17
  10. .Net Core Linux centos7行—.net core json 配置文件
  11. 关于学习js的Promise的心得体会
  12. windows2008服务器安全防护软件哪个好
  13. ppi 各代iphone_各代iPhone逻辑分辨率与物理分辨率
  14. 2021年中国医药工业经济运行现状及行业发展建议:主营业务收入、利润总额整体递增,建议加大监管,引导产业良性发展[图]
  15. Apache PdfBox 2.0.X 版本解析PDF文档(文字和图片)
  16. 进制的转换 如六进制
  17. DHPST分销系统-EP分销-云主机分销系统
  18. Redis设计与实现详解二:Redis数据库实现
  19. 【自动控制原理】根轨迹法之绘制根轨迹
  20. [离散数学]命题逻辑P_7:范式

热门文章

  1. ABB机器人开发基础之碰撞监控设置
  2. C906 RISCV HPM(PMU)使用方法
  3. 鲜枣课堂线上课程秋季促销,明天正式开始!
  4. python画螺旋图
  5. mysql如何查看数据库uuid_如何在MySQL数据库中使用uuid而不是整数
  6. 切换显卡 html5 黑,双显卡怎么切换到独立显卡 5步轻松搞定【图文教程】
  7. html伪类元素加图片,HTML中常见伪类和伪元素的区别
  8. FPGA实现MPEG2视频压缩 提供工程源码和技术支持
  9. spring-security验证登录https变成http导致登录跳转失败
  10. .net core基础入门