深入理解LSM-Tree_sorted run lsm-程序员宅基地

技术标签: 云存储技术  lsm  数据库  存储技术  


lsm-tree的背景、定义和适用场景本文不再详述。本文意在论述LSM的存储引擎、工业取舍以及发展现状。

基础概念

  1. 读放大(read amplification):一次读取操作需要访问磁盘的次数,主要由compaction策略引入。可以引入cache和bloom filter优化。
  2. 写放大(write amplification):一次写操作带来的数据落盘的次数。在LSM-Tree中可由某个key数据在磁盘上存储的数量这个指标体现,主要是由compaction策略引入的。
  3. 空间放大(space amplification):实际空间占用/理论数据量,LSM-Tree的架构导致空间放大必然存在
  4. 临时空间:compaction任务需要临时的空间来完成多个sstable的合并。
  5. run:借用wiki中的解释:
    • The on-disk data is organized into sorted runs of data. Each run contains data sorted by the index key. A run can be represented on disk as a single file, or alternatively as a collection of files with non-overlapping key ranges. To perform a query on a particular key to get its associated value, one must search in the Level 0 tree and each run.

    • 可见,每一个run都是有序的数据块。

compaction策略

LSM-Tree存储引擎的工作流程如下:

  1. 当写入时,将其天际到内存的平衡树数据结构中(如红黑树、AVL树或SkipList),这个内存树有时候被称为MemTable(leveldb)。
  2. 当内存表大于某个阈值(通常以MB为单位),将其作为SSTable文件写入磁盘,由于树已经维护了按键排序的key-value对,写磁盘的效率可以比较高效。新的SSTable文件称为数据库的最新部分,当SSTable写磁盘的同时,写入可以继续添加到一个新的内存表中
    1. 不同的实现使用了不同的压缩合并策略,用以优化write-intensive或read-intensive场景。
    2. LevelDB和RocksDB使用leveled compaction,HBase采用tiered compaction,Cassandra则同时支持这两种压缩。
  3. 为了处理读请求,首先尝试在内存表中查找key,然后按照LSM-Tree的层级依次向下查找,知道找到或未找到目标。
  4. 后台进程周期性的执行段合并与压缩过程,合并多个SSTable,丢弃已经stale的值。

从上面的存储流程可以看出,LSM-Tree的性能主要取决于Compaction策略,它主要分为两类,tiered compactionleveled compaction

  • tiered流派从Bigtable->HBase->Cassandra一脉相承,后面又有MyRocks、InfluxDB、Kudu等等
  • Leveled流派工程实现主要有LevelDB->RocksDB

Size-tired compaction strategy(STCS)/Tiered

tiered适合write-intensive workload,有较低的写放大,缺点是读放大和空间放大较高。

STCS

tiered compaction的策略是:memtable达到阈值之后刷入下一层(落盘)作为一个run,每层的run达到一定数量之后,将当前层所以的run进行compaction并刷入下一层,循环往复。这种策略给人直观的感受是,越下层的run越大,当然实际的策越要比描述的复杂,因为run与run之间重叠的部分是不确定的,有可能完全重叠,也有可能完全不重叠。

  • 由于所有的上层level的数据可以直接追加写入下层,所以写放大比较低
  • 由于每层都有多个run,且每个run中的数据可以重叠,所以点读操作需要由上到下遍历每一个run,读放大较严重。更不要提range查询肯定要合并每一层的每一个run的查询结果,读放大更严重。
  • 由于每层有多个run,每个run都可能含有重叠数据,所以空间放大较为严重。空间放大可以说是STCS最严重的问题了。Scylla的文章通过实验论证了这个问题
  • 每层的多个run需要先合并,才能写入下层,因此有可能导致临时空间较大。耗时也较长
  • 这里讨论一个特例time-series data。key是时间,几乎是恒定递增的,因此越是下层,run越是不重叠,写入和查询的策略都可以做对应的优化。

leveled compaction

leveled compaction和上面的STCS相比,降低了空间放大和读放大,比较适合read-intensive workload,当然这个也只是相对而言,如果真的是读很多,不如用B+ Tree。

leveled

leveled compaction每层只维护一个run,按照key排序可以分为多个partition(SSTable file)。每层容量达到一定限制后,选择某个sstable与上层merge。

  • 由于每层只有一个run,所以降低了读放大和空间放大。但是点查询仍然有可能会遍历所有的run、范围查询必然会遍历所以的run。
  • 由于leveled compaction上层与下层merge的过程有可能涉及到上下层所有的数据,可能造成上层run全量重写,导致写入放大,但是由于每个run可以分为多个partition(sstable),因此可以节约部分临时空间。
