zz from: http://www.cloudera.com/content/cloudera/zh-CN/documentation/core/v5-3-x/topics/impala_parquet.html

Impala 可帮助您创建、管理和查询 Parquet 表。Parquet 是一种面向列的二进制文件格式,其设计目标是为 Impala 最擅长的大规模查询类型提供高效率支持。Parquet 对于在表中扫描特定列的查询特别有效,例如查询一个包含许多列的表,或执行需要处理列中绝大部分或全部的值的如SUM()AVG() 等聚合操作。每个数据文件包含一系列行(行组)的值。在数据文件里,每列的值被重新组织以便它们相邻,从而对这些列的值进行良好的压缩。针对 Parquet 表的查询可以快速并以最小的 I/O 从任意列快速获取并分析这些数据。

Table 1. Impala 支持 Parquet 文件
文件类型 格式化 压缩编码解码器 Impala 能否支持 CREATE? Impala 能否支持 INSERT?
Parquet 结构化 Snappy、gzip;当前默认为 Snappy 是。 是:CREATE TABLEINSERTLOAD DATA 和查询。

Continue reading:

在 Impala 中创建 Parquet 表

要创建一个名为 PARQUET_TABLE 的 Parquet 格式的表,请使用类似下方的命令,并替换为你自己的表名、列名和数据类型:

[impala-host:21000] > create table parquet_table_name (x INT, y STRING) STORED AS PARQUET;

或者,要克隆一个现有表的列名和数据类型:

[impala-host:21000] > create table parquet_table_name LIKE other_table_name STORED AS PARQUET;

在 Impala 1.4.0 及更高版本中,您可以从原始 Parquet 数据文件中导出列定义,即使没有一个现存的 Impala 表。例如,您可以基于该目录中某文件的列定义,创建一个指向 HDFS 目录的外部表:

CREATE EXTERNAL TABLE ingest_existing_files LIKE PARQUET '/user/etl/destination/datafile1.dat'
  STORED AS PARQUET
  LOCATION '/user/etl/destination';

或者,您可以参考现有的数据文件,创建带有合适列名的新的空表。 然后使用 INSERT 创建新的数据文件 或者使用 LOAD DATA 将现有数据文件 导入至新表。

CREATE TABLE columns_from_data_file LIKE PARQUET '/user/etl/destination/datafile1.dat'
  STORED AS PARQUET;

新创建的表的默认属性与其它任何 CREATE TABLE 语句相同。 例如,默认的文件格式是文本文件;如果您希望新建的表使用 Parquet 文件格式, 请再加上STORED AS PARQUET

本例中,新建的表根据年、月、日进行分区。这些分区关键列不是数据文件的一部分,因此您需要在 CREATE TABLE 语句中指定:

CREATE TABLE columns_from_data_file LIKE PARQUET '/user/etl/destination/datafile1.dat'
  PARTITION (year INT, month TINYINT, day TINYINT)
  STORED AS PARQUET;

参见 创建表语句 了解 CREATE TABLE LIKE PARQUET 语法的详细信息。

当创建了表后,请使用类似下面的命令向表中插入数据,请再次使用您自己的表名:

[impala-host:21000] > insert overwrite table parquet_table_name select * from other_table_name;

如果 Parquet 表具有与其它表不同数量的列或不同的列名,请在对其它表的 SELECT 语句中指定列名,以代替 *

加载数据到 Parquet 表

根据原始数据是存在于 Impala 表中,还是存在于 Impala 表之外的数据文件中,选择不同技术将数据加载到 Parquet 表里。

如果您的数据已经在 Impala 或 Hive 表里,可能是在不同的文件格式或分区模式下,您可以使用 Impala 语法 INSERT…SELECT 将这些数据导入 Parquet 表。可以在同一个 INSERT 语句中,对数据执行转换、过滤、重新分区,以及其它类似操作。参见 Parquet 数据文件的 Snappy 和 GZip 压缩 查看一些示例,了解如何在 Parquet 表中插入数据。

