Amazon Athenaは、S3上に保存されたデータをサーバーレスのSQLで直接分析できるAWSのマネージドサービスです。インフラ管理が不要で「使った分だけ課金」というモデルは魅力的ですが、初めて使うエンジニアがほぼ必ずハマる落とし穴があります。それがスキャン量課金という料金体系です。
CloudTrailのログ分析で月100GBのデータを毎日クエリしていたら、月末に数万円の請求が届いた——という話は現場でよく耳にします。オンプレのDBでは全表スキャンのコストはCPU時間の問題でしたが、Athenaではスキャンしたデータ量がそのまま請求書の金額に変わります。
この記事では、Amazon Athenaのコスト最適化について料金の仕組みから具体的な削減手順まで徹底解説します。パーティション設計・データフォーマット変換(Parquet化)・圧縮・ワークグループ設定の4アプローチを組み合わせれば、クエリ料金を最大90%削減することは現実的な目標です。
なぜAthenaのコストは予想外に膨らむのか
オンプレのDB運用では、テーブルフルスキャンを実行しても請求額は変わりませんでした。ハードウェアを買い切っている以上、使い方がどれだけ非効率でもコストは固定です。インデックスをうまく使えばパフォーマンスは向上しますが、それはスピードの問題であり、請求書の問題ではありませんでした。
Athenaの世界ではこの常識が完全に覆ります。
・WHERE句を書いても全件スキャン: CSVやJSON形式のファイルは、WHERE条件を指定してもファイル全体を読み込んでからフィルタリングします。100GBのファイルから1行だけ取り出す場合でも、スキャン量は100GBです
・データが増えるほど毎回の料金も増える: ログをS3に1年分蓄積すると、新しいクエリを実行するたびに1年分のファイルをスキャンする状況になります
・開発中の試行錯誤が課金を積み上げる: クエリを修正して再実行する作業を繰り返すだけで、気づけば月額数万円に達することがあります
・SELECT *がそのまま被害を最大化する: 「とりあえずSELECT *で確認する」という習慣は、Athenaでは最もコスト効率の悪いアンチパターンです
オンプレからクラウドに移行したエンジニアが、これまでの作業習慣そのままにAthenaを使い始めた瞬間、月末に想定外の請求が届くというパターンを何度も見てきました。コスト最適化の前提として、まず「スキャンした分だけ払う」という感覚を体に染み込ませることが重要です。
Athenaの料金体系を正確に理解する
1. スキャン課金のしくみ
Athenaの基本料金はシンプルです。
・課金単位: クエリ1回ごとのS3スキャン量(TB単位)
・料金: 約$5.00/TB(東京リージョン、2026年3月時点)
・最低課金: 10MB(10MB未満でも10MBとして計算)
・キャンセルしたクエリ: キャンセル時点までのスキャン量で課金
・DDLクエリ(CREATE TABLE、DROP TABLE等): 無料
・失敗したクエリ: 無料(課金なし)
2. コスト計算の実例
具体的な数字で確認してみましょう。
| シナリオ | スキャン量 | 1回の料金 | 月30回の料金 |
|---|---|---|---|
| 100GB CSVを全件スキャン | 100 GB | 約$0.49 | 約$14.6 |
| 1TB CSVを全件スキャン | 1,000 GB | 約$4.88 | 約$146 |
| 100GB Parquet(日付パーティション) | 約5 GB | 約$0.024 | 約$0.73 |
| 1TB Parquet(日付パーティション) | 約50 GB | 約$0.24 | 約$7.3 |
最適化前と最適化後でコストが約20倍変わる計算になります。このインパクトを理解したうえで、具体的な削減テクニックを見ていきましょう。
なお、Athenaのコスト以外にもS3ストレージ料金やS3 APIリクエスト料金が別途発生します。また、クエリ結果はデフォルトでS3に保存されるため、長期間放置するとストレージコストが積み上がります。S3ライフサイクルポリシーでクエリ結果を7日後に削除する設定を忘れずに入れておきましょう。
コスト削減テクニック① パーティション設計
最もインパクトが大きい施策がパーティション設計です。S3上のデータを年・月・日などの単位でフォルダに分けて保存し、AthenaがWHERE句の条件に合致するフォルダだけを読み込めるようにします。
Hiveスタイル(key=value形式)のフォルダ命名規則を使うと、Athenaが自動認識できます。
# Hiveスタイルパーティションのフォルダ構造(推奨) s3://my-logs/cloudtrail/ year=2026/ month=05/ day=17/ file1.json.gz day=16/ file2.json.gz month=04/ ...
このフォルダ構造に合わせてAthenaテーブルを定義します。
-- AWS Athenaでのパーティション対応テーブル定義 CREATE EXTERNAL TABLE cloudtrail_logs ( eventVersion STRING, eventTime STRING, eventSource STRING, eventName STRING, awsRegion STRING, sourceIPAddress STRING, userAgent STRING ) PARTITIONED BY (year STRING, month STRING, day STRING) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' LOCATION 's3://my-logs/cloudtrail/'; -- パーティションをGlueカタログに登録する MSCK REPAIR TABLE cloudtrail_logs;
この設定後、WHERE句でパーティション列を直接指定します。
-- 2026年5月17日のログだけを対象にする(パーティションプルーニング有効) SELECT eventName, sourceIPAddress, eventTime FROM cloudtrail_logs WHERE year='2026' AND month='05' AND day='17' AND eventSource = 'ec2.amazonaws.com' LIMIT 100;
1年分のログがあっても、1日分のフォルダだけをスキャンするのでスキャン量は約1/365になります。
注意点として、パーティション列以外の列で日付フィルタリングしてもプルーニングは機能しません。`WHERE eventTime LIKE ‘2026-05-17%’`と書いてしまうとS3全体をスキャンしてしまうため、必ずパーティション列(year・month・day)を直接指定してください。
コスト削減テクニック② データフォーマットをParquet/ORCに変換する
次に大きな効果をもたらすのがデータフォーマットの変換です。CloudTrailやアクセスログはデフォルトでJSON/CSV形式ですが、これを列指向フォーマットのParquetまたはORCに変換することで、スキャン量を劇的に削減できます。
・JSON/CSV(行指向): 1行にすべての列データを格納。1列だけ参照しても行全体を読み込む
・Parquet/ORC(列指向): 列単位でデータをまとめて格納。SELECTで指定した列のデータだけを読み込む
実際の現場でJSONログをParquet(Snappy圧縮)に変換した結果、ファイルサイズが1/8になったケースもあります。スキャン量が1/8になればコストも1/8です。
AWS GlueのPySparkジョブを使ったParquet変換の例を示します。
# AWS Glue PySpark スクリプト(JSON → Parquet変換) import sys from awsglue.transforms import * from awsglue.utils import getResolvedOptions from pyspark.context import SparkContext from awsglue.context import GlueContext sc = SparkContext() glueContext = GlueContext(sc) spark = glueContext.spark_session # 入力: GlueカタログのJSONテーブル source_df = glueContext.create_dynamic_frame.from_catalog( database="my_database", table_name="raw_json_logs" ) # 出力: Parquet + Snappy圧縮でS3に保存 glueContext.write_dynamic_frame.from_options( frame=source_df, connection_type="s3", connection_options={ "path": "s3://my-logs/parquet-output/", "partitionKeys": ["year", "month", "day"] }, format="parquet", format_options={"compression": "snappy"} )
既存のCSVやJSONをすべて一度に変換するのが難しければ、今後受信する新しいデータだけをParquetで保存するパイプラインを先に構築し、古いデータは段階的に移行する方法も現実的です。
コスト削減テクニック③ 圧縮でファイルサイズを削減する
フォーマット変換と合わせて適切な圧縮を選ぶことで、さらにスキャン量を減らせます。Athenaがサポートする主な圧縮方式をまとめます。
| 圧縮方式 | 対応フォーマット | 圧縮率 | 現場での推奨度 |
|---|---|---|---|
| Snappy | Parquet、ORC | 中程度 | ★★★(Parquetとの組み合わせが定番) |
| ZSTD | Parquet、ORC | 高 | ★★★(新規データには最有力候補) |
| GZIP | CSV、JSON | 高 | ★★(既存ログのCSV圧縮に有効だがスプリット不可) |
| LZ4 | Parquet | 低 | ★(解凍速度重視の場合) |
| BZIP2 | CSV、JSON | 最高 | △(圧縮率は高いが処理が遅い) |
一点注意が必要です。CSVをGZIPで圧縮したファイル(.csv.gz)はAthenaが並列処理(スプリット)できません。大容量ファイルでは処理が遅くなる場合があります。Parquet + Snappyなら並列処理対応なので、大規模データセットでもパフォーマンスは維持されます。これが「Parquet + Snappyが現場の定番」といわれる理由の一つです。
コスト削減テクニック④ ワークグループで予算上限を設定する
最適化施策と並行して、コスト事故を防ぐ保険としてワークグループの活用を強くお勧めします。ワークグループはチーム・プロジェクト・環境(本番/開発)ごとに作成でき、クエリあたりのスキャン上限を設定できます。
# ワークグループ作成(AWS CLI) # 開発環境: 1クエリあたりスキャン上限10GB(約$0.05) aws athena create-work-group \ --name "dev-team" \ --configuration '{ "ResultConfiguration": { "OutputLocation": "s3://my-athena-results/dev-team/" }, "BytesScannedCutoffPerQuery": 10737418240, "EnforceWorkGroupConfiguration": true }' \ --description "開発チーム用 - スキャン上限10GB/クエリ" # 本番環境: 上限なし(または500GBなど大きめに設定) aws athena create-work-group \ --name "prod-analytics" \ --configuration '{ "ResultConfiguration": { "OutputLocation": "s3://my-athena-results/prod/" }, "EnforceWorkGroupConfiguration": true }' \ --description "本番分析用 - 制限なし"
・BytesScannedCutoffPerQuery: この値(バイト単位)を超えたクエリは自動でキャンセルされます。10GBは「10 × 1024 × 1024 × 1024 = 10,737,418,240」バイトです
・EnforceWorkGroupConfiguration: true: クライアント(AWSコンソール・SDK)側の設定よりワークグループの設定を優先適用します
開発環境での試行錯誤中に誤って大規模クエリを流してしまう事故はよくあります。開発用ワークグループに上限を設けておくだけで、最悪ケースの被害額が読めるようになります。
パーティションプロジェクションで管理を自動化する
パーティションを使う際の課題として「新しいパーティションが追加されるたびにMSCK REPAIR TABLEを実行しなければならない」という管理コストがあります。毎日新しいログが蓄積されるシステムでは、これを自動化しないとパーティションが古いままになって正しくスキャン範囲が絞れなくなります。
これを解決するのがパーティションプロジェクションです。日付など規則的なパターンのパーティションであれば、AthenaがS3パスを動的に計算するため、Glueカタログへのパーティション登録が不要になります。
-- パーティションプロジェクションを使ったテーブル定義 CREATE EXTERNAL TABLE cloudtrail_projected ( eventVersion STRING, eventTime STRING, eventSource STRING, eventName STRING, awsRegion STRING, sourceIPAddress STRING ) PARTITIONED BY (year STRING, month STRING, day STRING) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' LOCATION 's3://my-logs/cloudtrail/' TBLPROPERTIES ( "projection.enabled" = "true", "projection.year.type" = "integer", "projection.year.range" = "2024,2030", "projection.month.type" = "integer", "projection.month.range" = "1,12", "projection.month.digits" = "2", "projection.day.type" = "integer", "projection.day.range" = "1,31", "projection.day.digits" = "2", "storage.location.template" = "s3://my-logs/cloudtrail/year=${year}/month=${month}/day=${day}/" );
このテーブルを作成すると、新しいログファイルがS3に追加されるたびに自動的にAthenaから参照できます。MSCK REPAIR TABLEの定期実行や、Lambda + S3イベントトリガーによるパーティション追加の仕組みが不要になります。
CloudTrailやELBアクセスログのように毎日新しいパーティションが発生するユースケースでは、パーティションプロジェクションはほぼ必須のテクニックです。
よくあるトラブルと対処法
Q. パーティションを設定したのにスキャン量が減らない
WHERE句がパーティション列を直接指定しているか確認してください。`WHERE eventtime LIKE ‘2026-05%’`のようにパーティション列以外の列でフィルタリングしても、パーティションプルーニングは機能しません。`WHERE year=’2026’ AND month=’05’`のようにパーティション列を直接使う必要があります。クエリ実行後に「データスキャン量」をAthenaコンソールで確認し、意図した量になっているかをチェックしてください。
Q. Parquetに変換したのにスキャン量が変わらない
テーブル定義のSERDE設定を確認してください。Parquetに変換したS3ファイルを旧のJSONテーブル定義で参照していると、列指向の恩恵が得られません。`SHOW CREATE TABLE テーブル名`でROW FORMAT設定を確認し、Parquet向けの設定(org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe)になっているか見てください。
Q. ワークグループの上限で本番クエリがキャンセルされた
ワークグループは用途別に分けることをお勧めします。prodワークグループは上限なし(またはTB単位の大きな値)、devワークグループは10GB以下という形で環境を分離します。誰がどのワークグループを使うかを明確にし、IAMポリシーでワークグループの使用権限を制御するとより安全です。
Q. MSCK REPAIR TABLEを実行したが新しいパーティションが認識されない
S3のフォルダ名がHiveスタイル(key=value形式)になっているか確認してください。`year=2026/month=05/day=17/`のようなkey=value形式でなければ自動認識されません。形式が異なる場合は`ALTER TABLE … ADD PARTITION`で手動追加するか、パーティションプロジェクションへの移行を検討してください。
Q. Glue Data CatalogとAthenaの料金も考慮する必要があるか
Glue Data Catalogは月100万リクエストまで無料で、一般的なAthena利用での追加コストはほぼ発生しません。Glue ETLジョブ(DPU時間課金)は別途かかるため、大量データを変換する場合はGlue ETLの料金試算も忘れずに行ってください。
本記事のまとめ
Amazon Athenaのコスト最適化は、以下の優先順で取り組むのが効果的です。
| 優先度 | 施策 | 期待効果 | 難易度 |
|---|---|---|---|
| ★★★ | パーティション設計 | スキャン量を1/30~1/365に削減 | 中 |
| ★★★ | Parquet/ORC変換 | スキャン量・ファイルサイズを1/5~1/10に削減 | 中 |
| ★★ | Snappy/ZSTD圧縮 | ファイルサイズをさらに50~80%削減 | 低 |
| ★★ | ワークグループ上限設定 | コスト事故時の被害を最小化 | 低 |
| ★ | パーティションプロジェクション | パーティション管理の自動化 | 高 |
オンプレのDB運用に慣れたエンジニアにとって、「全件スキャンが請求額に直結する」という感覚は最初は違和感があるかもしれません。しかし一度このモデルを理解し、パーティション設計とParquet変換を習慣にすれば、Athenaは非常にコスト効率の高い分析基盤になります。
まずはワークグループの上限設定からはじめて事故を防ぎつつ、既存データのParquet移行を段階的に進めていくアプローチが現実的です。LinuxサーバーやEC2上のログ管理については、姉妹サイトLinuxMaster.JPでも詳しく解説しています。クラウド上のログ分析環境の構築と合わせて参考にしてください。
Athenaのコスト最適化、もっと詳しく知りたい方へ
パーティション設計やParquet変換は「知っている」と「実際にやった」の間に大きなギャップがあります。
オンプレの経験を活かしながら、現場で使えるクラウドスキルを体系的に身につけたい方へ、メルマガで実践的なクラウド活用ノウハウをお届けしています。
