Impala數據文件的碎碎念

Impala目前支持Hadoop中幾種常見的文件格式 ParquetORCTextAvroRCFileSequenceFile 。下面簡要說明各種格式的使用、限制和一些注意事項。

不同的文件格式有着不同的適用場景,並且對性能有着重大的影響。不同的文件格式支持的壓縮編碼不同,不同的編碼影響這IO和CPU資源的開銷,較高的壓縮率需要較大的CPU資源,但是數據移動成本較低。使用時需要權衡CPU時間和數據傳輸時間。

除了Parquet和Text(未壓縮)文件格式外,其他的文件格式impala都不能進行數據的插入。有關特定文件格式,請參見下表。

File Type Format Compression Codecs Impala Can CREATE? Impala Can INSERT?
Parquet 結構化 Snappy, gzip, zstd; 默認爲Snappy Yes. Yes: CREATE TABLE, INSERT, LOAD DATA, and query.
ORC 結構化 gzip, Snappy, LZO, LZ4; 默認爲gzip ORC是 CDH 6.1/Impala 3.1&Impala 2.12開始新增的實驗性的特性。禁用方式: set ‑‑enable_orc_scanner=false  No. Impala只能進行查詢,需要通過其他組件加載數據。
Text 非結構化 LZO, gzip, bzip2, Snappy Yes. 創建表時的默認格式。 未壓縮的情況下可以插入,否則不可以。如果是LZO的壓縮編碼,那麼需要通過HIVE來創建表和加載數據。其他壓縮編碼,加載數據需要通過其他組件完成
Avro 結構化 Snappy, gzip, deflate Yes,Impala 1.4.0開始可以創建,之前的版本需要通過hive創建 No. 需要通過其他組件進行數據加載
RCFile 結構化 Snappy, gzip, deflate, bzip2 Yes. No. 需要通過其他組件進行數據加載
SequenceFile 結構化 Snappy, gzip, deflate, bzip2 Yes. No. 需要通過其他組件進行數據加載

Impala支持以下的Compression Codecs:

Snappy: 在壓縮率和解壓縮速度之間比較平衡。Impala2.0及以上版本支持text, RC, Sequence 和 Avro文件格式

Gzip:擁有最高的壓縮率。Impala2.0及以上版本支持text, RC, Sequence 和 Avro文件格式

Deflate: 不支持文本格式

Bzip2:Impala2.0及以上版本支持text, RC, and Sequence文件格式

LZO:只支持文本格式。Impala可以查詢LZO壓縮的文本表,但目前無法創建它們或將數據插入其中。需要在Hive中執行這些操作。

Zstd:只支持Parquet文件格式

不同的文件格式和Compression Codecs適用於不同的數據集。爲數據選擇適當的格式可以產生更好的性能:

  • 如果是已經存在的文件,那麼在性能可以接受的情況下,繼續使用該文件格式。如果不可接受,那麼新建一個表,使用新的文件格式和compression codecs,並一次性將數據寫入到新表中。
  • text格式是默認的文件格式,可以很方便的在不同工具之間傳輸,並且是人類可讀的,所以在使用中是一個很普遍的格式。但是如果性能和磁盤空間是首要考慮因素,那麼就使用其他結構化的文件格式,並採用合理的壓縮編碼。

Text Data Files

Text是一種比較通用的文件格式,可以很方便的與其他應用或者腳本進行數據交換,常見的如CSV和TSV等。

文本文件格式在列定義上非常的靈活。文本文件中可以包含比Impala表定義多的列,此時查詢數據時會忽略文件中多出的列;包含的列也可以比Impala少,此時對於缺少的列,Impala會將其作爲 NULL 值。

關於查詢性能

以文本格式存儲的數據相對笨重,查詢性能不像二進制格式(如parquet)那樣高效。使用Text文件格式比較典型的場景是收到的文件即爲該文本格式,或者是需要輸出該文件格式用於與其他應用的數據交換。如果需要更加高效的文件格式,可以創建一個使用二進制 爲了得到更緊湊的數據,可以使用LZO壓縮,基於可分割的特性,可以讓不同節點,對同一文件的不同部分併發工作。

在impala 2.0或者更高版本,也可以使用gzip, bzip2, or Snappy 格式壓縮,但是這些壓縮格式不支持對文件進行併發操作。所以對於文本文件的壓縮,LZO是首選。