插入分区表时,尤其是使用 Parquet 文件格式时,可以在 INSERT 语句中包含提示来微调操作的整体性能及其资源使用率:

  • 这些提示在 Impala 1.2.2 及更高版本中可用。
  • 只有当由于容量限制而导致 INSERT 插入分区 Parquet 表的操作失败时,或者 INSERT 虽然成功却低于最佳性能时,才可以使用这些提示。
  • 要使用这些提示,请将提示关键字 [SHUFFLE][NOSHUFFLE](包括方括号)放置在 PARTITION 子句之后,SELECT 关键字之前。
  • [SHUFFLE] 选择的执行计划应能将同时写入到 HDFS 的文件数量以及保留分区数据的内存缓冲区的数量降到最少。它允许某些原本有可能失败的INSERT 操作成功执行,从而降低了 INSERT 操作的整体资源使用率。它涉及节点之间的一些数据传输,以便在同一个节点上构建某个特定分区的数据文件。
  • [NOSHUFFLE] 选择的执行计划应整体速度更快,但也能够生成大量的小数据文件或超出容量限制而导致 INSERT 操作失败。当由于所有节点尝试构建所有分区的数据而导致 INSERT 语句失败或运行无效时,使用 [SHUFFLE]
  • 如果 INSERT … SELECT 查询中提及的来源表的任何分区键列不显示列统计数据,则 Impala 会自动使用 [SHUFFLE] 方法。在这种情况下,仅使用[NOSHUFFLE] 提示是不会带来什么影响的。
  • 如果 INSERT … SELECT 查询中提及的来源表的所有分区键列均显示列统计数据,Impala 会根据这些列中预计的独特值数量以及 INSERT 操作中涉及的节点数量,选择是使用 [SHUFFLE] 还是 [NOSHUFFLE]。在这种情况下,您可能需要 [SHUFFLE][NOSHUFFLE] 提示来替代 Impala 选择的执行计划。

Parquet 表的任何 INSERT 语句都需要在 HDFS 文件系统中有足够的空间才能写入一个块。由于默认情况下 Parquet 数据文件使用的块大小为 1 GB,因此如果 HDFS 的运行空间不足,INSERT 就有可能失败(即使是极少量的数据)。

避免对 Parquet 表使用 INSERT…VALUES 语法,因为 INSERT…VALUES 会为每一个 INSERT…VALUES 语句产生一个极小的单独数据文件,而 Parquet 的强项在于它可以 块的方式处理数据(压缩、并行等操作)。

假如您有一个或多个 Impala 之外生成的 Parquet 数据文件,可通过以下方法之一,快速地让这些数据可以在 Impala 中查询:

  • 使用 LOAD DATA 语句,将一个单独文件或某个目录下所有数据文件移动到 Impala 表对应的数据目录中。这不会验证或转换数据。原始数据文件必须位于 HDFS 中,而不能是本地文件系统中。
  • 使用包含 LOCATION 子句的 CREATE TABLE 语句创建一个表,将数据继续存放在 Impala 数据目录之外。原始数据文件必须位于 HDFS 中,而不能是本地文件系统中。为加强安全性,假如这些数据长时间存在并被其他应用重用,您可以使用 CREATE EXTERNAL TABLE 语法,使得这些数据文件不会被 Impala 语句 DROP TABLE 删除。
  • 假如 Parquet 表已经存在,您可以直接复制 Parquet 数据文件到表的目录中,然后使用 REFRESH 语句使得 Impala 得以识别新添加的数据。请记住使用 hdfs distcp -pb 命令而不是 -put-cp 操作,以保留 Parquet 数据文件的块大小。参见 复制 Parquet 数据文件示例 了解以上操作的示例。

如果数据存在于 Impala 之外,并且是其它格式,请结合使用之前提到的两种技术。首先,使用 LOAD DATACREATE EXTERNAL TABLE … LOCATION语句把数据导入使用对应文件格式的 Impala 表中。然后,使用 INSERT…SELECT 语句将数据复制到 Parquet 表中,并转换为 Parquet 格式。

