《Designing Data-Intensive Applic

2019-04-12  本文已影响0人  lionel880

一、存储和检索

1、底层数据结构

1.1 log是保存文件数据最常见的方式,关于log的细节,比较通用的有:

1、append-only log,即不可修改,只能追加。
相比于可以修改value的结构,优点有3个。顺序追加更快,并发和恢复更简单,合并操作可以有效避免数据所片花
2、log的记录,随着时间变长,数据的增多,log拆分为多个segement,或者压缩,删减是所有都需要的操作
3、log的压缩,当key少,值经常变化时,日志的压缩,像redis压缩AOF持久化一样,是非常简便高效的方式

可以考虑的方面还有许多,
log的格式:是否用特定的格式,如二进制等
删除records:日志是否要进行优化
crash recovery:数据库重启,是从log从头到尾执行恢复还是 通过快照进行恢复
paritially written records:未确认的部分恢复
并发控制:单线程写入保证顺序,多线程读取

1.2 Hash Index

由HashMap 实现 Index,像字典一样,是最简单的index实现方式,可行性也很高。
所有的key在内存中,value为真正的数据文件地址或者offset偏移量,通过 append-only log实现

缺点:key只有在内存中才能查询快,在大数据的时候只能把key放在磁盘上,这样,通过磁盘查key的性能损耗太大;range query 是低效的,采用hashmap,无法很快得到 key从1-999的value

1.3 SSTable and LSM-Trees

SSTable(Sorted String Table),是对Hash Index的一个改进,原来的hash Index是杂乱的key-value pair,我们相同的key最近的value是什么,其他的我们什么都不关心,而SSTable,会将key进行排序

相比Hash Index的优点:
1.合并segment更快更简单


image.png

因为key是sorted的状态,所以合并算法会很简单,而且能保证合并后的还是sorted的状态

2.找一个特定的key,不在需要保存所有的key在内存中


image.png

因为key是sorted,所以你可以找某些key在内存中,就可以锁定你找的key是在2者之间,从而丁二到准确的key。 kafka采用的稀疏索引就是用了这个道理

3.range query更为方便了
因为key是有序的,你现在可以将某部分有序的record 压缩后 放在一个block写在磁盘中,这样range query可以直接一个block拿出来,现在计算机磁盘顺序IO是很快的,kafka仍然使用了这个原理

SSTable的工作流程
那么缺点有什么呢?

当数据库crash,memTable的数据会丢失。所以我们得维护另一个append-only log,就像最开始那样,不用管是否有序,只是在写入时,立刻持久化,目的就是为了恢复数据,每次memTable持久化后,这个log就可以丢弃

可以优化的细节

1.4 B-Trees

最为常见的索引结构 B-Trees。
之前的log结构,常常将log分为一个个segement,通常为MB级别或更大,但B-Tree将数据库分为一个个固定大小的block,通常为4kb,这是每次读写的一个page,这是由底层硬件设备所决定的,因为硬盘也是这么设计的。

B-Tree


B-Tree Index.png
如何查找或更新一个key

假设数据为图 B-tree index,我们想查找key=251的值,先在根节点找到200-300之前的ref,在找child page中的250-270间的ref,最后再 叶节点找到key=251的真正数据。同理,你想更新它也是一样的操作

如何插入一个key

如果你插入key时,那个page仍然有充足的空间,则直接插入即可,但若是没有空间,则会将split page 形成2个page,parent page 原来是leaf page,就更新为 包含child page的ref,通过这个split操作,使得B-Tree grow up


image.png
效率

通过B-Tree算法,保证了Tree是平衡的,一个 n个key的数据量,深度为O(logN),一般为3-4层,估算一下,一个4层的 ,每个page4kb的,branching factor 为500的,能存储256TB
4500500500500/1000/1000/1000=250TB

making B-Tree reliable

比较B-Tree和LSM,由于一个是append-only log实现,一个的数据是可以overwrite的,导致B-Tree在以下方面会相对而言更为复杂

B-Tree 优化

