DDIA读书笔记第三章——存储与检索

两大类存储引擎的结构:日志结构(log-structured)的存储引擎,面向页(page-oriented)的存储引擎

驱动数据库的数据结构

以实现一个KV数据库为例,最简单的方案,支持Get、Put,不支持Delete

#!/bin/bash
db_set () {
    echo "$1,$2" >> database
}

db_get () {
    grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}

写追加,效率高,读需要查询所有记录,效率低,同时磁盘空间无限增长。
为了提高查找效率,需要加索引,索引是从主数据衍生的附加(additional)结构。许多数据库允许添加与删除索引,这不会影响数据的内容,它只影响查询的性能,这是存储系统中一个重要的权衡:精心选择的索引加快了读查询的速度,但是每个索引都会拖慢写入速度。

哈希索引

通过内存中的哈希索引来索引磁盘上的数据,key是KV数据中的key,value是值在对应磁盘上的偏移,写仍然是追加写,读需要一次读内存和一次磁盘seek+磁盘读操作。限制是所有key都要存在内存中
DDIA读书笔记第三章——存储与检索_第1张图片
如何解决磁盘空间增长问题:日志切分,每写满一个Data Segment,就关闭当前文件,然后新建一个文件继续写,最后日志压缩,只保留最新的更新。日志压缩可以后台进行,Segment文件被关闭之后就只能读,不能写,被合并的段文件可以删除。
DDIA读书笔记第三章——存储与检索_第2张图片
在工业实现上,还有其他问题要解决:
文件格式:二进制形式文件,先对以字节为单位对字符串的长度进行编码,然后使用原始字符串(不需要转义)。
1、删除记录:用一条特殊的记录表示删除,在日志合并时,再删除健以前所有的值
2、宕机恢复:全盘扫描重建索引。优化点:将索引和数据一起落盘,加快载入
3、记录写坏,写少:文件系统的写入不是原子的,为一定大小的数据做校验和,比如4K,写入时更新校验和,读取时比对校验和
4、并发控制:一个写入线程用来追加数据,可以多个线程同时读取

追加日志接口的有点:
1、顺序写代替随机写,写性能高
2、已经关闭的文件不能更改,这样并发控制和崩溃恢复会很简单
3、合并端能够避免数据文件随着时间的推移而分散的问题。内存碎片更少

这种基于内存的哈希索引的缺点:
1、索引必须存在内存中。如果涉及磁盘上的哈希索引,会有很多随机写
2、范围查询性能不高

SSTables和LSM树

上述日志记录是按照时间顺序写在文件中的,如果把key排序呢?
排序字符串表(Sorted String Table),简称SSTable,好处:
1、合并段非常高效,归并排序
2、不需要把索引都保存在内存中,只需要保存部分索引,优化点:由于读取请求无论如何都需要扫描所请求范围内的多个键值对,因此可以将这些记录分组到块中,并在将其写入磁盘之前对其进行压缩(如图的阴影区域所示) 。稀疏内存中索引的每个条目都指向压缩块的开始处。除了节省磁盘空间之外,压缩还可以减少IO带宽的使用。
DDIA读书笔记第三章——存储与检索_第3张图片

构建和维护SSTables

在磁盘上有序数据结构是可能的,比如B数,在内训中维护很简单,比如树形结构,红黑树or AVL数,现在写入流程如下:
1、更新内存中的平衡树数据结构,个内存树有时被称为内存表(memtable)。
2、当内存表大于一定阈值,dump到磁盘上,形成SStable,接着新建内存数据结构继续写。
3、在后台合并或者压缩,丢弃覆盖或者删除的值
4、读的话,先尝试在内存表中查找,然后在最新的SStable中,然后在下一个交旧的段中查找
问题点:基于内存的数据在宕机时不会保存,会丢失数据,为了避免这个问题,我们可以在磁盘上保存一个单独的日志,每个写入都会立即被附加到磁盘上,该日志不是按排序顺序,但这并不重要,因为它的唯一目的是在崩溃后恢复内存表。每当内存表写出到SSTable时,相应的日志都可以被丢弃。

性能优化

1、加快查找:bloom filter,为每个SSTable维护一个
2、层级化组织SSTable,得压缩能够更加递增地进行,并且使用更少的磁盘空间。

B族树

DDIA读书笔记第三章——存储与检索_第4张图片
特点:
1、以页为单位组织数据,磁盘上叫页page,内存中叫block
2、页之间以key的大小逻辑组织,从而形成磁盘上的一棵树

查找:每层二分查找,知道命令或者达到叶子节点
插入or更新:先查找,找到之后,插入or更新,可能会发生分裂
删除:先查找,找到之后,删除,可能发生合并

让B树更可靠

更新or合并页面到一半时可能会发生奔溃
1、增加WAL(redo log),用来崩溃恢复
2、多线程更新B树时,采用latch 锁存器(轻量级锁)来保证线程安全

B树的优化

1、copy on write,不用WAL
2、中间节点的key做压缩,减少树高
3、B树的叶子节点可以存放连续的物理磁盘,方便范围查找
4、叶子节点加入兄弟节点指针,加快范围查找

B-tree和LSM-tree对比

1、B-tree读取快、LSM-tree写入快(顺序写,B-tree大量随机写)
2、写放大:B-tree WAL和数据(覆盖写)、合并或者分裂是涉及多个页面;LSM-tree,WAL和内存、后台compaction,LSM-tree的写放大较低
3、读放大:B-tree每层的二分查找,叶子page的的读取;LSM-tree,读内存,多级SSTable的读取;B-tree的读放大较低
4、存储放大:B-tree有些page没有用满;LSM-tree同一个key存多遍
5、压缩率:B-tree中有多内存碎片,LSM-tree更加紧凑,压缩潜力大(共享前缀)
6、后台流量:B-tree更稳定,LSM-tree后台compaction,影响磁盘带宽、写入吞吐(写入量大时,不能及时compact level0的SSTable),读写的tp99大
7、并发控制:B-tree同一个key只存一份,容易范围加锁,适合做关系型数据库;LSM-tree同一个key存多份,一般使用MVCC进行控制

事务处理还是事务分析?

在线事务处理(OLTP, OnLine Transaction Processing)、在线分析处理(OLAP, OnLine Analytice Processing)比较:
属性 事务处理 OLTP 分析系统 OLAP
主要读取模式 查询少量记录,按键读取 在大批量记录上聚合
主要写入模式 随机访问,写入要求低延时 批量导入(ETL),事件流
主要用户 终端用户,通过Web应用 内部数据分析师,决策支持
处理的数据 数据的最新状态(当前时间点) 随时间推移的历史事件
数据集尺寸 GB ~ TB TB ~ PB

列存储

在大多数OLTP数据库中,存储都是以面向行的方式进行布局的。
在一些查询中,行存储并不高效,需要把所有的行都从磁盘加载到内存中,再过滤掉不符合条件的行,为了加快此种类型的查询,有了列存储,即把列数据组织到一起。

列压缩

列式存储非常适合压缩

你可能感兴趣的:(DDIA读书笔记,linux,数据库,运维,分布式)