加载数据到 Parquet 表是内存密集型操作,因为输入数据会在内存中被缓存,直到其大小达到 一个数据块,然后这些块的数据会进行组织和压缩,最终写出。把数据插入到分区 Parquet 表时,内存消耗会更大,因为对分区关键列值的每种组合都要写入一个单独的数据文件中,可能需要内存同时操作几个 区块。

当向分区 Parquet 表插入数据时,Impala 会在节点之间重新分布数据以减少内存消耗。但是在插入操作时,您可能仍然需要临时增加 Impala 专用的内存量,或者把加载操作拆分到几个 INSERT 语句中,或者两种方式都采用。

  Note: 之前所有的技术都假定你所加载的数据与你的目标表的结构相匹配,包括列的顺序,列的名称,以及分区布局 (layout)。要转换或重组数据,请先将数据加载到与其底层数据相匹配的 Parquet 表中,然后使用如 CREATE TABLE AS SELECTINSERT … SELECT 之一的表复制技术,对列进行重新排序或重命名,将数据拆分到多个分区等等。举例来说,要加载单个包含所有数据的 Parquet 文件到分区表中,您应当使用包含动态分区的 INSERT … SELECT 语句,让 Impala 创建具有合适分区数值的单独的数据文件;示例参见 INSERT 语句

Impala Parquet 表的查询性能

Parquet 表的查询性能,取决于处理SELECT 列表和 WHERE 子句 时所需的列的个数,数据被分为 块大小等同于文件大小的大数据文件的方式,读取压缩格式下每列数据时 I/O 的降低,可以跳过哪些数据文件(分区表),以及解压每列数据时的 CPU 负载。

例如,以下对 Parquet 表的查询是高效的:
select avg(income) from census_data where state = 'CA';

这个查询只处理大量列中的两个列。假如表是根据 STATE 分区的,它甚至更有效率,因为查询仅仅需要对每个数据文件读取和解码 1 列,并且它可以只读取 state ‘CA’分区目录下的数据文件,跳过所有其它 state 的、物理上的位于其它目录的所有数据文件。
以下对 Parquet 表的查询相对低效:
select * from census_data;

Impala 不得不读取每个 数据文件的全部内容,并解压每一个行组中每一列的内容,浪费了面向列格式的 I/O 优化。与其它文件格式的表相比,该查询对 Parquet 表可能还是会更快,但它没有利用 Parquet 数据文件格式的独特优势。

Impala 可以优化对 Parquet 表的查询,特别是在联接查询方面,涉及对全部的表进行统计时效果更加明细。当表中加载或附加了大量数据后,对每个表发出COMPUTE STATS 语句。详细信息,请参见 COMPUTE STATS 语句

  Note: 目前,某个已知问题 (IMPALA-488) 会导致在 Parquet 表上执行 COMPUTE STATS 操作时使用的内存过多。解决办法是,发布 COMPUTE STATS语句前,在 impala-shell 中发布命令 SET NUM_SCANNER_THREADS=2。接着,发布 UNSET NUM_SCANNER_THREADS,然后继续查询。

Parquet 表的分区

正如 Impala 表分区所述,对 Impala 而言,分区是一项重要而通用的性能技术。本章节介绍一些关于分区 Parquet 表的性能考虑。

Parquet 文件格式非常适合包含许多列,并且绝大部分查询只涉及少数几列的表。正如 Parquet 数据文件如何组织所述,Parquet 数据文件的物理分布使得对于许多查询 Impala 只需读取数据的一小部分。当您结合使用 Parquet 表和分区时,这种方法的性能优势会进一步凸显。基于 WHERE 子句引用的分区关键列,Impala 可以完全跳过特定分区的数据文件。例如,分区表上的查询通常基于YEARMONTH,和/或 DAY列进行期间趋势分析,或是地理区域分析。请记住,Parquet 数据文件使用了 块尺寸,所以在确定如何精细地分区数据时,请尝试找到一个粒度,使得每个分区包含 256 MB 或更多数据,而不是创建大量属于多个分区的小文件。