Leveled-N

不同于leveled,Leveled-N允许每层多个Sorted run。Dostoevsky paper引入了一种Fluid LSM:允许最高层只有一个run,而其它层可以有多个run。

Hybrid

这里讨论scylladb实现的Hybrid模式。主要体现在这篇文章,其希望推出一种模式,可以吸纳tiered和leveled两者的优点。scylladb引入Hybrid主要为了优化tiered糟糕的空间放大。我推测主要有以下几点优化:

  1. 总体布局和tiered类似,只不过每层的run的数量可以根据负载动态调节,下层的run可以直接merge到上层的run里面。
  2. 运行时可以感知到当前磁盘剩余空间和run的key分布,如果剩余空间不多或这个每个run重叠太多(说明是overwrite场景),则compaction略逐步由tiered向leveled转移。
  3. 总的来说我觉得这个优化是将rocksDB繁杂的优化参数调整为代码中的一些策略,以优化特定的workload

example

Time-Window

这里主要讲scylladb/Cassandra针对时序数据引入的Time-Window compaction,时序数据有以下特点:

  1. key是单调递增的(timeline),
  2. 写入速率基本恒定,
  3. 查询方式以range-query为主,如查询某年、月、日的数据
  4. 可以通过设置TTL的方式删除超过一定时间的数据。

这种数据状况下,通过将不同的时间分配在不同的Time-Window中,将SSTable分配成了一个个time bucket,实现形式上采用tiered compaction,不同的time bucket不会合并(优化读操作)。由于overlap并不多,所以compaction压力不大,空间放大也不大。由于数据分布较好,查询也较方便。

比较

scylladb的文章总结了以下表格:

workload Size-Tiered Leveled Hybrid Time-Window
Write-only 2x peak space 2x writes Best -
Overwrite(指同一个key多次update) Huge peak space write amplification high peak space bug not like size-tiered -
Read-mostly, few updates read amplification Best read amplification -
Read-mostly, but a lot of updates read and space amplification write amplification may overwhelm read amplification -
Time series write,read,and space amplification write and space amplification write and read amplification best

工业实现

leveldb

顾名思义,levelDB使用leveled compaction,其CRUD流程如下:

  1. 写流程:
    1. 找到合适的memtable(skiplist)并写入数据,如果memtable写完并且immutableTable还未完成持久化,或者L0文件太多,则等待。如果memtable满了,则换一个memtable,并触发compaction。由此可见,l0tiered,对于每个run,key的范围有重叠。其它层是leveled
    2. 写日志,下刷日志
    3. 写memtable
  2. 读流程:
    1. 首先查找memtable,再查找immutable memtable
    2. 如果没有,查找下面所有持久化的levelVersion::Get,逐层向下搜索。搜索方法的策略是并不是遍历每一个sstable,而是先看需要查找的key是否落在sstable的范围内,如果落在,对sstable搜索。
    3. 对于读取mem中的数据(skiplist),正向遍历时间复杂度是O(1),反向遍历时间复杂度是O(log(N))
    4. 对于读取sstable中的数据,正向遍历有指针指向下一个数据位置,反向遍历每次Prev()都要重新定位
  3. 删除流程:
  4. 后台线程也会按需做compaction。通过MaybeScheduleCompaction()函数判断。

levelDB代码清晰,代码量少,阅读方便,不过缺少大规模工程应用场景。

RocksDB

rocksdb

LSM-Tre工业级实现,和levelDB相比,参数更多,优化更多,使用更灵活,Features Not in LevelDB这篇文章介绍了RocksDB相对于levelDB所做的优化,我选择一些优点列举如下:

  1. 实现了tiered(Universal Compaction),tiered+leveled(l0 tiered,高层leveled),和FIFO compaction(一条数据写入后,超过 TTL 指定的时间戳,就过期淘汰,对用户不可见。)
  2. 前缀遍历,针对相同前缀(prefix)的场景做了优化Options.prefix_extractor
  3. checksum数据校验
  4. 支持多线程compaction
  5. 支持全量和备份
  6. 支持read-only模式以优化读速度
  7. memtable优化:
    1. 支持多种数据结构:skiplist为默认,vector模式以优化Bacth-insert,hashtable模式以优化读操作。
    2. 支持多memtable并发插入(Memtable Pipelining)
    3. memtable持久化的时候会做合并,GC掉某些旧数据
  8. Column Families,可以设置某个kv归并为一个column family列族,如{cf1, key1, value1},列族之间可以相互隔离(不共享memtable和sstable)