1.WAL的替代方案,修改的并不会在原来的地方执行,而是在另一个地方创建,同时一个new version的parent page也被创建,这也同样可以用于并发控制
2.使用简短的key或者缩写的key,节省空间,从而使得branch factor可以更大
3.在range query时,LSM效率很高,但是B-Tree很难,因为只有leaf-pages 依次在一起才能达到顺序读取的效果,维护所有的leaf-pages是困难的
4.因此我们可以通过多一个指针来完成这个目的,每一个leaf-page都包含一个ref,这个ref指向它的兄妹leaf-page,这个样子查找就可以不跳回parent page,直接在leaf-page上依次找下去

1.5 B-Trees 和LSM 的对比

SLM的优点

SLM的缺点

1.6 其他索引结构

之前我们讲的索引都是唯一索引,即key是唯一的,但很多时候,我们会需要第二索引,第二索引和唯一索引不同的是,第二索引的key可能会有重复。大致上有2种思路解决这个问题

Storing values within the index

索引我们常常是将key作为索引,那我们到底是怎么存放value的,value一般可以有2种形式,一是真正的value,二是真正value的引用,最终的value在heap上。
为了节省空间,我们只会存储一份真实的数据,因此第二索引的value只能为 真实value的引用

2、事务和分析

2.1 数据仓库

传统数据库,我们一般是交互式的插入或更新数据,但后来随着数据分析的兴起,2者出现了一些区别,从而专门用于数据处理的数据仓库兴起了


image.png

OLTP通常和OLAP要进行区分,如果用同一个数据存储,analyse通常占用了太多资源,会影响正常的业务。正常,我们会将一个read_only的copy数据放入数据仓库用于分析,有时候放入前要清晰数据,以方便后续的分析业务
这一个过程,我们一般称之为ETL(extract-transform-load)
常见的开源数据仓库:Apache Hive,Spark Sql等

2.2 星型模型和雪花模型

在数据仓库中,常使用2种数据模型,星型模型和雪花模型

2.2 面向列的存储

实际使用中,一个核心表常常有上百列的规模,在OLTP中,一般都是面向row存储的,即每个row的数据是放在一起的。但实际分析中,我们常常只需要其中的几行,怎么办?
传统的面向row的数据存储,虽然可以select某几行,但实现上其实是先加载所有的数据到内存,再选出其中的几个字段。

解决方法就是 面向列的存储:每一列的数据进行存储,举例


列存储

可以看到,每一个列都成了一个单独的文件,顺序都是按原来的row模式的顺序。此时假设你需要找23行的 product_sk,quantity字段,你就可以分别到2个文件中,去找第23rd的列数据,再组合在一起

列的压缩

列存储,如age等,相似的概率很大,因此压缩效率会很高。常用的有bitmap等方式
此外还有cpu的SIMD技术

列的排序

当使用列存储时,单独对某个列文件进行排序是没意思,因为你会对应不上row。
这里的列排序,一般只是用于搜索时,如只要上个月数据,就去日期的列,先找到所有的row,排序,然后去其他的列中查找,最后整合

列存储的写入

列存储的目的是为了方便大数据的读取,包括存储的压缩。但它的写入也变得复杂,你可能需要去同时去更新很多文件。那么有什么办法去优化呢?
答案是:批量处理
我们会使用一个在内存中的LSM结构去存储插入的数据,在内存时,你不需要去关心最终到硬盘是row oriented还是column oriented,当内存累计到一个值,再批量写入磁盘
通过这个写入方式时,读取数据也需要结合disk的数据和内存的数据,再返回。但数据库帮你屏蔽了这些复杂的实现细节,对于用户来说,就是写入和读取是实时的

聚合

常见的聚合操作: COUNT, SUM, AVG, MIN, MAX

有一种解决方式为 materialized view(物化视图),和一般的view相比,一般的view其实只是一种抽象,最终还是会执行为sql,但物化视图是一个真正的,需要存储空间的表,他主要用于分析,因为需要同时维护一些聚合信息,所以,写入操作是很昂贵的,一般的OLTP系统不会用
本质上,通过物化视图提升查询速度,只是因为进行了预处理运算

上一篇 下一篇

猜你喜欢

热点阅读