插入到分区 Parquet 表的操作可能是一个资源密集型操作,因为对于每个不同分区关键列的组合,每个 Impala 节点都可能会将一个单独的数据文件写入 HDFS。大量同时打开的文件数可能会达到 HDFS transceivers 限制。考虑采用以下技术,避免达到这一限制:

  • 使用单独的 INSERT 语句加载不同的数据子集,每个语句的 PARTITION 子句包含特定值,例如 PARTITION (year=2010)
  • 增加 HDFS 的 transceivers 值,有时候写作 xcievers (sic)。即配置文件 hdfs-site.xml 中的 dfs.datanode.max.transfer.threads属性。例如,如果您加载 12 年的数据,根据年、月、日进行分区,那么即使该值设为 4096 也不够。这篇 博文 使用 HBase 例子作为说明,探讨了增加或减小这一数值的考虑。
  • 使用 COMPUTE STATS 语句在数据被复制的源表上采集 列统计信息,这样 Impala 查询可以评估分区关键列不同数值的个数,从而均衡工作负载。
  Note: 目前,某个已知问题 (IMPALA-488) 会导致在 Parquet 表上执行 COMPUTE STATS 操作时使用的内存过多。解决办法是,发布 COMPUTE STATS语句前,在 impala-shell 中发布命令 SET NUM_SCANNER_THREADS=2。接着,发布 UNSET NUM_SCANNER_THREADS,然后继续查询。

Parquet 数据文件的 Snappy 和 GZip 压缩

当 Impala 使用 INSERT 语句写入 Parquet 数据文件时,底层的压缩受 COMPRESSION_CODEC 查询选项控制。(Impala 2.0 版本以前,该查询选项的名称为PARQUET_COMPRESSION_CODEC。)这一查询选项允许的值包括 snappy(默认值)、gzipnone。选项值不区分大小写。如果该选项设为了其它无法识别的值,那么所有类型的查询都会因无效的选项设置而失败,不仅仅是涉及 Parquet 表的查询。

使用 Snappy 压缩的 Parquet 表示例

默认情况下,Parquet 表的底层数据文件采用 Snappy 压缩。快速压缩和解压,对于许多数据集来说是一个好选择。为确保使用了 Snappy 压缩,例如在试验了其它压缩编解码之后,请在插入数据之前设置 COMPRESSION_CODEC 查询选项为 snappy

[localhost:21000] > create database parquet_compression;
[localhost:21000] > use parquet_compression;
[localhost:21000] > create table parquet_snappy like raw_text_data;
[localhost:21000] > set COMPRESSION_CODEC=snappy;
[localhost:21000] > insert into parquet_snappy select * from raw_text_data;
Inserted 1000000000 rows in 181.98s

使用 GZip 压缩的 Parquet 表示例

如果您需要更密集的压缩(代价是,查询时需要更多的 CPU 周期以进行解压),请在插入数据之前设置 COMPRESSION_CODEC 查询选项为 gzip

[localhost:21000] > create table parquet_gzip like raw_text_data;
[localhost:21000] > set COMPRESSION_CODEC=gzip;
[localhost:21000] > insert into parquet_gzip select * from raw_text_data;
Inserted 1000000000 rows in 1418.24s

未压缩 Parquet 表示例

假如您的数据压缩效果非常有限,或者您想彻底避免压缩和解压缩带来的 CPU 负载,请在插入数据前设置 COMPRESSION_CODEC 查询选项为none

[localhost:21000] > create table parquet_none like raw_text_data;
[localhost:21000] > insert into parquet_none select * from raw_text_data;
Inserted 1000000000 rows in 146.90s

已压缩 Parquet 表的大小和速度示例

下面的例子演示了 10 亿条复合数据在数据大小和查询速度方面的差异,他们分别使用了不同的编解码器进行压缩。与往常一样,使用你自己真实的数据集进行类似的测试。实际的压缩比、对应的插入和查询速度,根据实际数据特征的不同而有所不同。

本例中,压缩方式从 Snappy 改为 GZip,数据大小减少了 40%,而从 Snappy 改为不压缩,大小增加了 40%:

