5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Amazon Athenaのスキャンされるデータ量の検証とParquetのcount(1)が便利という話

Posted at

概要

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
  • 実行クエリ
      1. SELECT * FROM 50MB_NOT_PARTITIONED_TABLE limit 10;
      1. SELECT * FROM 50MB_NOT_PARTITIONED_TABLE limit 1000;
      1. SELECT * FROM 50MB_NOT_PARTITIONED_TABLE limit 10000;
      1. SELECT * FROM 50MB_NOT_PARTITIONED_TABLE ;
      1. SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE limit 10;
      1. SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE limit 1000;
      1. SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE limit 10000;
      1. SELECT column1 FROM 50MB_NOT_PARTITIONED_TABLE ;
      1. SELECT count(1) FROM 50MB_NOT_PARTITIONED_TABLE ;

250MB_PARTITIONED_TABLE_TSV

  • レコード数 800,000件
  • パーティションあり
  • 指定したパーティション内のレコード数200,000件/67.99MB)
  • TSV
  • 実行クエリ
      1. SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10;
      1. SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 1000;
      1. SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10000;
      1. SELECT * FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' ;
      1. SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10;
      1. SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 1000;
      1. SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' limit 10000;
      1. SELECT column1 FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' ;
      1. SELECT count(1) FROM 250MB_PARTITIONED_TABLE_TSV where partitionkey = 'xxx' ;
      1. SELECT count(1) FROM 250MB_PARTITIONED_TABLE_TSV ;
      1. SELECT partitionkey,count(1) FROM 250MB_PARTITIONED_TABLE_TSV group by 1 ;

NGB_PARTITIONED_TABLE_PARQUET

  • レコード数 200,000,000件
  • パーティションあり
  • 指定したパーティション内のレコード数 1,900,000 万件/ 容量未調査)
  • Parquet
  • 実行クエリ
      1. SELECT * FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 10;
      1. SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 10;
      1. SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 1000;
      1. SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' limit 10000;
      1. SELECT column1 FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' ;
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' ;
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET ;
      1. SELECT partitionkey,count(1) FROM NGB_PARTITIONED_TABLE_PARQUET group by 1 ;
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' and notpartitionkey = 'yyy';
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey = 'xxx' and notpartitionkey is not null;
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where notpartitionkey = 'yyy'; --※このクエリは危険です
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where notpartitionkey is not null;--※このクエリは危険です
      1. SELECT count(1) FROM NGB_PARTITIONED_TABLE_PARQUET where partitionkey is not null ;
      1. 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関連の恩恵なのかなと思います。統計情報のみで完結する、ということなのだと思うのですが、リソース課金ではなくスキャンデータ量課金の場合、こうした恩恵に預かれるのですね。

そして、リソース課金のサービスも多いなか、必然、スキャンデータ量課金だと使う側の工夫や設計も変わってくるはずで、そのあたりが面白いなと感じます。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?