数据系统

基石

可靠性

造成错误的原因叫做故障(fault),预料并应对故障的系统特性可称为容错(fault-tolerant)或者弹性(resilient)。

故障(fault)不同于失效(failure),故障定义的是系统偏离正常状态,失效则是整个系统不能对用户提供服务

硬件的故障、软件错误、人为失误都会导致故障或者失效

可伸缩性

可伸缩性(Scalability) 是用来描述系统应对负载增长能力的术语

负载可以用一些称为 负载参数(load parameters) 的数字来描述,如每秒请求数、读写请求比率等等。

为了描述性能,单个请求的响应参数是没有太多参考参考价值的,需要通过一个可测量的数值分布来描述:

中位数、平均数、95%、99%

为了明确大多数用户的指标,中位数是最有价值的,而99%,95%则是用来描述异常的刺突。

为了应对负载,可以进行垂直扩展或者水平扩展。没有一种银弹可以解决所有的应对负载需求,一个良好适配应用的可扩展架构,是围绕着假设(assumption)建立的,这就是所谓负载参数。

可维护性

数据模型

不同的数据模型是为不同的应用场景而设计的。选择适合应用程序的数据模型非常重要。

应用层有各种领域概念,存储层则有JSON、XML或关系模型,数据库层则有以内存或字节来表示JSON、XML等,最底层使用电流或者脉冲来表示字节。

关系模型已经持续称霸了大约25~30年,2010年新起的NoSQL文档模型在最近一段时间内也慢慢与关系模型配合使用,随着时间的推移,关系数据库和文档数据库似乎变得越来越相似。

由于应用的数据最终都被转换成关系模型,应用代码跟数据库之间就需要一个转换中间层,由于现在大部分使用了OO编程语言,二者之间转换还是挺吃力的,即出现阻抗不匹配。

当前的文档模型和上世纪70年代的层次模型很像,一对多很容易支持,但多对多就抓瞎了,文档模型会不会重蹈层次模型的覆辙。

文档模型拥有较关系模型强的架构灵活性,因为我们对数据的解释是发生在我们的代码之中,这意味着一旦数据模型发生改变,修改代码就好了。

同时文档模型拥有数据局部性优势,但这只对同时需要同一文档里的大部分数据时下的场景有效。

查询语言

相较于命令式查询,声明式查询更适合并行。

对于声明式查询语言来说,在编写查询语句时,不需要指定执行细节:查询优化程序会自动选择预测效率最高的策略

介于命令式与声明式之间的有如MapReduce的map及reduce操作,函数式编程其实也是介于二者之间,其通过较高层级的抽象实现,底层运行时可以有效地利用并行性来优化

图状数据模型查询语言

存储与查询

许多数据库在内部使用了日志(log),也就是一个 仅追加(append-only) 的数据文件

为了解决对一个值多次写入造成的空间浪费,日志被分成多个固定大小段,当新启动一个段写入时,可以对段进行合并压缩减少磁盘空间占用

段合并与压缩

日志结构需要考虑的问题:

这种方式对于写入的性能非常好,但读取性能很差,于是引入了索引。

索引

事务处理与分析处理

为了避免进行事务分析对在线事务的影响,通过采用同步数据到数据仓库的方式来解决这个需求,将数据存入仓库的过程称为“抽取-转换-加载(ETL)”

数据仓库与ETL

将一张事实表转换为多张维度表方便进行数据分析

对于部分的查询只需要用到少部分列,但一个事实表可能用来成百上千个列,这个时候引入列存储来提升性能,相对来说一个列的重复数据会较多,可以执行列压缩,一些取值比较连续分布的数据,在使用列存储以后,可以利用 runlength encoding 类似的压缩方法大大提高压缩率

Dremel 通过对列里的每一个值,定义了 Repetition Level 和 Definition Level,区分清楚了每一个值是隶属于哪一层嵌套中的,以及是否需要填充对应的 Optional 的字段或者结构体,使得能够转化成列存储之前的每一行的数据。Dremel 的数据是以行列混合的形式存储下来的。在单个服务器上,数据是列存储的,但是在全局,数据又根据行进行分区,分配到了不同的服务器节点上

Dremel架构

Dremel通过使用中间服务器来并行合并执行结果来降低延迟,对于某些掉队的节点:跑起来特别慢,拖慢了整个系统返回一个查询结果的时间。一旦监测到掉队者的出现,它就会把任务再发给另外一个节点,由于Dremel是给数据分析所舒勇的,也可以设置扫描了 98% 或者 99% 的数据就返回一个近似的结果

物化视图加快查询

物化视图的特殊情况:数据立方体

编码与演化

新旧版本的代码,以及新旧数据格式可能会在系统中同时共处。系统想要继续顺利运行,就需要保持双向兼容性:

数据的使用形式:

相较于XML和JSON,二进制编码Thirft/Protocol Buffers/Avro更加节省空间。

为了保证软件演化过程的向前向后兼容,对每个字段,通过一个唯一标签来标志,只要字段不被删除,新的代码肯定可以处理老数据,但反过来,只有添加的每个字段是可选的或者有默认值,才能向前兼容。

数据类型也对演化过程有影响,如果老数据是32位的,新数据是64位的,升级可以无缝迁移,但向前兼容时就会有问题。

使用读模式跟写模式解决差异:

20213315519

同时如果数据流经过旧代码,那么部分数据很有可能丢失:

20213315109

数据流模式