$ hdfs dfs -du -h /user/hive/warehouse/parquet_compression.db
23.1 G  /user/hive/warehouse/parquet_compression.db/parquet_snappy
13.5 G  /user/hive/warehouse/parquet_compression.db/parquet_gzip
32.8 G  /user/hive/warehouse/parquet_compression.db/parquet_none

因为 Parquet 数据文件通常大小是 ,每一个目录都包含不同数量的数据文件并安排不同的行组。

同时,压缩比更小,解压速度就更快。在上面包含 10 亿行记录的表中,对于评估特定列全部数值的查询,不使用压缩的比使用 Snappy 压缩的快,使用 Snappy 压缩的比使用 Gzip 压缩的快。查询性能依赖于多个不同因素,因此请如往常一样,使用你自己的数据进行自己的基准测试,以获得数据大小、CPU 效率、以及插入和查询操作的速度等方面的理想均衡。

[localhost:21000] > desc parquet_snappy;
Query finished, fetching results ...
+-----------+---------+---------+
| name      | type    | comment |
+-----------+---------+---------+
| id        | int     |         |
| val       | int     |         |
| zfill     | string  |         |
| name      | string  |         |
| assertion | boolean |         |
+-----------+---------+---------+
Returned 5 row(s) in 0.14s
[localhost:21000] > select avg(val) from parquet_snappy;
Query finished, fetching results ...
+-----------------+
| _c0             |
+-----------------+
| 250000.93577915 |
+-----------------+
Returned 1 row(s) in 4.29s
[localhost:21000] > select avg(val) from parquet_gzip;
Query finished, fetching results ...
+-----------------+
| _c0             |
+-----------------+
| 250000.93577915 |
+-----------------+
Returned 1 row(s) in 6.97s
[localhost:21000] > select avg(val) from parquet_none;
Query finished, fetching results ...
+-----------------+
| _c0             |
+-----------------+
| 250000.93577915 |
+-----------------+
Returned 1 row(s) in 3.67s

复制 Parquet 数据文件示例

下面是最后一个示例,演示了使用不同压缩编解码器的数据文件在读操作上是如何相互兼容的。压缩格式的相关元数据会写入到每个数据文件中,无论当时COMPRESSION_CODEC 设置为什么值,在查询过程中都可以正常解码。本例中,我们从之前示例中使用的 PARQUET_SNAPPYPARQUET_GZIP,和PARQUET_NONE 表中复制数据文件,这几个表中每个表都包含 10 亿行记录,全都复制到新表 PARQUET_EVERYTHING的数据目录中。一对简单的查询表明,新表包含了 30 亿行记录,并且数据文件使用了不同的压缩编解码器。

首先,我们在 Impala 中创建表,以便在 HDFS 中有一个存放数据文件的目标目录:

[localhost:21000] > create table parquet_everything like parquet_snappy;
Query: create table parquet_everything like parquet_snappy

然后在 shell 中,我们将对应的数据文件复制到新表的数据目录中。 不采用 hdfs dfs -cp 这一常规文件的操作方式,我们采用 hdfs distcp -pb 命令以确保 Parquet 数据文件特有的 块大小 继续保留。

$ hdfs distcp -pb /user/hive/warehouse/parquet_compression.db/parquet_snappy \
  /user/hive/warehouse/parquet_compression.db/parquet_everything
...MapReduce output...
$ hdfs distcp -pb /user/hive/warehouse/parquet_compression.db/parquet_gzip  \
  /user/hive/warehouse/parquet_compression.db/parquet_everything
...MapReduce output...
$ hdfs distcp -pb /user/hive/warehouse/parquet_compression.db/parquet_none  \
  /user/hive/warehouse/parquet_compression.db/parquet_everything
...MapReduce output...

返回 impala-shell 编译器,我们采用 REFRESH 语句让 Impala 服务器识别表中新的数据文件,然后运行查询,结果表明数据文件包含 30 亿行记录,并且其中某个数值列的值与原来的小表相匹配:

[localhost:21000] > refresh parquet_everything;
Query finished, fetching results ...