Write Stalls

RocksDB会在某种情况下限制前台写请求的速度,称为Write Stalls。产生的原因不外乎因为某种原因,compaction的速率赶不上前台write的速率了。这种现象如果持续下去,会产生以下结果:

  1. 空间放大增大,写入的数据来不及压缩,最终耗尽磁盘空间
  2. 读放大增大,越是来不及compact,读放大就越大

这种情况下需要将前台写请求降速,直到compaction执行到某种可以继续高效处理前台读写请求的程度。

一般情况下,Write Stall产生的原因有以下几种可能:

  1. memtable,前台写入太快,memtable过多,而且都没有flush进l0
  2. l0 SSTable过多,导致读放大、merge也慢
  3. pending compaction bytes过多。磁盘 I/O 能力在业务高峰跟不上写入

scyllaDB/cassandra

scyllaDB/cassandra默认采用tiered,可以配置为levled、Hybrid或Time-Window。只有Hybrid是ScyllaDB独有。实现细节如上所述

hbase

HBase是一个分布式,版本化,面向列的开源数据库(面向列族Column Family,在)、分布式持久化KV数据库。HDFS(仅支持追加写)为Hbase提供可靠的底层数据存储服务,MapReduce为Hbase提供高性能的计算能力,Zookeeper为Hbase提供稳定服务和Failover机制,因此我们说Hbase是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案。Hbase适合存储PB级别的海量数据,虽然当个请求是时延不低,但是可以通过并行请求增大吞吐,并且单个IO的时延下降并不多。

架构和bigtable比较类似,通过key-range,将数据水平切分到多台服务器上,数据持久化在HDFS中。这点和现代的应用如tidb略有不同,其是将数据切分之后通过一致性协议存储在多个RSM上,每个RSM的数据引擎为LSM-Tree

Hbase 技术细节笔记(上)

hbase在实现中,是把整个内存在一定阈值后,flush到disk中,形成一个file,这个file的存储也就是一个小的B+树,因为hbase一般是部署在hdfs上,hdfs不支持对文件的update操作,所以hbase这么整体内存flush,而不是和磁盘中的小树merge update。内存flush到磁盘上的小树,定期也会合并成一个大树。整体上hbase就是用了lsm tree的思路。

因为小树先写到内存中,为了防止内存数据丢失,写内存的同时需要暂时持久化到磁盘,对应了HBase的HLog(WAL)和MemStore

MemStore上的树达到一定大小之后,需要flush到HRegion磁盘中(一般是Hadoop DataNode),这样MemStore就变成了DataNode上的磁盘文件StoreFile,定期HRegionServer对DataNode的数据做merge操作,彻底删除无效空间,多棵小树在这个时机合并成大树,来增强读性能。

TiKV/Titan

TiKV使用RocksDB作为存储引擎,并且实现了Titan作为RocksDB 的高性能单机 key-value 存储引擎插件。以支持大value(Blob),其核心特性在于支持将 value 从 LSM-tree 中分离出来单独存储,以降低写放大。

其主要设计灵感来源于 USENIX FAST 2016 上发表的一篇论文 WiscKey。WiscKey 提出了一种高度基于 SSD 优化的设计,利用 SSD 高效的随机读写性能,通过将 value 分离出 LSM-tree 的方法来达到降低写放大的目的。

Titan 适合在以下场景中使用:

  1. 前台写入量较大,RocksDB 大量触发 compaction 消耗大量 I/O 带宽或者 CPU 资源,造成 TiKV 前台读写性能较差。
  2. 前台写入量较大,由于 I/O 带宽瓶颈或 CPU 瓶颈的限制,RocksDB compaction 进度落后较多频繁造成 write stall。
  3. 前台写入量较大,RocksDB 大量触发 compaction 造成 I/O 写入量较大,影响 SSD 盘的寿命。

学术研究

Dostoevsky

