About connecting the dots.

data science related trivial things

Amazon Athena の Query Result Reuse で同じクエリの結果を高速に取得する

この記事は,AWS Analytics Advent Calendar 2022 の 5 日目の記事になります.

qiita.com

11/8 に Amazon Athena が Query Result Reuse をサポートしました.この機能,要するにクエリ結果のキャッシュが使えるようになったとのことで,似たようなクエリを何度も試すような場合には,結果を高速に取得できるようになります.この記事では,実際にいくつかのパターンで利用して,どのくらい効果が出るかを確認できればと思います.

aws.amazon.com

試してみる

集計データ

まずはマネコン上のクエリエディタから,実際にクエリを投げてみたいと思います.ちなみにこの機能は Athena のバージョン 3 エンジンでのみ使用可能なため,古いバージョンのエンジンを使っている方は,マネコンの左側メニュー Workgroups から,エンジンバージョンを最新の 3 に変更しておきましょう.今回使用したのは手元のテストデータで,5GB ほどのものになります.このくらいだとあまりキャッシュの恩恵はないというのはおいておくとして...

select
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
    , count(1)
    , sum(amount_sold)
from
    sales
    , customers
    , times
where
    sales.cust_id = customers.cust_id
    and sales.time_id = times.time_id
group by
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
order by
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
;

クエリを実行した結果は次のとおりです.データスキャン量が 5GB で,実行時間が 15 秒程度となっています.このデータは GZip 圧縮のものだったので,多少オーバーヘッドが出ており時間がかかっています.Parquet フォーマットならもっと早いかとは思います.

さて,ここで画面に Reuse query results というメニューがあるのが確認できるかと思います.こちらがすでにオンになっているので,同じクエリをもう一度実行すると,今度はキャッシュが使用されるはずです.結果は以下の通り,"Completed - using resused query results" と表示されており,実際にスキャンされたデータ量が "-" となっていることも確認できます.結果は 274ms ということで,結果を取ってくるぶんの時間だけですね.


生データそのまま

それでは次に,大きなデータを吐き出してみます.Athena は大規模データをそのまま持ってくるためのものではないので,結構時間がかかります.正確には,CTAS 等でテーブルとして書き出すのであれば高速に動作しますが,クエリ結果として取得する場合は 1 つの csv にまとめる形になるため,並列処理が働かず,結果としてファイルの書き出し部分がボトルネックとなって重くなってしまいます.これは大規模分散データ処理の宿命なので,仕方ないところではありますが.

select
    *
from
    sales
;

得られた結果は以下のとおりです.スキャン量は 4 GB 弱ですが,実行に 15 分以上かかりました.となりのタブにある統計情報を見てみると,全部でレコード数が 1.5 億弱行で,圧縮されてない元データサイズは 16.92GB だったことがわかります.

それでは,これをキャッシュを使ってもう一度実行してみるとどうなるでしょうか.当然ですが 229ms で結果が完了します.すばらしいですね.ただ今回はクエリエディタ上からの実行で,先頭の一部の行の結果しか取ってきてないため高速ですが,例えば CLI で結果を丸ごと取ってくる場合には,10GB を超えるデータをもってくるぶんの時間はかかると思われます.


キャッシュ時間の設定

キャッシュ再利用時間の設定は,コンソールの Reuse query results のところから設定でき,1 分から 7 日間までの間で選択することができます.このあたりは,クエリ対象データの性質に応じて適宜変えると良いでしょう.このキャッシュ再利用時間設定は,クエリ実行を行う毎に別の値を用いることができます.例えば boto3 経由でクエリ実行する場合には,下記の ResultReuseByAgeConfiguration を ResultReuseConfiguration オプションで設定することが可能です.

import boto3

client = boto3.client('athena')
response = client.start_query_execution(
    WorkGroup='my_work_group',
    QueryString='SELECT * FROM my_table LIMIT 10',
    ResultReuseConfiguration={
        'ResultReuseByAgeConfiguration': {
   	    	'Enabled': True,
     		'MaxAgeInMinutes': 60
        }
    }
)

AWS CLI でも同様に指定可能です.