Returned 0 row(s) in 0.32s
[localhost:21000] > select count(*) from parquet_everything;
Query finished, fetching results ...
+------------+
| _c0        |
+------------+
| 3000000000 |
+------------+
Returned 1 row(s) in 8.18s
[localhost:21000] > select avg(val) from parquet_everything;
Query finished, fetching results ...
+-----------------+
| _c0             |
+-----------------+
| 250000.93577915 |
+-----------------+
Returned 1 row(s) in 13.35s

与其他 Hadoop 组件交换 Parquet 数据文件

自 CDH 4.5 开始,您可以在 Hive、Pig、MapReduce 中读取和写入 Parquet 数据文件。参考 CDH 4 安装指南 了解详细信息。

之前,不支持在 Impala 中创建 Parquet 数据然后在 Hive 中重用这个表。现在 CDH 4.5 中,Hive 开始支持 Parquet,在 Hive 中重用已有的 Impala Parquet 数据文件需要更新表的元数据。假如您已经使用 Impala 1.1.1 或更高版本,请使用以下命令:

ALTER TABLE table_name SET FILEFORMAT PARQUET;

假如您使用比 Impala 1.1.1 老的版本,通过 Hive 执行元数据的更新:

ALTER TABLE table_name SET SERDE 'parquet.hive.serde.ParquetHiveSerDe';
ALTER TABLE table_name SET FILEFORMAT
  INPUTFORMAT "parquet.hive.DeprecatedParquetInputFormat"
  OUTPUTFORMAT "parquet.hive.DeprecatedParquetOutputFormat";

Impala 1.1.1 及以上版本可以重用 Hive 中创建的 Parquet 数据文件,不需要执行任何操作。

Impala 支持标量数据类型,您可以在 Parquet 数据文件中进行编码,但不支持复合 (composite) 或嵌套 (nested) 类型,如 maps/arrays。假如表的任何一列采用了不受支持的类型,Impala 将无法访问该表。

假如你在不同节点、乃至在相同节点的不同目录复制 Parquet 数据文件,请使用hadoop distcp -pb命令以确保保留原有的块大小。发出命令hdfs fsck -blocks HDFS_path_of_impala_table_dir 并检查平均块大小是否接近 256 MB(或是由 PARQUET_FILE_SIZE 查询设置所定义的其它任何大小),以验证是否保留了块大小。( hadoop distcp 操作通常会生出一些子目录,名称为 _distcp_logs_*,您可以之后从目标目录中删除这些目录)。发出hadoop distcp 命令,了解 distcp 命令的语法详情。

Parquet 数据文件如何组织

尽管 Parquet 是一个面向列的文件格式,但不要期望每列一个数据文件。Parquet 在同一个数据文件中保存一行中的所有数据,以确保在处理过程中,某行的所有列在同一个节点上都可用。Parquet 所做的是设置 HDFS 块大小和与之相匹配的最大数据文件大小,以确保 I/O 和网络传输请求适用于大批量数据。

在数据文件中,多个行的数据会重新排列,以便第一列的所有值都会被重新组织到一个连续的块中,然后是第二列的所有值,依此类推。相同列的值彼此相邻,从而 Impala 可以对这些列的值使用高效的压缩技术。

  Note:

Impala INSERT 语句通过一个大小 与数据文件大小相匹配的 HDFS 块来写入 Parquet 数据文件,以确保每个数据文件对应一个 HDFS 块,并且整个文件可以在单个节点上处理,不需要任何远程读取。

如果您不是通过 Impala 创建的 Parquet 数据文件,例如 MapReduce 或 Pig job,请确保 HDFS 块大小比文件要大或相同,从而维持 每个块一个文件 关系。将 dfs.block.size 或者dfs.blocksize 属性设置为足够大,使得每个文件可与单独的 HDFS 块大小匹配,即使该大小比正常的 HDFS 块要大。

如果在复制文件时块大小被重设为较低的值,你会发现涉及这些文件的查询性能会更低,并且 PROFILE 语句可通过远程读取揭示哪些 I/O 不是最优的。参见 复制 Parquet 数据文件示例 示例,了解在复制 Parquet 数据文件时,如何保留块大小。

