Athena でのパフォーマンスのチューニング - Amazon Athena

Athena でのパフォーマンスのチューニング

このトピックでは、Athena クエリのパフォーマンスを向上させるための一般的な情報と具体的な提案、および制限やリソース使用量に関連するエラーの回避方法について説明します。

Service Quotas

Athena では、クエリ実行時間、アカウント内の同時クエリ数、API リクエスト率などの指標に対してクォータを適用しています。これらのクォータの詳細については、「Service Quotas」を参照してください。これらのクォータを超えると、送信時またはクエリ実行中にクエリが失敗します。

このページのパフォーマンス最適化の多くのヒントは、クエリの実行時間を短縮するのに役立ちます。最適化によって容量の制限がなくなり、同時実行クォータ内でより多くのクエリを実行できるようになり、実行時間が長すぎたためにクエリがキャンセルされるのを防ぐことができます。

同時実行クエリと API リクエストの数のクォータは、AWS アカウント および AWS リージョン ごとです。ワークロードが同じクォータに対して競合しないように、AWS アカウント につき 1 つのワークロードを実行する (またはプロビジョニング済みのキャパシティ予約を別々に使用する) ことをお勧めします。

同じアカウントで 2 つのワークロードを実行すると、そのうちの 1 つのワークロードで大量のクエリを実行できます。これにより、残りのワークロードがスロットリングされたり、クエリの実行がブロックされたりする可能性があります。これを回避するには、ワークロードを個別のアカウントに移動して、各ワークロードに独自の同時実行クォータを割り当てることができます。片方または両方のワークロードのプロビジョニング済みキャパシティ予約を作成しても、同じ目標が達成されます。

その他のサービスのクォータ

Athena がクエリを実行すると、クォータを適用する他のサービスを呼び出せます。クエリの実行中に、Athena は AWS Glue Data Catalog、Amazon S3、および IAM や AWS KMS などその他の AWS サービスなどに API 呼び出しを行えます。フェデレーションクエリを使用する場合、Athena は AWS Lambda に呼び出しも行います。これらのサービスにはすべて、超過できる独自の制限と割り当てがあります。クエリ実行でこれらのサービスからエラーが発生すると、ソースサービスからのエラーも含めて失敗します。復元可能なエラーは再試行されますが、問題が時間内に解決されない場合、クエリが失敗する可能性があります。エラーメッセージをよく読んで、Athena からのものか別のサービスからのものかを判断してください。いくつかの関連するエラーは、このドキュメントで説明されています。

Amazon S3 サービスクォータに起因するエラーの回避方法の詳細については、このドキュメントの後半の「ファイルが多すぎにならないようにする」を参照してください。Amazon S3 のパフォーマンスの最適化の詳細については、「Amazon S3 ユーザーガイド」の「ベストプラクティスの設計パターン: Amazon S3 パフォーマンスの最適化」を参照してください。

リソースの制限

Athena は分散クエリエンジンでクエリを実行します。クエリを送信すると、Athena エンジンのクエリプランナーはクエリを実行するのに必要な計算能力を見積もり、それに応じて計算ノードのクラスターを準備します。DDL クエリなどの一部のクエリは、1 つのノードでのみ実行されます。大規模なデータセットに対する複雑なクエリでは、はるかに大きなクラスターで実行されます。ノードは均一であり、メモリ、CPU、およびディスク構成は同じです。Athena は、より要求の厳しいクエリを処理するためにスケールアップするのではなく、スケールアウトします。

クエリの要求は、クエリを実行するクラスターで使用可能なリソースを超えることがあります。この場合にはクエリは失敗し、「このスケールファクターでリソースを使い果たしました」というエラーが表示されます。

最も一般的に消費されるリソースはメモリですが、まれにディスク容量になる場合もあります。メモリエラーは通常、エンジンが結合またはウィンドウ関数を実行するときに発生しますが、カウントや集計が異なる場合もあります。

クエリが「リソース不足」エラーで一度失敗した場合でも、もう一度実行すると成功する可能性があります。クエリの実行は確定的ではありません。データの読み込みにかかる時間や中間データセットがノードにどのように分散されるかなどの要因により、リソースの使用量が異なる場合があります。たとえば、2 つのテーブルを結合するクエリで、結合条件の値の分布に大きな偏りがあるとします。このようなクエリはほぼ成功しますが、最も一般的な値が同じノードで処理されてしまうと失敗することがあります。

クエリが利用可能なリソースを超えないようにするには、このドキュメントに記載されているパフォーマンスチューニングのヒントを参考にしてください。特に、使用可能なリソースを使い果たすクエリを最適化する方法のヒントについては、「結合の最適化」、「ウィンドウ機能の最適化」、および「近似を使用したクエリの最適化」を参照してください