Text table的創建

不加任何額外語句創建的表格式就是text文件格式的。默認使用ctrl-A作爲分隔符。

create table my_table(id int, s string, n int, t timestamp, b boolean);

也可以通過 fields terminated by 指定分隔符

create table csv(id int, s string, n int, t timestamp, b boolean)
  row format delimited
  fields terminated by ','
  stored as textfile;  -- 一般情況下不需要指定,默認即爲textfile

create table tsv(id int, s string, n int, t timestamp, b boolean)
  row format delimited
  fields terminated by '\t';

create table pipe_separated(id int, s string, n int, t timestamp, b boolean)
  row format delimited
  fields terminated by '|'
  stored as textfile;

不要用引號來來將文本內容括起來,如果是文本中包含分隔符,使用 ESCAPE BY 子句指定的轉義符進行轉義。

DESCRIBE FORMATTED table_name 語句可以查看錶格式的詳細信息。

複雜類型的注意事項:目前Text表不支持複雜數據類型

Text Table中的數據文件

當Impala查詢文本格式數據的表時,它會查閱數據目錄中的所有數據文件,但有些例外:

  • 忽略點或者下劃線開頭的隱藏文件
  • Impala查詢時會忽略Hadoop工具用於臨時工作文件的擴展名的文件。如 .tmp.copying
  • 當是壓縮表的時候,通過後綴識別文件是否壓縮,切只掃描改部分文件。 .bz2 .snappy .gz

INSERT...SELECT 會在每一個SELECT語句處理數據的節點產生一個數據文件。 INSERT…VALUES 會爲每一個語句產生一個數據文件。經常使用 INSERT...VALUES 會產生大量的小文件,不建議使用該語句插入數據。如果發現表中存在大量的小文件,可以通過 INSERT...SELECT 將數據插入到新的表中來解決該問題。

Text數據文件中的特殊值

  • 對數值類型的列 inf 表示無窮大, nan 表示非數字。
  • \N 表示 null 。使用Sqoop的時候,設置以下參數來確保產生正確的 NULL--null-string '\\N' --null-non-string '\\N'
  • 默認情況下Sqoop會使用 null 字符串代表空值。這將導致在impala中識別錯誤,可以通過 ALTER TABLE name SET TBLPROPERTIES("serialization.null.format"="null") 來解決這個問題。
  • 在impala 2.6以及以上版本,可以通過 skip.header.line.countTBLPROPERTIES 參數來跳過表頭的指定行數。 alter table header_line set tblproperties('skip.header.line.count'='1');

加載數據到Impala的text表

  • LOAD DATA 語句指定具體的數據文件或者一個目錄,將其移動到Impala表的數據目錄下。
  • 其他格式的表通過 CREATE TABLE AS SELECT 或者 INSERT INTO 的方式傳輸到 Text 表。
-- Text table with default delimiter, the hex 01 character.
CREATE TABLE text_table AS SELECT * FROM other_file_format_table;

-- Text table with user-specified delimiter. Currently, you cannot specify
-- the delimiter as part of CREATE TABLE LIKE or CREATE TABLE AS SELECT.
-- But you can change an existing text table to have a different delimiter.
CREATE TABLE csv LIKE other_file_format_table;
ALTER TABLE csv SET SERDEPROPERTIES ('serialization.format'=',', 'field.delim'=',');
INSERT INTO csv SELECT * FROM other_file_format_table;
  • 也可以使用 HDFS的 hdfs dfs -puthdfs dfs -cp 將數據文件放置到impala表目錄中,並使用 REFRESH table_name 刷元數據。

Parquet Data Files

Impala允許你創建、管理和查詢Parquet表。Parquet是一個面向列的二進制文件格式,並且自帶Schema,支持高效率的大規模數據查詢,尤其適合查詢表中特定少部分列的場景。

Impala寫入的每個Parquet文件都包含了一組行數據,其中每列的值都被組織在相鄰的位置,從而對這些值實現了良好的壓縮,實現最快和最小IO的數據查詢。

創建ParquetTable

在建表時指定 STORE AS PQRQUET 子句.

create table parquet_table_name (x INT, y STRING) STORED AS PARQUET;