这篇论文详细地分析了各种操作在tired和leveled不同策略下的i/o复杂度,使用了level num、相邻level size比、entry数量、buffer size等一系列相关变量推导出不同操作具体的I/O代价公式和空间放大,对于point read还考虑了bloom filter带来的影响,range query分了short和long两种来分析。并且提出了优化策略lazying leveling,是tiering和leveling的结合体,最大一层使用leveling其它层使用tiering,lazying适合包含update、point lookup和long range lookup的混合负载,tiering适合大多为update的负载,leveling适合大多为lookup的负载。通过控制merge频率在tiering、leveling和lazying leveling几种不同策略下转换以适应不同的workload,称作fluid LSM((类似rocksdb的leveled-n)),并提出Dostoevsky,在执行期间动态计算最优配置以到达最大吞吐。

从作者的分析中可以看出compaction这个机制的复杂程度,要使用一套通用机制在不同场景下消耗最少的资源带来最好的性能基本是不可能的,实际工业界实现中需要考虑更多的因素(cache、delete、任务拆分粒度、复用技术等),因此大部分系统使用多种策略多个参数用以适配不同场景。

参考链接

  1. Cassandra Compaction
  2. LevelDB 之 Compaction
  3. 深入探讨LSM Compaction机制
  4. LSM Tree的Leveling 和 Tiering Compaction
  5. Scylla’s Compaction Strategies Series: Space Amplification in tiered CompactionSTCS图出处
  6. Size Tiered Compaction Strategy In Apache Cassandra
  7. rocksdb Leveled Compaction 图文并茂
  8. RocksDB Overview
  9. RocksDB Performance Benchmarks
  10. Direct IO什么场景里可以使用directIO
  11. RocksDB调优指南翻译自官方wiki
  12. RocksDB中文网
  13. RocksDB Tuning Guide
  14. Compaction rocksdb讲tirered和leveled
  15. Name that compaction algorithmTiered/Leveled比较,这哥们专门做lsmtree的。
  16. How to Ruin Your Performance by Choosing the Wrong Compaction Strategyscylladb的这篇文章写得很好
  17. Scylla’s Compaction Strategies Series: Write Amplification in Leveled Compaction
  18. Scylla’s Compaction Strategies Series: Space Amplification in Size-Tiered Compaction
  19. Titan 的设计与实现
  20. SIGMOD’18|Dostoevsky
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zxpoiu/article/details/116190330

智能推荐

生活垃圾数据集(YOLO版)_垃圾回收数据集-程序员宅基地

文章浏览阅读1.6k次,点赞5次,收藏20次。【有害垃圾】:电池(1 号、2 号、5 号)、过期药品或内包装等;【可回收垃圾】:易拉罐、小号矿泉水瓶;【厨余垃圾】:小土豆、切过的白萝卜、胡萝卜,尺寸为电池大小;【其他垃圾】:瓷片、鹅卵石(小土豆大小)、砖块等。文件结构|----classes.txt # 标签种类|----data-txt\ # 数据集文件集合|----images\ # 数据集图片|----labels\ # yolo标签。_垃圾回收数据集

天气系统3------微服务_cityid=101280803-程序员宅基地

文章浏览阅读272次。之前写到 通过封装的API 已经可以做到使用redis进行缓存天气信息但是这一操作每次都由客户使用时才进行更新 不友好 所以应该自己实现半小时的定时存入redis 使用quartz框架 首先添加依赖build.gradle中// Quartz compile('org.springframework.boot:spring-boot-starter-quartz'..._cityid=101280803

python wxpython 不同Frame 之间的参数传递_wxpython frame.bind-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏8次。对于使用触发事件来反应的按钮传递参数如下:可以通过lambda对function的参数传递:t.Bind(wx.EVT_BUTTON, lambda x, textctrl=t: self.input_fun(event=x, textctrl=textctrl))前提需要self.input_fun(self,event,t):传入参数而同时两个Frame之间的参数传..._wxpython frame.bind

cocos小游戏开发总结-程序员宅基地

文章浏览阅读1.9k次。最近接到一个任务要开发消消乐小游戏,当然首先就想到乐cocosCreator来作为开发工具。开发本身倒没有多少难点。消消乐的开发官网发行的书上有专门讲到。下面主要总结一下开发中遇到的问题以及解决方法屏幕适配由于设计尺寸是750*1336,如果适应高度,则在iphonX下,内容会超出屏幕宽度。按宽适应,iphon4下内容会超出屏幕高度。所以就需要根据屏幕比例来动态设置适配策略。 onLoad..._750*1336

ssm435银行贷款管理系统+vue_vue3重构信贷管理系统-程序员宅基地