クエリを最適化するテクニック

このセクションで説明するクエリ最適化手法を使用して、クエリの実行を高速化したり、Athena のリソース制限を超えるクエリの回避策として使用します。

結合の最適化

分散クエリエンジンで結合を実行するには、さまざまな方法があります。最も一般的なのは、分散ハッシュ結合と複雑な結合条件のクエリの 2 つです。

分散型ハッシュ結合

最も一般的なタイプの結合では、結合条件として等価比較を使用します。Athena はこのタイプの結合を分散ハッシュ結合として実行します。

分散型ハッシュ結合では、エンジンは結合の片側からルックアップテーブル (ハッシュテーブル) を作成します。この側はビルド側と呼ばれます。ビルド側のレコードはノード全体に分散されます。各ノードは、そのサブセットのルックアップテーブルを作成します。結合のもう一方の側 (プローブ側と呼ばれる) がノードを介してストリーミングされます。プローブ側のレコードは、ビルド側と同じ方法でノードに分散されます。これにより、各ノードは独自の検索テーブルで一致するレコードを検索して結合を実行できます。

結合のビルド側から作成されたルックアップテーブルがメモリに収まらない場合、クエリが失敗する可能性があります。ビルド側の合計サイズが利用可能なメモリよりも小さい場合でも、レコードの分布に大きな偏りがあると、クエリが失敗する可能性があります。極端なケースでは、すべてのレコードの結合条件の値が同じで、1 つのノードのメモリに収まる必要がある場合があります。スキューの少ないクエリでも、値のセットが同じノードに送信され、その値の合計が使用可能なメモリを超えると、失敗する可能性があります。ノードにはレコードをディスクにスピルする機能がありますが、スピルするとクエリの実行が遅くなり、クエリの失敗を防ぐには不十分である場合があります。

Athena は、大きい方のリレーションをプローブ側に、小さい方のリレーションをビルド側として使用するように結合の順序を変更しようとします。ただし、Athena はテーブル内のデータを管理しないため、情報が限られており、多くの場合、最初のテーブルが大きく、2 番目のテーブルが小さいと想定する必要があります。

等価ベースの結合条件で結合を記述する場合、JOIN キーワードの左側の表がプローブ側、右側の表がビルド側であると仮定します。正しいテーブル (ビルド側) がテーブルのうち小さい方であることを確認してください。結合のビルド側をメモリに収まるほど小さくできない場合は、ビルドテーブルのサブセットを結合する複数のクエリを実行することを検討してください。

その他の結合型

結合条件が複雑なクエリ (LIKE>、または他の演算子を使用するクエリなど) では、多くの場合、計算負荷が高くなります。最悪の場合には、結合の片側のすべてのレコードを、結合の反対側のすべてのレコードと比較する必要があります。実行時間はレコード数の 2 乗に比例して長くなるため、このようなクエリには最大実行時間を超えるリスクがあります。

Athena がクエリをどのように実行するかを事前に確認するには、EXPLAIN ステートメントを使用できます。詳細については、Athena での EXPLAIN および EXPLAIN ANALYZE の使用およびAthena EXPLAIN ステートメントの結果についてを参照してください。

ウィンドウ機能の最適化

ウィンドウ関数はリソースを大量に消費する操作であるため、クエリの実行が遅くなったり、「このスケールファクターではリソースを使い果たしました」というメッセージが表示され、クエリが失敗したりする可能性があります。ウィンドウ関数は、結果を計算するために操作したすべてのレコードをメモリに保持します。ウィンドウが非常に大きい場合、ウィンドウ関数のメモリが不足する可能性があります。

クエリが使用可能なメモリ制限内で実行されるように、ウィンドウ関数が処理するウィンドウのサイズを小さくしてください。そのようにするためには、PARTITIONED BY 句を追加するか、既存の分割節の範囲を絞り込めます。

ウィンドウ以外の関数を代用する

ウィンドウ関数を含むクエリは、ウィンドウ関数なしで書き直せる場合があります。たとえば、row_number を使用して上位 N レコードを検索する代わりに、ORDER BY および LIMIT を使用できます。row_number または rank を使用してレコードの重複排除を行う代わりに、max_bymin_by任意 などの集計関数を使用できます。

たとえば、センサーからの更新を含むデータセットがあるとします。センサーは定期的にバッテリーの状態を報告し、位置情報などのメタデータを含みます。各センサーの最新のバッテリー状態とその位置を知りたい場合は、次のクエリを使用できます。

SELECT sensor_id, arbitrary(location) AS location, max_by(battery_status, updated_at) AS battery_status FROM sensor_readings GROUP BY sensor_id