当 Impala 检索或测试特定列的数据时,它会打开所有数据文件,但只会读取每个文件中包含该列数据的部分。这些列的值是连续存放,使得处理同一列的数据所需的 I/O 最小化。如果其它的列在 SELECT 列表或 WHERE 子句中列出,在同一个数据文件中同一行的所有列的数据都可用。

假如一个 INSERT 语句导入的数据小于 一个 Parquet 数据块的大小,那么最终的数据文件将小于理想大小。因此,如果您把一个 ETL 作业拆分成多个INSERT 语句,请尽量确保每一个 INSERT 语句插入的数据量接近 256 MB,或 256 MB 的倍数

Parquet 数据文件的 RLE 编码和字典编码

Parquet 基于实际数据值的分析,使用一些自动压缩技术,例如游程编码 (RLE) 和字典编码。当数据值被编码成紧凑的格式后,使用压缩算法,编码的数据可能会被进一步压缩。Impala 创建的 Parquet 数据文件 可以使用 Snappy、GZip,或不进行压缩;Parquet 规格还支持 LZO 压缩,但是目前不支持 LZO 压缩的 Parquet 文件。

除了应用到整个数据文件的 Snappy 或 GZip 压缩之外,RLE 和字典编码是 Impala 自动应用到 Parquet 数据值组的压缩技术。这些自动优化可以节省您的时间,并可省去传统数据仓库通常需要的规划。例如,字典编码降低了创建数值型 ID 作为长字符串缩写的需求。

游程编码压缩了一组重复的数据值。例如,如果多个连续行都包含相同的国家编码,那么可以用该值以及连续出现的次数来表示这些重复的值。

字典编码取出存在于列中的不同的值,并用紧凑的 2-字节替代原始值进行表示,而原始值可能有多个字节。(对压缩后的值还可进行进一步压缩,以节省更多空间。)当列的不同值的个数少于 2**16 (16,384) 时,使用这一类型的编码。对于 BOOLEAN数据类型的列不会应用该编码,因为原始值已经足够短。TIMESTAMP 列有时每行的值都不同,这时可能很快就超过 2**16 个不同值的限制。2**16 的列不同值限制可为每个数据文件进行重新设置,因此如果有多个不同的数据文件,每个文件都包含 10,000 个不同的城市名,每一个数据文件中的城市名一列依然可以使用字典编码来进行压缩。

为 Parquet 表压缩数据文件

如果您对 Parquet 表重用了现有的表结构或 ETL 过程,您可能会遇到 很多小文件 的情况,这时查询效率不是最优的。例如,类似以下的语句可能会产生组织低效的数据文件:

-- In an N-node cluster, each node produces a data file
-- for the INSERT operation. If you have less than
-- N GB of data to copy, some files are likely to be
-- much smaller than the default Parquet block size.
insert into parquet_table select * from text_table;

-- Even if this operation involves an overall large amount of data,
-- when split up by year/month/day, each partition might only
-- receive a small amount of data. Then the data files for
-- the partition might be divided between the N nodes in the cluster.
-- A multi-gigabyte copy operation might produce files of only
-- a few MB each.
insert into partitioned_parquet_table partition (year, month, day)
  select year, month, day, url, referer, user_agent, http_code, response_time
  from web_stats;

