概要
Amazon Athenaを本格的に運用に乗せてくると、データを更新した際などのレコード件数などの確認は重要となってきます。
しかしながら、扱いが困るくらい大きなデータだからこそAthenaなどを使っているわけで、データの確認に都度お金が掛かると、必要な事とはいえ心理的に阻害されることがあるかもしれません。
今回、運用していて「なるほどこのケースだとData scannedが0になるのか」と気づいた点がいくつかあったので、スキャンされるデータ量の検証もしつつ、まとめてみました。
お断り
公式ドキュメントにおいては 特定ケースでスキャン量がゼロになる とまで明言されているものを見つけられなかったので(あるかもしれません)2021年5月時点での仕様、かつ私が扱っているデータセット要因によるものである可能性もあります。
結論
- Parquet形式のデータの場合、whereやgroup byなどを付けない単純なcount(1)のSELECTであればData scanned 0KBでレコード数が確認できる。
- パーティションのキーであればwhereで指定しても、group by をしても同様にData scanned 0KBとなる。
おさらい(Athenaの料金とスキャン)
公式にわかりやすくまとめられています。
https://aws.amazon.com/jp/athena/pricing/
一方で「スキャン」という単語に統一されており、「課金体系はスキャンされるデータ量だよ~」と説明はできますが「じゃあ例えばこういうクエリっていくらかかるの?」と問われると、回答が難しいと感じます。
というわけで、実際に見てみました。
スキャンの発生検証
以下のパターンで試していきます。
対象テーブルと調査クエリ
50MB_NOT_PARTITIONED_TABLE
- レコード数 250,000件
- パーティションなし
- TSV
- 実行クエリ
-
- SELECT * FROM 50MB_NOT_PARTITIONED_TABLE limit 10;
-
- SELECT * FROM 50MB_NOT_PARTITIONED_TABLE limit 1000;
-
- SELECT * FROM 50MB_NOT_PARTITIONED_TABLE limit 10000;
-
- SELECT * FROM 50MB_NOT_PARTITIONED_TABLE ;
-
- SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE limit 10;
-
- SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE limit 1000;
-
- SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE limit 10000;
-
- SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE ;
-
- SELECT count(1) FROM 50MB_NOT_PARTITIONED_TABLE ;
-
250MB_PARTITIONED_TABLE_TSV
- レコード数 800,000件
- パーティションあり
- 指定したパーティション内のレコード数200,000件/67.99MB)
- TSV
- 実行クエリ
-
- SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10;
-
- SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 1000;
-
- SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10000;
-
- SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' ;
-
- SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10;
-
- SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 1000;
-
- SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10000;
-
- SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' ;
-
- SELECT count(1) FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' ;
-
- SELECT count(1) FROM 250MB_PARTITIONED_TABLE_TSV ;
-
- SELECT partitionkey,count(1) FROM 250MB_PARTITIONED_TABLE_TSV group by 1 ;
-
NGB_PARTITIONED_TABLE_PARQUET
- レコード数 200,000,000件
- パーティションあり
- 指定したパーティション内のレコード数 1,900,000 万件/ 容量未調査)
- Parquet
- 実行クエリ
-
- SELECT * FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 10;
-
- SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 10;
-
- SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 1000;
-
- SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 10000;
-
- SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' ;
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' ;
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET ;
-
- SELECT partitionkey,count(1) FROM NGB_PARTITIONED_TABLE_PARQUET group by 1 ;
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' and notpartitionkey = 'yyy';
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' and notpartitionkey is not null;
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where notpartitionkey = 'yyy'; --※このクエリは危険です
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where notpartitionkey is not null;--※このクエリは危険です
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey is not null ;
-
- SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey like '%';
-
実行結果
上記で書かれたSQLについて、10回程度実行し平均、最大、最少を求めてます。
試行回数も少ないのと、どうもテーブルへの格納状況によっても変わるのかな~という感じもしており(※ただの感覚です)参考程度かもです。
50MB_NOT_PARTITIONED_TABLE
No. | select | limit | 平均(MB) | 最大(MB) | 最小(MB) |
---|---|---|---|---|---|
1 | * | 10 | 1.37 | 2.42 | 0.80 |
2 | * | 1000 | 1.68 | 2.39 | 1.20 |
3 | * | 10000 | 3.88 | 5.60 | 2.80 |
4 | * | (なし) | 51.38 | 51.38 | 51.38 |
5 | column1 | 10 | 11.38 | 21.97 | 3.69 |
6 | column1 | 1000 | 10.31 | 14.69 | 7.30 |
7 | column1 | 10000 | 14.32 | 32.98 | 7.32 |
8 | column1 | (なし) | 51.38 | 51.38 | 51.38 |
9 | count(1) | - | 51.38 | 51.38 | 51.38 |
- カラムを指定するよりも * によるSELECTのほうがスキャン量が少なくなる、という面白い結果になりました(違うサイズのカラムなどを選択しても同様でした)。
- おおよそ limit 1000 くらいまではあまりスキャン量は変わらないようです。
- TSVの場合は、count(1) としてもそのテーブルをすべてスキャンすることとなるようです。
250MB_PARTITIONED_TABLE_TSV
No. | select | limit | where | 平均(MB) | 最大(MB) | 最小(MB) |
---|---|---|---|---|---|---|
1 | * | 10 | partitionkey='xxx' | 1.56 | 1.95 | 1.29 |
2 | * | 1000 | partitionkey='xxx' | 1.367 | 1.95 | 1.29 |
3 | * | 10000 | partitionkey='xxx' | 4.69 | 9.13 | 3.9 |
4 | * | (なし) | partitionkey='xxx' | 67.99 | 67.99 | 67.99 |
5 | column1 | 10 | partitionkey='xxx' | 29.87 | 39.58 | 17.99 |
6 | column1 | 1000 | partitionkey='xxx' | 30.23 | 57.57 | 17.99 |
7 | column1 | 10000 | partitionkey='xxx' | 35.27 | 39.58 | 17.99 |
8 | column1 | (なし) | partitionkey='xxx' | 67.99 | 67.99 | 67.99 |
9 | count(1) | - | partitionkey='xxx' | 67.99 | 67.99 | 67.99 |
10 | count(1) | - | (なし) | 256.89 | 256.89 | 256.89 |
11 | partitionkey,count(1) | - | (なし) | 256.89 | 256.89 | 256.89 |
- 中身はTSVなので、基本的に結果は前項と同じになります。
- where句を外すと、partitionkeyでgroup by しようが当然すべてのデータをスキャンすることとなります。
NGB_PARTITIONED_TABLE_PARQUET
No. | select | limit | where | 平均(MB) | 最大(MB) | 最小(MB) |
---|---|---|---|---|---|---|
1 | * | 10 | partitionkey='xxx' | 8.92 | 12.69 | 5.00 |
2 | column1 | 10 | partitionkey='xxx' | 0.40 | 0.82 | 0.12 |
3 | column1 | 1000 | partitionkey='xxx' | 0.99 | 3.28 | 0.23 |
4 | column1 | 10000 | partitionkey='xxx' | 0.51 | 0.68 | 0.25 |
5 | column1 | (なし) | partitionkey='xxx' | 3.28 | 3.28 | 3.28 |
6 | count(1) | (なし) | partitionkey='xxx' | 0.00 | 0.00 | 0.00 |
7 | count(1) | - | (なし) | 0.00 | 0.00 | 0.00 |
8 | partitionkey,count(1) | - | (なし) | 0.00 | 0.00 | 0.00 |
- Parquetにすると、列数にもよりますがスキャン量が激減します。
- limitをかけても、その項目におけるフルスキャンが走る、ような挙動が時々ありました(No.3の最大がそれ)。
- count(1) は、whereをつけずにテーブル全体に行ったり、パーティションを指定した場合にスキャンが0KBになります。
- count(1)はパーティションキーでgroup by をしてもスキャンが0KBになります。
以下おまけ。
No. | select | where | 結果(MB) |
---|---|---|---|
9 | count(1) | partitionkey = 'xxx' and notpartitionkey = 'yyy' | 6.05 (何度やっても) |
10 | count(1) | partitionkey = 'xxx' and notpartitionkey is not null | 6.05 (何度やっても) |
11 | count(1) | notpartitionkey = 'yyy' | ※ |
12 | count(1) | notpartitionkey is not null | ※ |
13 | count(1) | partitionkey is not null | 0.00 |
14 | count(1) | partitionkey like '%' | 0.00 |
- パーティションキー以外をwhere句につけると、スキャンが発生します。これは指定するカラムによって変わり、SELECTですべて取得した時の値と一致しているように見えます(9と10で結果が一致しているのも、どういう挙動かが分かる感じがしますね)。
- ※はパーティションキーを指定していないので、数秒でキャンセルしても数十MBに。やっちゃだめです。
- パーティションキーは、それ自体をwhere句に入れあれこれいじってもスキャン量は0のままのようでした。
雑感
スキャン量を減らす、ということは、そのままパフォーマンスチューニングに繋がるわけです。こうした挙動の肌感覚を持っておくことはAthenaを使っていくうえで重要と考えています。
ちなみにParquetが強いのは、こちらのドキュメント を見ると、Predicate pushdown関連の恩恵なのかなと思います。統計情報のみで完結する、ということなのだと思うのですが、リソース課金ではなくスキャンデータ量課金の場合、こうした恩恵に預かれるのですね。
そして、リソース課金のサービスも多いなか、必然、スキャンデータ量課金だと使う側の工夫や設計も変わってくるはずで、そのあたりが面白いなと感じます。