データを最適化する - Amazon 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 を使用できます。詳細については、「列形式に変換する」を参照してください。

データを圧縮する

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

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

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

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

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

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

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

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

クエリが列内の複数の値を頻繁に検索する場合は、バケット化を避ける

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

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

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

データセットが多数の小さなファイルで構成されていると、全体的なクエリパフォーマンスが低下します。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 を使用してください。