以下技术可帮助你在 ParquetINSERT 操作中产生大的数据文件,并压缩现有的过小的数据文件:

  • 向分区 Parquet 表中插入数据时,请使用静态分区INSERT 语句,这样分区关键值将被指定为常量。理想情况下,为每个分区使用一个单独的 INSERT语句。

  • 执行 INSERTCREATE TABLE AS SELECT 语句期间,可以暂时将 NUM_NODES 选项设为 1。通常情况下,这些语句针对每个数据节点生成一个或多个数据文件。如果写入操作涉及少量数据、Parquet 表和/或分区表,则默认的行为是当您凭直觉只希望输出一个文件时生成多个小文件。SET NUM_NODES=1 关闭写入操作的已分配方面,使其更有可能只生成一个或几个数据文件。

  • 与你习惯的传统分析数据库系统相比,做好减少分区关键列个数的准备。

  • 不要期望 Impala-写入的 Parquet 文件会填满整个 Parquet 区块大小。Impala 在计算为每个 Parquet 文件写入多少数据时,会采取保守策略。典型情况下,通过 Parquet 文件格式的压缩和解压缩技术,磁盘内存中的未压缩数据会显著减少。最终的数据文件大小取决于数据的压缩情况。因此,如果一个256 MB 的文本文件被分为 2 个 Parquet 数据文件,并且每个文件都小于 256 MB,这将是正常现象。

  • 如果不巧一个表的末尾是很多小的数据文件,可以考虑使用前述技术中的一种或多种,通过CREATE TABLE AS SELECTINSERT … SELECT 语句,将所有数据复制到一个新的 Parquet 表中。

    为避免重复查询更改表名,你可以采用一个约定,始终在一个视图上运行重要的查询。立即更改视图定义,所有后继查询都使用新的基础表:

    create view production_table as select * from table_with_many_small_files;
    -- CTAS or INSERT...SELECT all the data into a more efficient layout...
    alter view production_table as select * from table_with_few_big_files;
    select * from production_table where c1 = 100 and c2 < 50 and ...;
    

Parquet 表的模式 (Schema) 演进

模式演进是指使用ALTER TABLE … REPLACE COLUMNS 语句,更改表的名称、数据类型或列的个数。您可以根据以下步骤,执行 Parquet 表的模式演进:

  • Impala ALTER TABLE 语句不会更改表中的任何数据文件。对 Impala 而言,模式演进涉及根据新的表定义,对相同的数据文件进行解释。某些类型的模式变化合乎情理,可以正确表示。其它类型的模式变化可能无法以合理的方式表示,会在查询过程中产生特殊的结果值或是出现转换错误。

  • 语句 INSERT 总是以最新的表定义创建数据。如果你依次执行了INSERTALTER TABLE … REPLACE COLUMNS 语句,你可能得到列数不同或内部数据表示不同的数据文件。

  • 如果你在末尾使用 ALTER TABLE … REPLACE COLUMNS 来定义更多的列,当原始数据文件在查询中被使用时,最后添加的列将全部被认为是NULL 值。

  • 如果你使用 ALTER TABLE … REPLACE COLUMNS 定义了比过去更少的列,当原始数据文件在查询中被使用时,未使用的列在数据文件中仍然会被忽略。

  • Parquet 视 TINYINTSMALLINT,和 INT 为相同的内部类型,都以 32-位整数的方式存储。

    • 这意味着很容易将 TINYINT 列升级为 SMALLINTINT,或是将 SMALLINT 列升级为 INT。在数据文件中数字将以完全相同的方式展示,升级后的列不会包含任何超出范围的值。
    • 如果你把其中任何一个列类型更改为小一些的类型,那么在新类型下超出范围的值,其返回值将发生错误,典型地为负数。

    • 你无法将 TINYINTSMALLINTINT 列更改为 BIGINT,或其它类似方式。尽管 ALTER TABLE 执行成功,但是任何对这些列的查询都会导致转换错误。

    • 查询过程中,列的任何其它类型的转换都会在产生转换错误。例如, INT 转换至 STRINGFLOAT 转换至 DOUBLETIMESTAMP 转换至STRINGDECIMAL(9,0) 转换至 DECIMAL(5,2),等待。

Parquet 表的数据类型考虑

Parquet 格式定义了一系列数据类型,其名称与相应的 Impala 数据类型名称不同。如果你在准备 Parquet 文件过程中使用的是其它 Hadoop 组件(如 Pig 或 MapReduce),你可能需要使用 Parquet 定义的类型名称。下图列出了 Parquet 定义的类型,以及对应的 Impala 类型。

基本类型:

BINARY -> STRING
BOOLEAN -> BOOLEAN
DOUBLE -> DOUBLE
FLOAT -> FLOAT
INT32 -> INT
INT64 -> BIGINT
INT96 -> TIMESTAMP

逻辑类型:

BINARY + OriginalType UTF8 -> STRING
BINARY + OriginalType DECIMAL -> DECIMAL

 

Leave a Reply