aws athena start-query-execution \
  --work-group "my_work_group" \
  --query-string "SELECT * FROM my_table LIMIT 10" \
  --result-reuse-configuration \
    "ResultReuseByAgeConfiguration={Enabled=true,MaxAgeInMinutes=60}"

キャッシュされたデータとその挙動

置かれている場所

さて,このキャッシュされたデータはどこに置かれているのでしょうか.Athena のドキュメントには OutputLocation が条件として書かれているので,普通に考えれば S3 に置かれた結果ファイルを再利用していると想像されます.では,実際にそちらを確認してみましょう.下記のように,Unsaved/YYYY/MM/DD というパスでクエリの実行結果が保存されています.

この結果が実際に使われているかを確かめるために,データを他の場所に移動してみましょう.まずはデータ本体から.

この状態でクエリを再度実行すると,キャッシュが使われることなく,スキャンが走ります.ということで,少なくとも現状では,やはりクエリ結果の出力フォルダにあるデータがキャッシュとして使われていると考えて良さそうです.もちろん実ワークロードで使う際には,ここのファイルを安易に消せるような権限設定にするべきではないわけですが.

ちなみにメタデータ側を削除すると,"using reused query results" は表示されるのに,結果は取得できないという状況になります.


発動条件について

キャッシュ再利用を発動させるためには,ドキュメントに書かれている内容が必要とのことです.特に気になるのは `The query string is an exact match.` とのことで,同一クエリでも半角スペースが一つ入ったり,コメントが入っていたらどうなるのかが気になります.ということで,こちらも検証してみました.

以下のように,2 行目に半角スペースを一つ追加してみたところ,書かれている通りキャッシュは使われず再実行が走りました.

select
     calendar_year
    , calendar_month_number
    , country_id
    , cust_city
    , count(1)
    , sum(amount_sold)
from
    sales
    , customers
    , times
where
    sales.cust_id = customers.cust_id
    and sales.time_id = times.time_id
group by
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
order by
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
;

同様に,コメントを追加したら,やはり結果は再利用されませんでした.

/* Test comment */
select
     calendar_year
    , calendar_month_number
    , country_id
    , cust_city
    , count(1)
    , sum(amount_sold)
from
    sales
    , customers
    , times
where
    sales.cust_id = customers.cust_id
    and sales.time_id = times.time_id
group by
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
order by
    calendar_year
    , calendar_month_number
    , country_id
    , cust_city
;

おそらくは,内部的にクエリ文字列をハッシュ変換しており,それと一致しているものがあるかどうかで同一性の確認をしているのだと推測できます.便利ではありますが,気をつけて使わないといけない部分ですね.

これ起因で,QuickSight から接続した場合も,また AWS SDK for Pandas から接続した場合も,現状だとキャッシュ再利用は働かないようです.QuickSight からのクエリに関しては,以下のようにハッシュが入った形の名前がついていたり,また冒頭にコメントが入ってしまう仕様のようです.

/* QuickSight d9425c29-18c5-4616-9043-973213fbdf0d */ SELECT COUNT(*) AS "count", SUM("amount_sold") AS "9ab75332-3ccf-484d-ad58-9a010e03fd49.amount_sold_sum" FROM "AwsDataCatalog"."sh10_gz"."sales"

AWS SDK for Pandas の場合は,こちらもハッシュ値のついた temp_table にデータを保存する形になるようなので,キャッシュは使えないということになります.

CREATE TABLE "sh10_gz"."temp_table_7e183e2045e34e1fa52fd0fa8b12e29c" WITH( external_location = 's3://aws-athena-query-results-666254511816-us-east-1/temp_table_7e183e2045e34e1fa52fd0fa8b12e29c', format = 'PARQUET') AS select * from sales limit 1000000

まとめ

本記事では,Amazon Athena の Query Result Reuse 機能について,少し突っ込んで中身を色々と確認してみました.いくつか制約はあるものの,色々な可能性のある機能だと思いますので,ぜひ活用していきましょう! また,AWS Analytics Advent Calendar 2022 の他の記事もぜひお楽しみに!