-- 或者從其他表複製表結構,並指定存儲文件格式
create table parquet_table_name LIKE other_table_name STORED AS PARQUET;

在Impala 1.4.0以及以上版本,可以通過parquet數據文件來推測表結構

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

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

-- 如果是分區表,分區信息不是parquet文件的一部分,需要建表時指定
CREATE TABLE columns_from_data_file LIKE PARQUET '/user/etl/destination/datafile1.dat'
  PARTITION (year INT, month TINYINT, day TINYINT)
  STORED AS PARQUET;

加載數據到ParquetTables

  • 可以使用 INSERT...SELECT 語句將其他表的數據寫入parquet表。避免使用 INSERT...VALUE 語句向parquet表寫入數據,它會爲每個語句產生一個文件。在INSERT數據時,至少保證HDFS中有一個數據塊(默認1GB)的空間,否則會插入失敗。
  • 如果是已經存在parquet數據文件,可以使用以下方式加載數據: 1) LOAD DATA 語句將數據文件或者目錄移動到Parquet表目錄中 2) CREATE TABLE 的時候指定 LOCATION 位置爲已經存在Parquet數據文件的目錄。或者使用 CREATE EXTERNAL TABLE 可以保證在刪除表的時候不會刪除數據文件。 3) hadoop的 hadoop distcp -pb 命令(這個命令可以保證parquet數據文件塊的大小與表定義一致)將文件移動到相應的parquet表目錄下,並 REFRESH 表以識別新加的數據文件

加載數據到parquet表是一個內存敏感的操作,寫入的數據會緩存達到一個數據塊大小的時候纔會寫入到磁盤。如果是分區表,會爲每個分區緩存一份數據,當所有數據都到達時再寫入數據文件。當向分區表寫入數據時,Impala會在節點之間重新分佈數據以減少內存消耗。

Parquet表的查詢性能

一個Parquet文件由Header、DataBlock和Footer組成。 HEADER部分會有個Magic Number用於標識這是一個Parquet文件。 DataBlock是實際存儲數據的地方,包括以下三部分: 1)行組(row group):多行數據組成,默認一個行組與HDFS的一個數據塊大小對齊。實現數據的並行處理。 2)列塊(Column Chunk):行組中的數據以列塊的形式保存,同一列的數據相鄰。實現列式存儲。 3)數據頁(data page):最小存儲編碼,每一個列塊包含多個頁。更細粒度的數據訪問 Footer部分由File Metadata、Footer Length和Magic Number三部分組成。Footer Length是一個4字節的數據,用於標識Footer部分的大小,幫助找到Footer的起始指針位置。Magic Number同樣是PAR1。File Metada包含了非常重要的信息,包括Schema和每個Row Group的Metadata。每個Row Group的Metadata又由各個Column的Metadata組成,每個Column Metadata包含了其Encoding、Offset、Statistic信息等等

1)影響查詢性能的主要因素是查詢語句中使用到的列的數量,parquet的列式存儲,在一個具有大量列的表中,查詢少量的列,可以減少數據的讀取提升性能。 2)對於分區表,Impala只會讀取指定分區中的數據。 3)依據Footer中的Statistic信息可以進行Predicate Filter,根據where條件,只讀取指定文件中特定row group的數據。在這裏分兩種情況,一種是數值型的,則根據max和min值篩選。另一種是字符型,根據字典編碼篩選。

Parquet表的壓縮

parquet文件支持 Snappy, gzip , zstd 三種壓縮格式,通過 COMPRESSION_CODEC 的查詢參數控制。默認情況下是 snappy 格式的壓縮, gzip 的壓縮比例最大。越高的壓縮比,在解壓縮的時候需要耗費更多的CPU時間。