位置などのメタデータはすべてのレコードで同じなので、arbitrary 関数を使用してグループから任意の値を選択できます。

max_by 関数を使用すると、最新のバッテリー状態を取得できます。max_by 関数は、別の列の最大値が見つかったレコードから列の値を選択します。この場合、グループ内の最終更新時刻を含むレコードのバッテリーステータスを返します。このクエリは、ウィンドウ関数を使用する同等のクエリよりも実行速度が速く、メモリ使用量も少なくて済みます。

集約の最適化

Athena が集約を実行すると、GROUP BY 句内の列を使用してレコードがワーカーノード全体に分散されます。レコードをグループと照合するタスクを可能な限り効率的に行うために、ノードはレコードをメモリに保持し、必要に応じてディスクに書き出そうとします。

GROUP BY 句に重複する列を含めないようにするのも良い考えです。列の数が少ないと必要なメモリも少なくなるため、使用する列の数が少ないグループを記述するクエリの方が効率的です。また、数値列は文字列よりもメモリ使用量が少なくなります。たとえば、数値のカテゴリ ID とカテゴリ名の両方を持つデータセットを集約する場合、GROUP BY 句にはカテゴリ ID 列のみを使用してください。

場合によっては、列が GROUP BY 句または集計式の一部である必要があるという事実を回避するために、クエリの GROUP BY 句に列が含まれることがあります。このルールに従わない場合、次のようなエラーメッセージが表示されることがあります。

EXPRESSION_NOT_AGGREGATE: 1:8 行目:「カテゴリ」は集合式であるか、GROUP BY 句に含まれている必要があります

GROUP BY 句に余分な列を追加しなくても済むように、次の例のように任意の関数を使用できます。

SELECT country_id, arbitrary(country_name) AS country_name, COUNT(*) AS city_count FROM world_cities GROUP BY country_id

ARBITRARY 関数はグループから任意の値を返します。この関数は、グループ内のすべてのレコードが 1 つの列に対して同じ値を持つことは把握しているがその値がグループを識別できない場合に便利です。

上位 N のクエリの最適化

ORDER BY 句は、クエリの結果をソートされた順序で返します。Athena は分散ソートを使用して、ソート操作を複数のノードで並行して実行します。

結果を厳密にソートする必要がない場合は、ORDER BY 句を追加しないでください。また、必ずしも必要ではない場合は、内部クエリへの ORDER BY の追加は避けてください。多くの場合、クエリプランナーは冗長なソートを削除できますが、これは保証されていません。このルールの例外は、内部クエリが上位 N の操作 (最新の N の値、または最も一般的な Nの値を検索するなど) を実行している場合です。

Athena が ORDER BY を LIMIT を使用して見た場合、実行されている N クエリが上位のクエリであることを認識し、それに応じて専用の操作を行います。

注記

Athena では、上位の N を使用する row_number のようなウィンドウ関数も多くの場合検出できますが、ORDER BY と LIMIT を使用するより単純なバージョンをお勧めします。詳細については、「ウィンドウ機能の最適化」を参照してください。

必要な列のみを含める

列が必ずしも必要ではない場合は、クエリに含めないでください。クエリが処理しなければならないデータが少ないほど、実行速度は速くなります。これにより、必要なメモリ量と、ノード間で送信する必要のあるデータ量の両方が削減されます。列形式のファイル形式を使用している場合、列の数を減らすと、Amazon S3 から読み取られるデータの量も減ります。

Athena には結果の列数に特別な制限はありませんが、クエリの実行方法によって、可能な列の合計サイズが制限されます。列を合わせたサイズには、名前とタイプが含まれます。

たとえば、次のエラーは、リレーションがリレーション記述子のサイズ制限を超えるために発生します。

GENERIC_INTERNAL_ERROR: io.airlift.bytecode.CompilationException

この問題を回避するには、クエリ内の列の数を減らす、またはサブクエリを作成して、より少ない量のデータを取得する JOIN を使用します。一番外側のクエリで SELECT * を実行するクエリがある場合は、* を必要な列のみのリストに変更する必要があります。

近似を使用したクエリの最適化

Athena では、近似集計関数をサポートしており、個別値、最も頻度の高い値、パーセンタイル (近似中央値を含む) のカウント、およびヒストグラムの作成を行えます。これらの関数は、正確な値が不要な場合に使用してください。

COUNT(DISTINCT col) 操作とは異なり、approx_distinct はメモリ使用量がはるかに少なく、実行速度も速くなります。同様に、ヒストグラムの代わりに numeric_histogram を使用すると、近似法を使用するため、メモリ使用量が少なくなります。

LIKE の最適化

LIKE を使用して一致する文字列を検索できますが、文字列が長い場合は計算量が多くなります。regexp_like 関数は、ほとんどの場合、より高速な代替手段であり、柔軟性にも優れています。

