Clouderaのドキュメントに書かれているImpala Performance Guidelines and Best Practices
が非常に素晴らしい内容なので翻訳した。
内容は Apache Impala (incubating) (以下 Impala) をターゲットとして記述しているが、パーティション設計などについては Hive にそのまま適用できる内容なので、Impala を使用していない人でも読んで損はないと思う。
環境
CDH 5.7.0 (Impala 2.5.0)
本文
このドキュメントは、Impalaを利用するCDHクラスタのための、計画、実験、パフォーマンスチューニング時に利用可能なパフォーマンスガイドラインとベストプラクティスです。この情報は全て、Impala ドキュメンテーションの他のページでより詳細に記載されているものです。これらの情報はクックブックとして提供され、多くの場合に最も高い投資対効果を提供するパフォーマンステクニックを強調するためにここに集約されています。
目次
- データに適したファイルフォーマットを選択する
- 多数の小さなファイルを生成するデータ取り込みプロセスを避ける
- 実際のデータ量に基づいてパーティションの粒度を選択する
- パーティションキー列に対し、最小かつ適切な整数型を使用する
- 適切なParquetブロックサイズを選択する
- パフォーマンスが重要なクエリ、あるいは大量のJOINクエリに使用される全てのテーブルの統計情報を収集する
- クライアントに結果を返す転送のオーバヘッドを最小化する
- 効率的かつ論理的な方法でクエリが計画されていることを確認する
- クエリのパフォーマンス特性を確認する
- 適切なオペレーティングシステムの設定を使用する
データに適したファイルフォーマットを選択する
一般的に、大量のデータにおいて(テーブル毎またはパーティション毎に数GB)、Parquetファイルフォーマットは最高のパフォーマンスを発揮しますが、これはカラムナストレージレイアウト、大きなI/Oリクエストサイズ、圧縮そしてエンコーディングによるものです。Impalaがサポートする全てのファイルフォーマットの比較についてはImpalaはどのようにHadoopファイルフォーマットと連携するのかを、ParquetファイルフォーマットについてはParquetファイルフォーマットをImpalaテーブルに使用するを参照してください。
注意:各テーブルあるいはパーティションで数GB以下のデータしかないような小さいデータ量では、ファイルフォーマット間での有意な性能差は確認できない可能性があります。小さいデータ量においては、効率的な圧縮済みファイルフォーマットによるI/Oの低減は、並列実行の機会の低減によって相殺される場合があります。本番環境の計画やベンチマークの実施時には、パフォーマンスやスケーラビリティについての正確な情報を取得するために、常に現実的なデータ量を使用してください。
多数の小さなファイルを生成するデータ取り込みプロセスを避ける
Impalaの外でデータファイルを生成するときは、一行づつファイルを作ることができるテキストフォーマットかAvroのいずれかを使用することを推奨します。一旦データがImpalaに入れば、 INSERT ... SELECT
文を用いることにより、より効率的なParquetフォーマットに変換し、複数のデータに分割することができます。あるいは、もしデータ準備プロセスの一部として数MBのParquetファイルを生成する基盤を持っている場合は、Impala内での変換ステップをスキップしてその処理を行ってください。
Impala内でのテーブル間の大量のデータのコピーには、常に INSERT...SELECT
を使用してください。あらゆる大量のデータあるいはパフォーマンスが重要なテーブルに対する INSERT...VALUES
の使用を避けてください、なぜならその文は分割された非常に小さなデータファイルを生成するからです。 INSERT ... SELECT
文の例については INSERT 文 を参照してください。
たとえば、もしParquetテーブル内に数千パーティションがあり、それぞれが256MB未満のデータしかない場合、より粒度の粗い、たとえば年/月/日ではなく年/月といった風にパーティショニングを行うことを検討してください。もし、非効率なデータ取込みプロセスが同じテーブルあるいはパーティションに対し数千データファイルを生成する場合、 INSERT ... SELECT
を実行して異なるテーブルに全データをコピーすることでデータのコンパクションを行うことを検討してください。 この処理により、データはより大きく、かつより少数のファイルに再編成されます。
実際のデータ量に基づいてパーティションの粒度を選択する
パーティションは、1つ以上の列の値、たとえば年、月、日、地域、年、ウェブサイトのセクションなどの値にもとづいてデータを物理的に分割する手法です。パーティションキー列の値の特定の値または範囲を要求するクエリを発行すると、Impalaは無関係なデータの読み取りを回避することで、間接的に膨大なディスクI/Oの節約をもたらすことができます。
パーティショニングに使用する列(複数可)を決定する際には、粒度の適切なレベルを選択してください。たとえば、年、月、日によって、または年と月のみによってパーティション化すべきか、などです。HDFSのバルクI/OとImpalaの分散クエリの利点を活かすために、各パーティションに少なくとも256MBのデータを置くようにパーティション戦略を選択してください。
過分割によって、Impalaが不必要なパーティションまでプルーニングしてしまうために、クエリ実行計画に必要以上に時間をかける可能性があります。テーブルパーティションの数は3万以下に抑えるのが理想です(2018/02/06追記: 最近は10万以下と言われています)。
パーティションディレクトリに移動させるためのデータファイルを準備する場合、多数の小さいファイルではなく数個の大きなファイルを作成するようにしてください。もし多くの小さなファイルの形式でデータを受信し、かつ入力形式を制御できない場合、 INSERT ... SELECT
文を使ってデータをテーブルあるいはパーティションから別の場所にコピーすることで、ファイルを比較的小さい数(クラスタ内のノード数に基づいて決定されます)にコンパクションすることを検討してください。
もしパーティションの総数を減らし、各パーティション内のデータ量を増やす必要がある場合は、まず、滅多に参照されないあるいは(SLAの対象でない)重要度の低いクエリから参照されているパーティションキー列を探してください。たとえば、Webサイトのログデータは年、月、日、時間によってパーティションを切られているかもしれませんが、もしほとんどのクエリが一日ごとに結果をまとめる場合は、パーティションは年、月、日しか必要ないかもしれません。
もしさらに粒度を減らす必要がある場合、「バケット」というパーティション・キー値の異なるセットに対応する計算された値を作成することを検討してください。たとえば、週または四半期などの時間間隔にもとづいた日付と時刻の値をグルーピングするために TIMESTAMP
カラムと TRUNC()
関数を使用することができます。詳細はImpalaの日付と時刻関数を参照してください。
パーティショニングにおけるパフォーマンスの検討についての完全な詳細については、Impalaのテーブルをパーティショニングするを参照してください。
パーティションキー列に対し、最小かつ適切な整数型を使用する
最終的には全てHDFSディレクトリ名になるため、パーティションキー列に文字列を使いたくなりますが、 年
、 月
、 日
などの一般的なパーティションキーのフィールドに数値を使用してメモリ使用量を最小限に抑えることができます。適切な値の範囲を保持する最小の整数型を使用してください。例えば、 月
と 日
には TINYINT
、 年
には SMALLINT
などです。 TIMESTAMP
の値から個別の日付と時刻のフィールドを引き出すために EXTRACT()
関数を使用し、 CAST()
して適切な整数型として値を返すようにしてください。
適切なParquetブロックサイズを選択する
デフォルトでは、Impalaの INSERT ...SELECT
文では、256MBのブロックサイズでParquetファイルを作成します。(このデフォルトは、Impala2.0で変更されました。以前はこの制限は1GBでしたが、Impalaが圧縮について保守的な見積もりを行っていて、結果として1GBよりも小さいファイルに影響を与えていました。)
Impalaによって書かれたそれぞれのParquetファイルは、単一のホストによって一つの単位としてファイル全体が処理されることを可能にします。ParquetファイルをHDFSに投入する、あるいはHDFSファイルシステム間でコピーする場合、 hdfs dfs -pb
を使用して元のブロックサイズを保持してください。
もしParquetテーブル、あるいはあるクエリからアクセスされる唯一のパーティションに1つあるいは数個のデータブロックしかない場合、データ量が不十分でImpalaの並列分散クエリの恩恵を受けることができないという理由により速度低下を経験することになるかもしれません。各データブロックは、データノードのいずれかの単一のコアによって処理されます。16コアのマシンの100ノードのクラスタでは、潜在的に同時に数千データブロックを処理することができます。バルクI/Oと並列処理のバランスをとる、「多くの小さなファイル」と「単一の巨大なファイル」の中間のスイートスポットを見つけるにはどうしたらいいでしょうか。 INSERT ... SELECT
文を使う前に PARQUET_FILE_SIZE
を設定することで、生成される各 Parquet ファイルのサイズを減らすことができます。(サイズはバイト数の絶対値として、あるいはImpala2.0以降であれば、MB単位なら m
、GB単位なら g
を末尾につけて指定します。) 特定のデータ量の適切なバランスポイントを見つけるために、異なるファイルサイズでベンチマークを実行します。
パフォーマンスが重要なクエリ、あるいは大量のJOINクエリに使用される全てのテーブルの統計情報を収集する
COMPUTE STATS
文を使用して統計情報を収集します。詳細はJOINクエリのためのパフォーマンスの検討を参照してください。
クライアントに結果を返す転送のオーバヘッドを最小化する
次のような技術を使用してください。
- 集約。どれだけの行が条件に合うか、ある列においてマッチした値の合計値、マッチした値の最小値や最大値などを知る必要がある場合、結果セットをアプリケーションに返してそれらの計算を行うのではなく、クエリ内で
COUNT()
、SUM()
、そしてMAX()
などの集約関数を呼び出してください。集約していない結果セットのサイズは巨大な可能性があり、ネットワークを介して送信するためにかなりの時間を必要とする可能性があることに注意してください。 - フィルタリング。大きな結果セットを生成し、アプリケーション・ロジックを使用して、それをフィルタリングするよりも、関連していない行を除去するために、クエリの
WHERE
句に適用可能なすべてのテストを使用してください。 -
LIMIT
句。結果セットのわずかなサンプル値、あるいはORDER BY
を使用したクエリの最初あるいは最後を見るだけの場合、完全な結果セットを要求し、そしてそのほとんどの行を捨てるのではなく、LIMIT
句を含めて結果セットのサイズを小さくしてください。 - 結果セットを表示して、画面上に表示するオーバーヘッドを避けてください。
impala-shell
から結果を取得するときは、impala-shell
の-B
や--output_delimiter
のようなオプションを使って特別な整形を行うことなく出力したり、出力を画面に表示するのではなくファイルにリダイレクトするようにしてください。HDFS内で新しいファイルに直接書き出すときは、INSERT ... SELECT
の使用を検討してください。impala-shell
コマンドラインオプションの詳細はimpala-shell 設定オプション を参照してください。
効率的かつ論理的な方法でクエリが計画されていることを確認する
クエリを実際に実行する前に、 EXPLAIN
プランを調べてください。詳細はEXPLAIN 文及びパフォーマンスチューニングのためにEXPLAINプランを使用するを参照してください。
クエリのパフォーマンス特性を確認する
クエリを実行した後のクエリプロファイルを調べることによって、I/O、メモリ使用量、ネットワーク帯域幅、CPU使用率などの低レベルの状況が期待される範囲内にあることを確認してください。詳細はパフォーマンスチューニングのためのクエリプロファイルを使用するを参照してください。
適切なオペレーティングシステムの設定を使用する
Impalaのパフォーマンスに影響を与えることのできるOSの設定変更の推奨については、CDHのパフォーマンスを最適化するを参照してください。特に、Linuxカーネルの vm.swappiness
の値をゼロ以外の値に設定を変更すると、全体のパフォーマンスを向上させることができるかもしれません。