[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

壓縮之後會把壓縮格式的元數據寫入數據文件當中,此時查詢的時候會忽略 COMPRESSION_CODEC 參數,所以,一個表中可以包含多種壓縮格式的文件。

當在不同的節點或者目錄複製parquet文件的時候 使用 hadoop distcp -pb 來確保文件塊的大小與之前相同。

壓縮parquet文件(小文件合併大文件)

哪些情況會導致很多小文件? 1、當在N個節點進行數據處理,會在每個節點產生一個數據文件。並且每個節點產生的數據很少的時候。 2、即使每個節點有大量數據,但是在此基礎上進行分區,如果每個分區的文件大小過小,也會產生大量小文件

避免產生大量小文件的方法: 1、插入分區表時,使用靜態分區,不要使用動態分區 2、當 INSERT 或者 CREATE TABLE AS 語句,會在不同節點產生大量小文件時。使用 SET NUM_NODES=1 查詢參數,來取消分佈式的特性,從而產生一個或少量文件。 3、減少分區數量 4、在內存中是未被壓縮的數據大小,一個在內存中大於256MB的數據,被壓縮後會小於256MB,但是仍然會寫入到兩個數據文件之中 5、當目前有一個很多小文件的表的時候,考慮將數據寫入到新的表中。

表模式的修改

ALTER TABLE ... REPLACE COLUMNS 對列進行修改: 1)該語句不會修改數據文件。只是根據新的定義來解析數據文件。某些不合理的修改會導致查詢結果出現異常值或者產生錯誤 2) INSERT 語句將使用最近的一次表定義的結構。 3)當新增列之後,之前插入數據缺少的這列值將會被置空。 4)如果修改之後,表定義減少了一些列,在讀取數據的時候會忽略之前寫入的數據。 5)Parquet文件中 TINYINT SMALLINTINT 在內部都是32位整數。

ORC Data Files

對orc數據文件的讀取,作爲實驗功能,從impala 3.1開始加入。

創建ORC表,並加載數據

CREATE TABLE orc_table (column_specs) STORED AS ORC;

目前impala不能向orc數據文件中寫入數據,只能通過HIVE或者其他外部組件將數據加載進來,然後使用 REFRESH table_name 刷新數據。

啓用壓縮

hive> SET hive.exec.compress.output=true;
hive> SET orc.compress=SNAPPY;
hive> INSERT OVERWRITE TABLE new_table SELECT * FROM old_table;

-- 對於分區表
hive> CREATE TABLE new_table (your_cols) PARTITIONED BY (partition_cols) STORED AS new_format;
hive> SET hive.exec.dynamic.partition.mode=nonstrict;
hive> SET hive.exec.dynamic.partition=true;
hive> INSERT OVERWRITE TABLE new_table PARTITION(comma_separated_partition_cols) SELECT * FROM old_table;

關於性能

比text快,但是比parquet慢

Avro Data Files

Avro的性能比Text好,比Parquet差。

創建表

在表定義時需要聲明所有的列以匹配Avro文件的Schema

[localhost:21000] > CREATE TABLE avro_only_sql_columns
                  > (
                  >   id INT,
                  >   bool_col BOOLEAN,
                  >   tinyint_col TINYINT, /* Gets promoted to INT */
                  >   smallint_col SMALLINT, /* Gets promoted to INT */
                  >   int_col INT,
                  >   bigint_col BIGINT,
                  >   float_col FLOAT,
                  >   double_col DOUBLE,
                  >   date_string_col STRING,
                  >   string_col STRING
                  > )
                  > STORED AS AVRO;

[localhost:21000] > CREATE TABLE impala_avro_table
                  > (bool_col BOOLEAN, int_col INT, long_col BIGINT, float_col FLOAT, double_col DOUBLE, string_col STRING, nullable_int INT)
                  > STORED AS AVRO
                  > TBLPROPERTIES ('avro.schema.literal'='{
                  >    "name": "my_record",
                  >    "type": "record",
                  >    "fields": [
                  >       {"name":"bool_col", "type":"boolean"},
                  >       {"name":"int_col", "type":"int"},
                  >       {"name":"long_col", "type":"long"},
                  >       {"name":"float_col", "type":"float"},
                  >       {"name":"double_col", "type":"double"},
                  >       {"name":"string_col", "type":"string"},
                  >       {"name": "nullable_int", "type": ["null", "int"]}]}');

[localhost:21000] > CREATE TABLE avro_examples_of_all_types (
                  >     id INT,
                  >     bool_col BOOLEAN,
                  >     tinyint_col TINYINT,
                  >     smallint_col SMALLINT,
                  >     int_col INT,
                  >     bigint_col BIGINT,
                  >     float_col FLOAT,
                  >     double_col DOUBLE,
                  >     date_string_col STRING,
                  >     string_col STRING
                  >   )
                  >   STORED AS AVRO
                  >   TBLPROPERTIES ('avro.schema.url'='hdfs://localhost:8020/avro_schemas/alltypes.json');