多くの場合、探している部分的な文字列を固定することで検索を最適化できます。たとえば、プレフィックスを探している場合は、'% substr%' の代わりに 'substr %' を使用する方がはるかに優れています。または regexp_like、「^ substr」を使用している場合。

UNION の代わりに UNION ALL を使用する

UNION ALL と UNION は、2 つのクエリの結果を 1 つの結果にまとめる 2 つの方法です。 UNION ALL は最初のクエリのレコードを 2 番目のクエリと連結し、UNION は同じ処理を実行しますが、重複も削除します。UNION ではすべてのレコードを処理して重複を見つける必要があり、メモリと計算を大量に消費しますが、UNION ALL は比較的高速な操作です。レコードの重複排除が必要でない限り、UNION ALL はベストパフォーマンスを実現するために使用してください。

大きな結果セットには UNLOAD を使用する

クエリの結果が大きくなることが予想される場合 (たとえば、数万行以上など)、UNLOAD を使用して結果をエクスポートします。ほとんどの場合、これは通常のクエリを実行するよりも高速です。また、UNLOAD を使用すると、出力をより細かく制御できます。

クエリの実行が終了すると、Athena は結果を 1 つの非圧縮 CSV ファイルとして Amazon S3 に保存します。結果が圧縮されないだけでなく、操作を並列化できないため、UNLOAD よりも時間がかかります。これとは対照的に、UNLOAD は結果をワーカーノードから直接書き込み、計算クラスターの並列処理を最大限に活用します。さらに、結果を圧縮形式や JSON や Parquet などの他のファイル形式で書き込むように UNLOAD を構成できます。

詳細については、「UNLOAD」を参照してください。

CTAS または Glue ETL を使用して、頻繁に使用する集計をマテリアライズする

クエリの「マテリアライズ」とは、事前に計算された複雑なクエリ結果 (集計や結合など) を保存して後続のクエリで再利用することで、クエリのパフォーマンスを向上させる方法です。

クエリの多くに同じ結合や集計が含まれている場合は、共通のサブクエリを新しいテーブルとして作成し、そのテーブルに対してクエリを実行できます。クエリ結果からのテーブルの作成 (CTAS)、または Glue ETL などの専用 ETL ツールを使用して新しいテーブルを作成できます。

たとえば、オーダーデータセットのさまざまな側面を表示するウィジェットを含むダッシュボードがあるとします。各ウィジェットには独自のクエリがありますが、クエリはすべて同じ結合とフィルターを共有します。注文テーブルは品目テーブルと結合され、過去 3 か月のみ表示するフィルターがあります。これらのクエリに共通する機能がわかれば、ウィジェットが使用できる新しいテーブルを作成できます。これにより、重複が減り、パフォーマンスが向上します。欠点は、新しいテーブルを最新の状態に保つ必要があることです。

クエリ結果の再利用

同じクエリが短期間に複数回実行されることはよくあります。たとえば、複数のユーザーが同じデータダッシュボードを開いたときに発生する可能性があります。クエリを実行するときに、以前に計算した結果を再利用するよう Athena に指示できます。再利用できる結果の最大保管期間を指定します。同じクエリがその時間枠内で以前に実行された場合、Athena はクエリを再実行する代わりにそれらの結果を返します。詳細については、「Amazon Athena ユーザーガイド」の クエリの結果を再利用する、「AWS ビッグデータブログ」の「Amazon Athena クエリ結果の再利用によるコスト削減とクエリパフォーマンスの向上」を参照してください。

データを最適化するテクニック

パフォーマンスはクエリだけでなく、データセットの構成方法や使用するファイル形式と圧縮にも大きく依存します。

データのパーティション化

パーティショニングを行うと、テーブルが複数部分に分割され、日付、国、地域などのプロパティに基づいて関連データがまとめられます。パーティションキーは仮想列として機能します。パーティションキーはテーブルの作成時に定義し、クエリのフィルタリングに使用します。パーティションキー列をフィルタリングすると、一致するパーティションのデータのみが読み取られます。たとえば、データセットが日付で分割されていて、クエリに先週のみ一致するフィルターが設定されている場合、先週のデータのみが読み取られます。パーティショニングについての詳細は、「Athena でのデータのパーティション化」を参照してください。

クエリをサポートするパーティションキーを選択する

パーティショニングはクエリのパフォーマンスに大きな影響を与えるため、データセットとテーブルを設計するときは、パーティションの分割方法を慎重に検討してください。パーティションキーが多すぎると、データセットが断片化し、ファイルが多すぎたり、ファイルが小さすぎたりする可能性があります。逆に、パーティションキーが少なすぎたり、パーティショニングがまったく行われなかったりすると、クエリが必要以上に多くのデータをスキャンすることになります。