文章浏览阅读745次,点赞21次,收藏21次。web项目的框架,通常更简单的数据源。21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对银行贷款管理系统进行了介绍,包括研究的现状,还有涉及的开发背景,然后还对系统的设计目标进行了论述,还有系统的需求,以及整个的设计方案,对系统的设计以及实现,也都论述的比较细致,最后对银行贷款管理系统进行了一些具体测试。_vue3重构信贷管理系统

乌龟棋 题解-程序员宅基地

文章浏览阅读774次。题目描述原题目戳这里小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。乌龟棋的棋盘是一行 NNN 个格子,每个格子上一个分数(非负整数)。棋盘第 111 格是唯一的起点,第 NNN 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。乌龟棋中 MMM 张爬行卡片,分成 444 种不同的类型( MMM 张卡片中不一定包含所有 444 种类型的卡片,见样例),每种类型的卡片上分别标有 1,2,3,41, 2, 3, 41,2,3,4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数

随便推点

python内存泄露的原因_Python服务端内存泄露的处理过程-程序员宅基地

文章浏览阅读1.5k次。吐槽内存泄露 ? 内存暴涨 ? OOM ?首先提一下我自己曾经历过多次内存泄露,到底有几次? 我自己心里悲伤的回想了下,造成线上影响的内存泄露事件有将近5次了,没上线就查出内存暴涨次数可能更多。这次不是最惨,相信也不会是最后的内存的泄露。有人说,内存泄露对于程序员来说,是个好事,也是个坏事。 怎么说? 好事在于,技术又有所长进,经验有所心得…. 毕竟不是所有程序员都写过OOM的服务…. 坏事..._python内存泄露

Sensor (draft)_draft sensor-程序员宅基地

文章浏览阅读747次。1.sensor typeTYPE_ACCELEROMETER=1 TYPE_MAGNETIC_FIELD=2 (what's value mean at x and z axis)TYPE_ORIENTATION=3TYPE_GYROSCOPE=4 TYPE_LIGHT=5(in )TYPE_PRESSURE=6TYPE_TEMPERATURE=7TYPE_PRO_draft sensor

【刘庆源码共享】稀疏线性系统求解算法MGMRES(m) 之 矩阵类定义三(C++)_gmres不构造矩阵-程序员宅基地

文章浏览阅读581次。/* * Copyright (c) 2009 湖南师范大学数计院 一心飞翔项目组 * All Right Reserved * * 文件名:matrix.cpp 定义Point、Node、Matrix类的各个方法 * 摘 要:定义矩阵类,包括矩阵的相关信息和方法 * * 作 者:刘 庆 * 修改日期:2009年7月19日21:15:12 **/

三分钟带你看完HTML5增强的【iframe元素】_iframe allow-top-navigation-程序员宅基地

文章浏览阅读1.7w次,点赞6次,收藏20次。HTML不再推荐页面中使用框架集,因此HTML5删除了<frameset>、<frame>和<noframes>这三个元素。不过HTML5还保留了<iframe>元素,该元素可以在普通的HTML页面中使用,生成一个行内框架,可以直接放在HTML页面的任意位置。除了指定id、class和style之外,还可以指定如下属性:src 指定一个UR..._iframe allow-top-navigation

Java之 Spring Cloud 微服务的链路追踪 Sleuth 和 Zipkin(第三个阶段)【三】【SpringBoot项目实现商品服务器端是调用】-程序员宅基地

文章浏览阅读785次,点赞29次,收藏12次。Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,

烁博科技|浅谈视频安全监控行业发展_2018年8月由于某知名视频监控厂商多款摄像机存在安全漏洞-程序员宅基地

文章浏览阅读358次。“随着天网工程的建设,中国已经建成世界上规模最大的视频监控网,摄像头总 数超过2000万个,成为世界上最安全的国家。视频图像及配套数据已经应用在反恐维稳、治安防控、侦查破案、交通行政管理、服务民生等各行业各领域。烁博科技视频安全核心能力:精准智能数据采集能力:在建设之初即以应用需求为导向,开展点位选择、设备选型等布建工作,实现前端采集设备的精细化部署。随需而动的AI数据挖掘能力:让AI所需要的算力、算法、数据、服务都在应用需求的牵引下实现合理的调度,实现解析能力的最大化。完善的数据治理能力:面_2018年8月由于某知名视频监控厂商多款摄像机存在安全漏洞