大家好,我是明哥!
本片文章,跟大家一起探讨下,列式存储与数据质量的相关话题。
1. 什么是列式存储
- 所谓行式存储,指存储结构化数据时,在底层的存储介质上,数据是以行的方式来组织的,即存储完一条记录的所有字段,再存储下一条数据的所以字段,以此类推;
- 所谓列式存储,指存储结构化数据时,在底层的存储介质上,数据时以列的方式来组织的,即存储完若干条记录的首个字段后,再存储这些记录的第二个字段,然后是这些记录的第三个字段,以此类推,当这些记录的所有字段存储完毕后,再按照这种方式,组织存储下一批若干条记录的所有字段;
比如需要存储以下记录:
姓名 | 年龄 | 工资 |
则行存储格式,底层是这样组织的:(示意图)
而列存储格式,底层是这样组织的:(示意图)
说明:
- 以上只是示意图,实际存储时,往往还会存储一些元数据,比如校验信息,统计信息等等;(列式存储往往会存储更多的元数据信息,以便于检索/查询/统计等)
- 行存储与列存储,存储的都是结构化数据;(非结构化数据,就无所谓行和列了)
- 行存储和列存储,描述的是底层存储介质上,数据的组织形式,而存储介质可以是磁盘,也可以是内存;存储介质的上次建筑,可以是文件系统,也可以是对象存储;
- 很多数据库,在底层即支持行存储,也支持列存储,这两者有时是同时混合使用的;(比如 oracle12c,就引入了对 In-Memory Column Store 的支持;再比如 TiDB, 底层同时支持行存储引擎 TiKV 和 列存储引擎 TiFlash);
2. 深入了解列式存储 - 以 Parquet 为例
说到当下最流行的开源列式存储引擎,就非 apache parquet 莫属了。
- 绝大多数计算引擎,比如 apache spark/presto/impala/flink, 都将 parquet 作为首选的列示存储引擎;
- orc 是 apache hive 社区推出的,另一款开源列示存储引擎;
- apache hive 在早期只支持 orc 列示存储的基础上,有扩展了对 parquet 的支持;
有鉴于此,我们有必要以 parquet 为例,来深入了解下列示存储。
以下是 parquet 底层,数据的组织格式:
相关述语解释如下:
- Block (hdfs block): 指 hdfs 文件系统的 block,parquet 是在 hdfs 文件之上的数据组织格式;(当然现在很多对象存储系统,比如 S3,也支持 parquet 存储格式);
- File: 指 hdfs 文件,使用 parquet 格式时,每个 hdfs 文件底层必须包括 parquet 元数据 - 事实上该文件底层可以不包含数据,但必须包含元数据;(当然现在很多对象存储系统,比如 S3,也支持 parquet 存储格式);
- Row group: 文件底层的所有数据,在逻辑上被水平切割,每个 row group 存储的都是一份这些被水平切割后的数据。(row group 是对数据的水平切,分比如上图中,就显示了 row group 0 和 row group1 两个 row group);
- Column chunk: 每个 row group 底层都包含一系列 column chunk,每个colum 都有一个对应的 column chunk;(数据有多少列,水平切割的每个 row group底层,就有多少个 column chunk);
- Page: Column chunks 被进一步切分为若干个 page 页,page 是压缩和编码的最小单位;(比如上如,row group0 的 Column a,就显示了两个 page: page a 和 page b);
概括如下:
- 每个文件由一个或多个 row group 构成,每个 row group 包含了多个 column chunk (column chunk 和 column 是 一一对应的),Column chunks 包含了一个或多个 page;
- MR 等计算作业,其并行操作的单位是 File/Row Group;
- IO 的单位是 Column chunk;
- 编码和压缩的单位是 Page;
3. 行式存储与列式存储的优劣势都有哪些
通过上述对底层数据组织格式的对比,不难发现,行存储有以下特点:
- 行存储将每条数据的所有列连续存储在一起,一条记录接着一条记录;
- 行存储中数据写入的成本较低,适合数据有频繁更新的场景;
- 通过使用索引,能大幅提高行存储的数据查询速度;
- 行存储是传统的数据组织形式,更适合传统的 OLTP 系统;(OLTP 数据库表的设计强调范式,底层一般有多张有关联关系的窄表)
而列存储有以下特点:
- 列存储将多行记录的列连续存储在一起,一列接着一列;
- 由于连续存储在一起的列的数据类型都一样,所以数据压缩率更高,更省存储空间;
- 列存储中数据查询的成本较低,特别适合分析时只查询部分列的场景,因为不需要扫描/读取不需要查询的列;
- 列存储由于数据更新成本较高,一般适合读多写少的场景;(但是不代表不能更新!)
- 列存储是新型数据组织形式,更适合 OLAP 分析型系统;(OLAP数据库表的设计强调反范式,底层一般是星型模式的若干张事实表和维度表,倾向使用大宽表)
4. 列式存储与数据质量
各厂各司在其数据仓库或数据湖泊的数据规范部分,大都强调要尽可能地使用列式存储格式,而尽可能不要使用行式存储格式。
究其原因,一方面是列式存储相比行式存储,其压缩率更高读写效率更快;另一方面是因为,相比行式存储,列式存储的数据质量更高!
之所以说列示存储能提高数据质量,更准确地说,是有些行式存储会降低数据质量,比如 csv/tsv 等 TextFile。
比如,以我们熟悉的 HIVE 为例,一个常见的 DDL 语句如下:
CREATE TABLE student_text_test( id double, name string, sex double, age double, class double, address string) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' STORED AS TEXTFILE;
- 大家可以看到,TextFile 的 DDL 语句中,需要通过 xx terminated by xx 来指定列分隔符和行分隔符(列分隔符默认是'\001',行分隔符默认是'\n');
- 而上游rdbms表中某些字段,可能会包含特殊字符比如换行符,此时就可能会跟上述textfile 表的 DDL 语句中指定的列分隔符或分隔符冲突;
- 后续通过数据同步工具比如 sqoop/datax/spark sql 等同步上游数据到 hive中 textfile 格式的表时,同步作业可以执行成功;
- 但是后续再查询该同步的 textfile 格式的表时,因为大数据schema on read 的特性,在读数据时按就会按照 DDL 中指定的行和列分隔符来切割数据,此时就可能会因为行分隔符造成数据切割混乱,从而查出大量 NULL 字段的错乱数据;
- 不幸的是,上游 RDBMS 中的数据往往不受大数据部门管控,会出现各种各样的奇怪数据;
- 更不幸的是,目前hive的textfile只支持\n作为行分隔符(验证了cdh6.3,也就是hive2.1)
所以,正式出于数据质量的原因,各厂各司在其数据仓库或数据湖泊的数据规范部分,大都强调:
- 要尽可能地使用列式存储格式比如orc/parquet,而尽可能不要使用行式存储格式textfile;
- 数仓中各层,包括 ods/dwd/dws/ads ,以及业务临时表与技术临时表,都尽量不要使用textfile;
- 除非是选用的数据同步工具不支持orc/parquet(比如sqoop),且此时也仅仅只能在 ods层的落地表中使用textfile, 后面 dws等各层需要切换使用列示存储 orc/parquet;
- 必要时,对数据同步工具重新进行技术选型,比如抛弃不支持列示存储的 sqoop, 而选用支持列示存储的 spark sql/datax 等。