まれなクエリの最適化は避ける

最もよく使われるクエリを最適化し、まれであるクエリには最適化しないようにするのが良い戦略です。たとえば、クエリが日単位の期間を対象としている場合は、一部のクエリがそのレベルに絞り込まれていても、時間単位で分割しないでください。データに詳細なタイムスタンプ列がある場合、時間別にフィルタリングするまれなクエリではタイムスタンプ列を使用できます。まれなケースで必要以上のデータをスキャンしたとしても、まれなケースという理由で全体的なパフォーマンスを低下させることは、通常は良いトレードオフとは言えません。

クエリでスキャンするデータ量を減らしてパフォーマンスを向上させるには、列指向のファイル形式を使用し、レコードをソートしたままにしてください。時間ごとに分割する代わりに、レコードをタイムスタンプでソートしておきます。短い時間枠でのクエリでは、タイムスタンプによるソートは、時間ごとの分割とほぼ同じくらい効率的です。さらに、通常はタイムスタンプでソートしても、日単位でカウントされたタイムウィンドウでのクエリのパフォーマンスが低下することはありません。詳細については、「列指向ファイルフォーマットを使用する」を参照してください。

数万のパーティションを含むテーブルでのクエリは、すべてのパーティションキーに述語がある方がパフォーマンスが向上することに注意してください。これが、最も一般的なクエリ用にパーティションスキームを設計するもう一つの理由です。詳細については、「等式によるパーティションのクエリ」を参照してください。

パーティション射影を使用する

パーティション射影は Athena の機能の 1 つであり、パーティション情報を AWS Glue Data Catalog には格納せず、AWS Glue のテーブルのプロパティにルールとして格納します。Athena は、パーティション射影が設定されたテーブルに対してクエリを計画する際、テーブルのパーティション射影ルールを読み取ります。Athena は、AWS Glue Data Catalog でパーティションを検索する代わりに、クエリとルールに基づいてメモリに読み込むパーティションを計算します。

パーティション射影は、パーティション管理を簡素化するだけでなく、多数のパーティションを持つデータセットのパフォーマンスを向上させることができます。クエリにパーティションキーの特定の値の代わりに範囲が含まれている場合、カタログで一致するパーティションを検索すると、パーティションの数が多いほど時間がかかります。パーティション射影を使用すると、カタログにアクセスせずにフィルターをメモリ内で計算でき、はるかに高速になります。

特定の状況では、パーティション射影によってパフォーマンスが低下する可能性があります。一例として、テーブルが「スパースな」場合が挙げられます。スパーステーブルには、パーティション射影の設定で記述されたパーティションキー値のすべての順列に関するデータはありません。スパーステーブルでは、クエリから計算されたパーティションセットとパーティションプロジェクション設定は、データがない場合でもすべて Amazon S3 に一覧表示されます。

パーティション射影を使用する場合は、必ずすべてのパーティションキーに述語を含めてください。Amazon S3 の一覧表示が不要にならないように、指定できる値の範囲を絞り込んでください。100 万の値の範囲を持つパーティションキーと、そのパーティションキーにフィルターがないクエリを想像してください。クエリを実行するには、Athena は少なくとも 100 万回の Amazon S3 リストオペレーションを実行する必要があります。パーティション射影を使用するか、カタログにパーティション情報を保管するかに関係なく、特定の値に対してクエリを実行すると、クエリは最も速くなります。詳細については、「等式によるパーティションのクエリ」を参照してください。

パーティション射影用のテーブルを設定するときは、指定する範囲が適切であることを確認してください。クエリにパーティションキーの述語が含まれていない場合は、そのキーの範囲内のすべての値が使用されます。データセットが特定の日付に作成された場合は、その日付を任意の日付範囲の開始点として使用します。終了日の範囲として NOW を使用します。値の数が多い数値範囲は避け、代わりに注入された型の使用を検討してください。

パーティション射影の詳細については、「Amazon Athena でのパーティション射影」を参照してください。

パーティションインデックスの使用

パーティションインデックスは、多数のパーティションを持つテーブルのパーティションルックアップのパフォーマンスを向上させる AWS Glue Data Catalog の機能です。

カタログ内のパーティションのリストは、リレーショナルデータベースのテーブルのようなものです。このテーブルには、パーティションキー用の列と、パーティションの場所用の別の列があります。分割テーブルをクエリすると、このテーブルをスキャンしてパーティションの場所が検索されます。