avro中的空值,用 null 表示

Impala 2.3以及以上版本,Impala會在創建表的時候就檢查表定義與Avro中Schema是否一致。當存在表定義,與avro文件不匹配的時候的處理方式: 1)當列數不匹配的時候,impala會使用avro中的模式定義 2)當列名稱或者類型不匹配的時候,impala會使用avro中的模式定義 3)當impala中的 TIMESTAMP 映射到avro中的 STRING 時,以 STRING 表示

Avro數據文件不支持定義複雜數據類型。

Impala目前不支持向Avro中寫入數據,因此數據只能從其他組件加載,然後在Impala中查詢。

開啓壓縮

只能在Hive中操作。Impala支持 snappydeflate

hive> set hive.exec.compress.output=true;
hive> set avro.output.codec=snappy;

RCFile Data Files

impala目前不能往RCFile中寫入文件。只能通過其他組件加載數據到Impala。

RCFile的性能比Text好,比Parquet差。

$ impala-shell -i localhost
[localhost:21000] > create table rcfile_table (x int) stored as rcfile;
[localhost:21000] > create table rcfile_clone like some_other_table stored as rcfile;
[localhost:21000] > quit;

$ hive
hive> insert into table rcfile_table select x from some_other_table;
3 Rows loaded to rcfile_table
Time taken: 19.015 seconds
hive> quit;

$ impala-shell -i localhost
[localhost:21000] > select * from rcfile_table;
Returned 0 row(s) in 0.23s
[localhost:21000] > -- Make Impala recognize the data loaded through Hive;
[localhost:21000] > refresh rcfile_table;
[localhost:21000] > select * from rcfile_table;
+---+
| x |
+---+
| 1 |
| 2 |
| 3 |
+---+
Returned 3 row(s) in 0.23s

開啓壓縮

只能在Hive中操作。

hive> SET hive.exec.compress.output=true;
hive> SET mapred.max.split.size=256000000;
hive> SET mapred.output.compression.type=BLOCK;
hive> SET mapred.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
hive> INSERT OVERWRITE TABLE new_table SELECT * FROM old_table;

--如果是分區表
hive> CREATE TABLE new_table (your_cols) PARTITIONED BY (partition_cols) STORED AS new_format;
hive> SET hive.exec.dynamic.partition.mode=nonstrict;
hive> SET hive.exec.dynamic.partition=true;
hive> INSERT OVERWRITE TABLE new_table PARTITION(comma_separated_partition_cols) SELECT * FROM old_table;

Sequence Data Files

RCFile的性能比Text好,比Parquet差。

create table sequencefile_table (column_specs) stored as sequencefile;

Impala不能向sequence文件中寫入數據。使用Hive或其他方式加載數據。

$ impala-shell -i localhost
[localhost:21000] > create table seqfile_table (x int) stored as sequencefile;
[localhost:21000] > create table seqfile_clone like some_other_table stored as sequencefile;
[localhost:21000] > quit;

$ hive
hive> insert into table seqfile_table select x from some_other_table;
3 Rows loaded to seqfile_table
Time taken: 19.047 seconds
hive> quit;

$ impala-shell -i localhost
[localhost:21000] > select * from seqfile_table;
Returned 0 row(s) in 0.23s
[localhost:21000] > -- Make Impala recognize the data loaded through Hive;
[localhost:21000] > refresh seqfile_table;
[localhost:21000] > select * from seqfile_table;
+---+
| x |
+---+
| 1 |
| 2 |
| 3 |
+---+
Returned 3 row(s) in 0.23s

開啓壓縮

在Hive中進行操作

hive> SET hive.exec.compress.output=true;
hive> SET mapred.max.split.size=256000000;
hive> SET mapred.output.compression.type=BLOCK;
hive> SET mapred.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
hive> insert overwrite table new_table select * from old_table;

-- 如果是分區表
hive> create table new_table (your_cols) partitioned by (partition_cols) stored as new_format;
hive> SET hive.exec.dynamic.partition.mode=nonstrict;
hive> SET hive.exec.dynamic.partition=true;
hive> insert overwrite table new_table partition(comma_separated_partition_cols) select * from old_table;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章