リレーショナルデータベースと同様に、インデックスを追加することでクエリのパフォーマンスを向上できます。複数のインデックスを追加して、さまざまなクエリパターンをサポートできます。AWS Glue Data Catalog パーティションインデックスは、>>= などの等価演算子と比較演算子の両方をサポートし、< は AND 演算子を結合します。詳細については、「AWS Glue 開発者ガイド」の「AWS Glue でのパーティションインデックスの使用」と、「AWS ビッグデータブログ」の「AWS Glue Data Catalog パーティションインデックスを使用して Amazon Athena クエリのパフォーマンスを向上させるETL ジョブを構成するには」を参照してください。

パーティションキーのタイプとして常に STRING を使用する

パーティションキーをクエリする場合、Athena ではパーティションフィルタリングを AWS Glue にプッシュダウンするためにパーティションキーのタイプ STRING が必要であることを覚えておいてください。パーティションの数が少なくない場合は、他のタイプを使用するとパフォーマンスが低下する可能性があります。パーティションキーの値が日付型または数値型の場合は、クエリ内の適切な型にキャストしてください。

古いパーティションや空のパーティションを削除する

Amazon S3 のパーティションから (Amazon S3 ライフサイクルを使用するなどして) データを削除する場合は、AWS Glue Data Catalog からそのパーティションエントリも削除する必要があります。クエリのプランニング中、クエリに一致するパーティションはすべて Amazon S3 に一覧表示されます。空のパーティションが多数ある場合、それらのパーティションを一覧表示することによるオーバーヘッドは悪影響をおよぼす可能性があります。

また、パーティションが何千もある場合は、不要になった古いデータのパーティションメタデータを削除することを検討してください。たとえば、クエリで 1 年以上前のデータが検索されない場合、古いパーティションのパーティションメタデータを定期的に削除できます。パーティションの数が数万に増えた場合、未使用のパーティションを削除すると、すべてのパーティションキーに述語が含まれていないクエリを高速化できます。クエリにすべてのパーティションキーに述語を含める方法については、「等式によるパーティションのクエリ」を参照してください。

等式によるパーティションのクエリ

すべてのパーティションキーに等価述語を含むクエリでは、パーティションメタデータを直接読み込めるため、実行速度が速くなります。1 つ以上のパーティションキーに述語がない場合や、述語で値の範囲を選択するクエリは避けてください。このようなクエリでは、すべてのパーティションのリストをフィルタリングして、一致する値を見つける必要があります。ほとんどのテーブルではオーバーヘッドは最小限ですが、パーティションが数万以上あるテーブルではオーバーヘッドが大きくなる可能性があります。

クエリを書き直してパーティションを等値でフィルタリングできない場合は、パーティション射影を試してみてください。詳細については、「パーティション射影を使用する」を参照してください。

パーティションのメンテナンスに MSCK REPAIR TABLE を使用しないでください

MSCK REPAIR TABLE は実行に時間がかかる場合があり、新しいパーティションを追加するだけで、古いパーティションは削除されないため、パーティションを管理する効率的な方法ではありません (「考慮事項と制約事項」を参照)。

パーティションは、AWS Glue Data Catalog APIALTER TABLE ADD PARTITION またはAWS Glueクローラーを使用して手動で管理する方が適切です。別の方法として、パーティション射影を使用できます。これにより、パーティションを完全に管理する必要がなくなります。詳細については、「Amazon Athena でのパーティション射影」を参照してください。

クエリがパーティションスキームと互換性があることを確認してください。

クエリがどのパーティションをスキャンするかは、EXPLAIN ステートメントを使用して事前に確認できます。クエリの前に EXPLAIN キーワードを付けてから、EXPLAIN 出力の下部にある各テーブルのソースフラグメント(たとえば、Fragment 2 [SOURCE])を探します。右側がパーティションキーとして定義されている割り当てを探してください。下の行には、クエリの実行時にスキャンされるパーティションキーのすべての値のリストが含まれています。

たとえば、dt パーティションキーを含むテーブルに対してクエリを実行し、EXPLAIN のクエリのプレフィックスを付けたとします。クエリ内の値が日付で、フィルターで 3 日間の範囲を選択した場合、EXPLAIN 出力は次のようになります。

dt := dt:string:PARTITION_KEY :: [[2023-06-11], [2023-06-12], [2023-06-13]]

EXPLAIN 出力は、プランナーがクエリと一致するこのパーティションキーの値を 3 つ見つけたことを示しています。また、それらの値が何であるかも表示されます。EXPLAIN の使用の詳細については、「Athena での EXPLAIN および EXPLAIN ANALYZE の使用」と「Athena EXPLAIN ステートメントの結果について」を参照してください。

列指向ファイルフォーマットを使用する

Parquet や ORC のような列指向のファイル形式は、分散分析ワークロード向けに設計されています。データを行別ではなく列別に整理します。データを列形式で整理することには、次のような利点があります。

  • クエリに必要な列のみが読み込まれる

  • ロードする必要のあるデータの総量が減る

  • 列の値はまとめて保存されるため、データを効率的に圧縮できる

  • ファイルには、エンジンが不要なデータのロードをスキップできるようにするメタデータを含められる

ファイルメタデータの使用方法の例として、ファイルメタデータには、データページの最小値と最大値に関する情報を含められます。クエリされた値がメタデータに記載されている範囲にない場合は、ページをスキップできます。

このメタデータを使用してパフォーマンスを向上させる方法の 1 つは、ファイル内のデータを確実にソートすることです。たとえば、created_at エントリが短期間にあるレコードを検索するクエリがあるとします。created_at データが列でソートされている場合、Athena はファイルメタデータの最小値と最大値を使用して、データファイルの不要な部分をスキップできます。

列指向のファイル形式を使用する場合は、ファイルが小さすぎないことを確認してください。ファイルが多すぎにならないようにする で説明したように、小さなファイルが多いデータセットはパフォーマンスの問題を引き起こします。これは特に列指向ファイル形式の場合に当てはまります。小さなファイルの場合、列指向ファイル形式のオーバーヘッドの方がメリットよりも重要です。

Parquet と ORC は内部的には行グループ (Parquet) とストライプ (ORC) で整理されていることに注意してください。行グループのデフォルトサイズは 128 MB、デフォルトのストライプサイズは 64 MB です。列が多い場合は、行グループとストライプサイズを大きくしてパフォーマンスを向上できます。行グループまたはストライプサイズをデフォルト値より小さくすることはお勧めしません。

他のデータ形式を Parquet または ORC に変換するには、AWS Glue ETL または Athena を使用できます。ETL の Athena の使用に関する詳細については、「ETL およびデータ分析での CTAS および INSERT INTO の使用」を参照してください。

データを圧縮する

Athena は幅広い圧縮形式をサポートしています。圧縮データのクエリは、解凍前にスキャンされたバイト数に応じて支払われるため、高速かつ安価です。

gzip 形式は圧縮率が高く、他のツールやサービスを幅広くサポートしています。zstd (Zstandard) 形式は、パフォーマンスと圧縮率のバランスが取れた新しい圧縮形式です。

JSON や CSV データなどのテキストファイルを圧縮するときは、ファイル数とファイルサイズのバランスをとるようにしてください。ほとんどの圧縮形式では、リーダーはファイルを最初から読み取る必要があります。つまり、圧縮されたテキストファイルは一般的に並行して処理できません。大きな非圧縮ファイルは、クエリ処理中の並列性を高めるためにワーカー間で分割されることがよくありますが、これはほとんどの圧縮形式では不可能です。

ファイルが多すぎにならないようにする で説明したように、ファイルが多すぎたり少なすぎたりしない方がよいでしょう。ファイル数によってクエリを処理できるワーカーの数が制限されるため、このルールは圧縮ファイルの場合に特に当てはまります。

Athena での圧縮の使用の詳細については、「Athena での圧縮のサポート」を参照してください。

カーディナリティが高いキーの検索にはバケットを使用する

バケット処理とは、いずれかの列の値に基づいてレコードを個別のファイルに分散する手法です。これにより、同じ値を持つすべてのレコードが同じファイルにあることが保証されます。バケット化は、カーディナリティの高いキーがあり、クエリの多くがそのキーの特定の値を検索する場合に役立ちます。

たとえば、特定のユーザーのレコードセットをクエリするとします。データがユーザー ID 別にまとめられている場合、Athena は特定の ID のレコードを含むファイルと含まないファイルを事前に把握します。これにより、Athena はその ID を含められるファイルのみを読み取れるため、読み取るデータ量を大幅に削減できます。また、特定の ID のデータを検索するために必要となる計算時間も短縮されます。

バケット化のデメリット

クエリでデータがバケット化されている列で複数の値を頻繁に検索する場合、バケット化の価値は低くなります。クエリする値が多いほど、すべて、またはほとんどのファイルを読み取らなければならない可能性が高くなります。たとえば、バケットが 3 つあり、クエリが 3 つの異なる値を検索する場合、すべてのファイルを読み取る必要がある場合があります。バケット処理は、クエリが単一の値を検索するときに最も効果的です。

詳細については、「Athena におけるパーティション化とバケット化」を参照してください。

ファイルが多すぎにならないようにする

データセットが多数の小さなファイルで構成されていると、全体的なクエリパフォーマンスが低下します。Athena がクエリを計画すると、すべてのパーティションの場所が一覧表示されるため、時間がかかります。各ファイルの処理と要求には、計算上のオーバーヘッドもあります。したがって、Amazon S3 から 1 つの大きなファイルをロードする方が、多数の小さいファイルから同じレコードをロードするよりも高速です。

極端なケースでは、Amazon S3 のサービス制限が発生する可能性があります。Amazon S3 は、1 つのインデックスパーティションに対して 1 秒あたり最大 5,500 の要求をサポートします。最初は、バケットは単一のインデックスパーティションとして扱われますが、リクエストの負荷が増えると、複数のインデックスパーティションに分割できます。

Amazon S3 はリクエストパターンを調べ、キープレフィックスに基づいて分割します。データセットが何千ものファイルで構成されている場合、Athena からのリクエストはリクエストクォータを超える可能性があります。ファイルが少なくても、同じデータセットに対して複数のクエリを同時に実行すると、クォータを超える可能性があります。同じファイルにアクセスする他のアプリケーションが、リクエストの総数に影響する可能性があります。

リクエストレート limit を超えると、Amazon S3 は次のエラーを返します。このエラーは Athena のクエリのステータス情報に含まれています。

SlowDown: リクエスト率を下げてください

トラブルシューティングを行うには、まず、エラーの原因が単一のクエリなのか、同じファイルを読み取る複数のクエリなのかを判断します。後者の場合は、同時に実行されないようにクエリの実行を調整します。これを実現するには、アプリケーションにキューイングメカニズムを追加するか、リトライを行う必要があります。

1 つのクエリを実行してもエラーが発生する場合は、データファイルを組み合わせるか、クエリを変更して読み込むファイルの数を減らしてみてください。小さなファイルを結合する最適なタイミングは、ファイルが書き込まれる前です。そのためには、以下の方法を検討してください。

  • ファイルを書き込むプロセスを変更して、より大きなファイルを書き込んでください。たとえば、レコードが書き込まれる前にレコードを長時間バッファリングできます。

  • Amazon S3 上の場所にファイルを置き、Glue ETL などのツールを使用してファイルをより大きなファイルにまとめます。次に、サイズの大きいファイルをテーブルが指す場所に移動します。詳細については、「AWS Glue デベロッパーガイド」の「大きなグループの入力ファイルの読み込み」か、および「AWS re:Post ナレッジセンター」の「より大きなファイルを出力するように AWS Glue ETL ジョブを構成するには」を参照してください。

  • パーティションキーの数を減らしてください。パーティションキーが多すぎると、各パーティションのレコード数が少なくなり、小さなファイルが多すぎることがあります。作成するパーティションの決定については、「クエリをサポートするパーティションキーを選択する」を参照してください。

パーティションの外部にストレージ階層を追加することは避けてください

クエリプランニングのオーバーヘッドを回避するには、ファイルを各パーティションの場所にフラット構造で格納します。追加のディレクトリ階層は使用しないでください。

Athena がクエリを計画すると、クエリに一致するすべてのパーティション内のすべてのファイルが一覧表示されます。Amazon S3 自体にはディレクトリはありませんが、慣例として / スラッシュはディレクトリ区切り文字として解釈されます。Athena がパーティションの場所を一覧表示すると、見つかったすべてのディレクトリが再帰的に一覧表示されます。パーティション内のファイルを 1 つの階層にまとめると、リストが複数回表示されます。

すべてのファイルが直接パーティションの場所にある場合は、ほとんどの場合、リスト操作を 1 回実行するのみで済みます。ただし、Amazon S3 はリストオペレーションごとに 1000 個のオブジェクトしか返さないため、パーティションに 1000 を超えるファイルがある場合は、複数の連続リストオペレーションが必要です。また、1 つのパーティションに 1000 個を超えるファイルがあると、さらに深刻なパフォーマンスの問題が発生する可能性があります。詳細については、「ファイルが多すぎにならないようにする」を参照してください。

SymlinkTextInputFormat は必要な場合にのみ使用する

SymlinkTextInputFormat テクニックを使用すると、テーブルのファイルがパーティションにきちんと整理されていない状況を回避できます。たとえば、シンボリックリンクは、すべてのファイルが同じプレフィックスにある場合や、スキーマの異なるファイルが同じ場所にある場合に役立ちます。

ただし、シンボリックリンクを使用すると、クエリ実行に間接的なレベルが追加されます。これらのレベルのインダイレクションは、全体的なパフォーマンスに影響します。シンボリックリンク ファイルを読み取り、ファイルが定義する場所をリストする必要があります。これにより、通常の Hive テーブルでは不要な複数のラウンドトリップが Amazon S3 に追加されます。結論として、ファイルの再編成などのより適切なオプションが利用できない場合にのみ SymlinkTextInputFormat を使用してください。

追加リソース

Athena でのパフォーマンスチューニングの詳細については、次のリソースを参照してください。