Skip to content

Athena基本設計ガイド

1. 概要

基本アーキテクチャ

S3バケット(ログデータ)

AWS Glue Data Catalog(メタデータ管理)

Amazon Athena(クエリエンジン)

S3バケット(クエリ結果保存)

FastDoctorのAthena設計方針

FastDoctorでは、プロダクト別のコスト追跡とセキュリティを両立するため、以下の設計を採用します:

設計の3本柱

【1. ワークグループ】プロダクト単位
├── analytics-payment-service
└── analytics-online-service
  ↓ 目的: プロダクト別コスト追跡、組織変更に強い設計

【2. データベース】ログ種別ごと
├── access_logs           (HTTPアクセスログ)
├── datadog_log_archives  (Datadogログ)
├── audit_logs            (監査ログ)
└── cost_monitoring       (コストデータ)
  ↓ 目的: 論理的なグループ化、横断分析

【3. テーブル】プロダクト別S3バケットを参照
├── access_logs.payment_service_alb    → s3://payment-service-logs/alb/
├── access_logs.online_service_alb     → s3://online-service-logs/alb/
└── audit_logs.common_cloudtrail       → s3://audit-logs/cloudtrail/
  ↓ 目的: 実データの分離、IAMでアクセス制御

重要な前提:

  • コスト追跡: ワークグループのタグでCUR/Cost Explorerでプロダクト別コスト分析
  • セキュリティ:
    • データベース(名前空間)は共通、テーブルはプロダクト別
    • 実データへのアクセスはS3バケット単位でIAM制御(個人情報・機密情報保護)
    • プロダクト単位でアクセス制御(組織変更を考慮)
    • 必要になった際にIAMロールを作成・追加(IAMグループ上限10個の制約対策)

FastDoctorでの位置づけ

  • 長期保存されたログの分析: Datadogログアーカイブ、CloudTrail、VPCフローログなどの過去データ分析
  • コスト分析: Cost Explorerベースのコスト集計結果の分析(将来的にCURへ移行予定)
  • セキュリティ監査: アクセスログ、監査ログの調査
  • パフォーマンス分析: ALB/CloudFrontログからのトラフィック傾向分析
  • インシデント調査: 過去のログデータからの原因究明

分析対象ログの分類

FastDoctorで分析可能なログは以下の3つに分類されます。各ログの詳細な実装方法やクエリ例については、分析対象別ドキュメントを参照してください。

即座に分析可能(S3に保存済み)

  • Datadogログアーカイブ
  • コスト集計データ(Cost Explorerベース、CSV形式)
  • ALB/NLBアクセスログ
  • CloudTrailログ
  • VPCフローログ
  • CloudFrontアクセスログ
  • Bastion監査ログ
  • S3アクセスログ

追加設定後に分析可能(CloudWatch Logs → S3エクスポート設定が必要)

  • API Gatewayアクセスログ
  • ECS/アプリケーションログ
  • Lambdaログ
  • RDS/Auroraログ(スロークエリ、エラーログ)
  • OpenSearchログ

ログ出力設定が必要

  • WAFログ(S3またはCloudWatch Logsへの出力設定)

Datadogとの棲み分け

FastDoctorでは既にDatadogを利用したログ管理・監視が運用されています。AthenaとDatadogは競合するものではなく、それぞれの強みを活かして使い分けることでコスト効率と分析効率を両立させます。

Datadogの強み・ユースケース

  • リアルタイム監視・アラート: アプリケーションエラー、パフォーマンス異常の即座な検知
  • インタラクティブな調査: Live Tailによるリアルタイムログ閲覧、フィルタリング
  • 短期間のログ分析: 直近数日〜数週間のログ分析(保持期間内)
  • APM・メトリクス統合: トレース、メトリクス、ログの相関分析
  • ダッシュボード: リアルタイムダッシュボードでの可視化
  • アラート設定: 閾値ベースのアラート、異常検知

コスト特性: ログの取り込み量・保持期間に応じた課金。長期保存はコストが高い。

Athenaの強み・ユースケース

  • 長期データ分析: 数ヶ月〜数年前のログを低コストで分析
  • 大規模データ処理: TB単位のログデータを効率的に集計・分析
  • カスタムSQL分析: 複雑な集計、JOIN、統計分析が可能
  • コスト最適化: Datadogログアーカイブ(S3保存)からの再分析で長期保存コストを削減
  • 過去データとの比較: 年次比較、トレンド分析

コスト特性: S3ストレージコスト + クエリ実行時のスキャンデータ量に応じた課金。保存コストは非常に安価。

棲み分けの基本方針

観点DatadogAthena
時間軸直近(数日〜数週間)過去(数週間〜数年)
用途リアルタイム監視、アラート過去ログ分析、トレンド分析
分析パターンインタラクティブ、探索的SQL、バッチ処理
コスト保持期間に比例して高くなるS3保存は安価、クエリ時のみ課金
レスポンス即座(ミリ秒〜秒)数秒〜数分

実践的な使い分け例

シナリオ1: アプリケーションエラー調査

  • Datadog: 直近のエラーログをLive Tailで確認、APMトレースと紐付けて原因特定
  • Athena: 過去3ヶ月のエラー発生傾向を分析、特定エラーの発生頻度推移を調査

シナリオ2: セキュリティ監査

  • Datadog: 異常なアクセスパターンをリアルタイム検知、Security Monitoring
  • Athena: 過去6ヶ月のCloudTrailログから特定ユーザーのAPI操作履歴を監査

2. ストレージ設計の基本方針

S3バケット構成

データ保存場所

  • 既存のログバケット: 各ログソースごとに既に存在するS3バケットを活用
  • パーティション: ログソースに応じて日付ベース、サービスベースのパーティション構造を設計

クエリ結果保存場所

  • 専用バケット: Athenaクエリ結果専用のS3バケットを環境ごとに作成
  • ライフサイクル管理: クエリ結果は監査証跡として保存。監査要件ガイドラインのシステム管理ログ保存期間(3年)に準拠
    • 全環境共通: 3年保持(監査証跡として保存)
  • 暗号化: SSE-S3またはSSE-KMSによる暗号化を有効化

S3ストレージクラス移行戦略

クエリ結果ログはコスト最適化のため、アクセス頻度に応じてストレージクラスを移行:

  • 頻繁なアクセス: Standard(初期90日)
  • 低頻度アクセス: Standard-IA(90日後)
  • アーカイブ: Glacier Flexible Retrieval(必要に応じて)

パーティショニング戦略

パーティション設計原則

  • 日付ベース: year=YYYY/month=MM/day=DD/ 形式を推奨
  • サービスベース: マルチテナント環境では service=xxx/ も検討
  • クエリパターン: よく使用されるフィルタ条件に基づいてパーティションを設計

運用上の注意点:

  • パーティション数の上限: 1テーブルあたり数万パーティションを超えないよう設計
    • 超過すると、Glue API呼び出しの遅延やクエリ実行前のメタデータ取得に時間がかかる
    • 目安: 10,000パーティション以下を推奨
  • パーティション粒度: 時間単位より日単位を推奨
    • 時間単位: 1年で8,760パーティション(管理負荷が高い)
    • 日単位: 1年で365パーティション(推奨)

パーティションプロジェクション

AWS Glue Crawlerを使用せずにパーティションを自動認識する仕組み:

sql
-- パーティションプロジェクション設定例
TBLPROPERTIES (
  "projection.enabled" = "true",
  "projection.dt.type" = "date",
  "projection.dt.range" = "2024-01-01,NOW",
  "projection.dt.format" = "yyyy-MM-dd",
  "storage.location.template" = "s3://bucket/logs/${dt}/"
)

データフォーマット選択指針

フォーマット用途メリットデメリット
JSONログファイル(元データ)可読性が高い、構造化しやすいスキャンコストが高い
Parquet大量データ分析列指向で高速、圧縮率が高い変換処理が必要
ORCHive互換性重視高圧縮率Parquetより汎用性が低い
CSV/TSVシンプルなデータ変換不要型情報がない、圧縮率が低い

FastDoctorでの推奨フォーマット

  • そのまま分析: JSONまたはCSV(生ログをそのまま保存)
  • 頻繁にクエリ: CTAS(Create Table As Select)でParquetに変換
  • コスト分析: 現状はCSV(将来CUR実装時はParquet形式)

Glue Data Catalog管理方針

AWS Glue Data Catalogは、Athenaが使用するメタデータ(データベース、テーブル、パーティション情報)を管理するための中央カタログです。適切なカタログ管理により、データの発見性、アクセス制御、運用効率が向上します。

データベース設計の原則

FastDoctorでは、ログ種別ごとにデータベースを分離します。各データベース内のテーブルは、プロダクト別の異なるS3バケットを参照します。

命名規則
${service_name}_access_logs # アクセスログ(ALB、CloudFront等)
cost_monitoring             # コスト分析データ(CUR、Cost Explorer等)
datadog_log_archives        # Datadogログアーカイブ
audit_logs                  # 監査ログ(CloudTrail、VPCフローログ等)

前提:

  • データベース: ログ種別で分離(論理的なコンテナ)
  • テーブル: プロダクト別のS3バケットを参照
  • アクセス制御: IAMでS3バケット単位で制御
データベース分離の方針
  1. ログ種別ごとに分離

    • 同じ種類のログ(アクセスログ、監査ログ等)は同じデータベースに集約
    • スキーマの一貫性を保ちやすい
  2. プロダクト横断可能

    • 同じデータベース内で複数プロダクトのデータを横断分析可能
    • 例: 全プロダクトのALBアクセスログを集計
  3. S3レベルでセキュリティ確保

    • データベース(名前空間)は共通、テーブルはプロダクト別
    • 実データへのアクセスはIAMでS3バケット単位で制御
    • テーブル定義(メタデータ)は見えるが、S3アクセス権がなければクエリ失敗
データベース定義例(Terraform)
hcl
# ログ種別ごとにデータベースを作成
resource "aws_glue_catalog_database" "access_logs" {
  name        = "access_logs"
  description = "HTTP access logs from ALB, CloudFront, etc."

  tags = {
    LogType   = "access"
    ManagedBy = "Terraform"
  }
}

resource "aws_glue_catalog_database" "cost_monitoring" {
  name        = "cost_monitoring"
  description = "Cost and usage data from CUR, Cost Explorer"

  tags = {
    LogType   = "cost"
    ManagedBy = "Terraform"
  }
}

resource "aws_glue_catalog_database" "datadog_log_archives" {
  name        = "datadog_log_archives"
  description = "Datadog application logs archive"

  tags = {
    LogType   = "application"
    ManagedBy = "Terraform"
  }
}

テーブル定義例(プロダクト別S3バケットを参照):

テーブル定義のTerraformコード例
hcl
# access_logsデータベース内のテーブル(プロダクト別にS3バケットが異なる)
resource "aws_glue_catalog_table" "payment_service_alb" {
  database_name = aws_glue_catalog_database.access_logs.name
  name          = "payment_service_alb"

  storage_descriptor {
    location = "s3://payment-service-logs/alb/"  # ← payment-service専用S3バケット

    input_format  = "org.apache.hadoop.mapred.TextInputFormat"
    output_format = "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"

    ser_de_info {
      serialization_library = "org.apache.hadoop.hive.serde2.RegexSerDe"
    }

    columns {
      name = "type"
      type = "string"
    }
    columns {
      name = "time"
      type = "string"
    }
    # ... ALBログのカラム定義
  }

  partition_keys {
    name = "dt"
    type = "string"
  }
}

# datadog_log_archivesデータベース内のテーブル
resource "aws_glue_catalog_table" "payment_service_datadog" {
  database_name = aws_glue_catalog_database.datadog_log_archives.name
  name          = "payment_service_datadog"

  storage_descriptor {
    location = "s3://datadog-archives-prod/payment-service/"  # ← プロダクト専用パス

    input_format  = "org.apache.hadoop.mapred.TextInputFormat"
    output_format = "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"

    ser_de_info {
      serialization_library = "org.openx.data.jsonserde.JsonSerDe"
    }

    columns {
      name = "message"
      type = "string"
    }
    columns {
      name = "level"
      type = "string"
    }
    # ... Datadogログのカラム定義
  }

  partition_keys {
    name = "dt"
    type = "string"
  }
}

# audit_logsデータベース内のテーブル(全プロダクト共通)
resource "aws_glue_catalog_table" "common_cloudtrail" {
  database_name = aws_glue_catalog_database.audit_logs.name
  name          = "common_cloudtrail"

  storage_descriptor {
    location = "s3://common-audit-logs/cloudtrail/"  # ← 共通監査ログ
    # ... CloudTrailのスキーマ定義
  }
}

重要なポイント:

  • 同じデータベース内でも、各テーブルは異なるS3バケットを参照可能
  • プロダクト別にS3バケットを分けることで、IAMでアクセス制御
  • 監査ログなど共通ログは、共有S3バケットを参照

テーブル管理の原則

テーブル命名規則

プロダクトとログソースが明確にわかる命名を使用:

{product}_{log_source}

例(access_logsデータベース内):
- payment_service_alb      # payment-serviceのALBアクセスログ
- online_service_alb       # online-serviceのALBアクセスログ
- common_cloudfront        # 共通のCloudFrontアクセスログ

例(datadog_log_archivesデータベース内):
- payment_service_datadog  # payment-serviceのDatadogログ
- online_service_datadog   # online-serviceのDatadogログ

例(audit_logsデータベース内):
- common_cloudtrail        # 全プロダクト共通のCloudTrail
- common_vpc_flow          # 全プロダクト共通のVPCフローログ
- common_s3_access         # 全プロダクト共通のS3アクセスログ

例(cost_monitoringデータベース内):
- cur_data                 # Cost and Usage Report
- cost_explorer_data       # Cost Explorerベースコスト集計
- athena_usage             # Athena使用状況(メタ分析)

命名の原則:

  • プロダクト固有ログ: {product}_{log_source}形式
  • 共通ログ: common_{log_source}形式
  • アンダースコア区切り: 単語の区切りにはアンダースコア(_)を使用
    • 注意: AWS Glue Data Catalogではハイフン(-)は使用不可
    • データベース名・テーブル名: 小文字、数字、アンダースコア(_)のみ使用可能
テーブル定義の方法

Terraform + パーティションプロジェクションを採用する

FastDoctorでは、以下の理由からTerraform + パーティションプロジェクションの組み合わせを採用します:

  • インフラコード管理: Terraformで全てのテーブル定義を管理し、再現性・変更履歴を確保
  • コスト効率: Glue Crawlerのコストが不要
  • 即座に利用可能: Crawler実行を待たずに、テーブル作成後すぐにクエリ可能
  • パーティション自動認識: 日付ベースの規則的なパーティション構造でCrawler不要
テーブルのタグ付け

Glue Catalogテーブルにはタグを付与できますが、Athenaクエリのコスト配分には使用できません。 Athenaのコスト配分はワークグループのタグで行います。

テーブルのタグは以下の管理目的で使用します:

  • データの分類と検索性向上
  • データカタログの整理
  • データガバナンスポリシーの適用

注意:

  • Glueテーブルのタグは、Glue Crawlerやデータカタログ管理のコストには影響しますが、Athenaクエリのコストには影響しません
  • Athenaのクエリコスト配分は、ワークグループのタグ(Product、CostCenter等)で実施します

メタデータ管理のベストプラクティス

1. テーブルコメントの活用
sql
CREATE EXTERNAL TABLE logs.alb_access_logs (
  type string COMMENT 'Type of log (http/https)',
  time string COMMENT 'Time when ALB received the request',
  -- ...
)
COMMENT 'ALB access logs for production environment'
PARTITIONED BY (dt string COMMENT 'Date in YYYY-MM-DD format')
LOCATION 's3://bucket/logs/'
2. スキーマバージョニング

FastDoctorでは、基本的にログフォーマット変更は想定していません。ただし、アプリケーションログのフォーマットが変更された場合は、以下の方針で対応します:

対応方針:

  1. パーティションで分ける: schema_versionをパーティションキーとして追加
  2. 統合Viewを活用: 複数バージョンを統合したViewを作成し、ユーザーは単一Viewをクエリ

スキーマバージョニングのデメリット:

  • ①複雑性増加: 複数テーブル・ビュー管理の負担
  • ②クエリ複雑化: UNION ALL等の複雑なクエリが必要
  • ③データ移行の手間: バージョンアップ時の作業負荷
  • ④コスト増: 複数テーブルのスキャンによるコスト増加
  • ⑤運用ミスリスク: 誤ったバージョンへの書き込み等

そのため、可能な限りスキーマバージョニングは避け、後方互換性を保つスキーマ設計を採用します。

3. 定期的なメタデータ監査(将来的な検討事項)

実施検討のトリガー: 以下のいずれかに該当する場合、メタデータ監査の導入を検討してください:

  1. テーブル数が100を超えた時
    • 管理が複雑化し、未使用テーブルの識別が困難に
  2. 単一テーブルのパーティション数が10,000を超えた時
    • Glue APIの応答遅延が発生
    • クエリ実行前のメタデータ取得に数秒かかる
  3. Glue APIの無料枠(月間100万リクエスト)を超えそうな時
    • パーティション数が多いと、クエリのたびに大量のAPI呼び出しが発生
  4. S3とメタデータの不一致が頻発する時
    • S3でライフサイクル削除されたデータのパーティション情報が残る
    • クエリ実行時に「ファイルが見つかりません」エラーが多発

Glue Crawlerの使用方針

FastDoctorでは、Glue Crawlerを継続的に使用しません。以下の方針で運用します:

基本方針:

  • テーブル定義: Terraformによる手動定義 + パーティションプロジェクション
  • Crawlerの使用: 初回のスキーマ調査のみ(1回限り)
    • 新しいログソースのスキーマが不明な場合
    • スキーマ確認後、Terraformコードに反映して以降はCrawler停止

理由:

  • コスト効率: Crawlerは時間課金(DPU-Hour単位)
  • 制御性: 手動定義で完全な制御と再現性を確保
  • 即座にクエリ可能: パーティションプロジェクションで、Crawler実行待ち不要

運用上の注意点:

  • 不要なCrawlerを稼働させない: 定期実行されているCrawlerがないか定期確認
  • メタデータ変更の防止: Crawler実行時の予期しないスキーマ上書きに注意

Glue Data Catalogの権限管理

テーブルレベルでのアクセス制御:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "glue:GetDatabase",
        "glue:GetTable",
        "glue:GetTables",
        "glue:GetPartition",
        "glue:GetPartitions"
      ],
      "Resource": [
        "arn:aws:glue:ap-northeast-1:ACCOUNT_ID:catalog",
        "arn:aws:glue:ap-northeast-1:ACCOUNT_ID:database/${env}_logs",
        "arn:aws:glue:ap-northeast-1:ACCOUNT_ID:table/${env}_logs/alb_access_logs"
      ]
    }
  ]
}

3. 権限設計

FastDoctorのアクセス制御戦略

FastDoctorでは、3層のアクセス制御を組み合わせて、プロダクト別のデータアクセスを管理します:

【Layer 1: ワークグループアクセス】
IAM: athena:StartQueryExecution on workgroup ARN
→ どのワークグループを使用できるか(コスト追跡用)

【Layer 2: データカタログアクセス】
IAM: glue:GetDatabase/GetTable on database/table ARN
→ どのデータベース・テーブルのメタデータを参照できるか
   設計方針: 全テーブルのメタデータを公開(データディスカバリー促進)

【Layer 3: S3データアクセス】← ★最重要★
IAM: s3:GetObject on bucket ARN
→ どのS3バケットの実データを読めるか
   設計方針: 厳密に制御(自プロダクトと共通ログのみ)

FastDoctorのセキュリティ設計思想

データカタログは全公開、S3で厳密に制御

  1. Layer 2(データカタログ)は全公開:

    • 理由: メタデータ(テーブル名、カラム名、S3ロケーション)は機密情報ではない
    • メリット:
      • データディスカバリー: 社内でどんなデータがあるか探索可能
      • 管理の簡素化: テーブル追加時にIAMポリシー更新不要
      • コラボレーション促進: チーム間のデータ共有が容易
    • セキュリティ: 実データにアクセスできなければ問題ない
  2. Layer 3(S3)で制御:

    • 実データのアクセス権限はS3バケット単位で厳密に制御
    • IAMロールに紐付けたS3バケットポリシーで、どのプロダクトのログを読めるか制限
    • 個人情報・機密情報を含む可能性があるログへのアクセスを防ぐ

効果:

sql
-- payment-serviceのユーザーが実行

-- ✅ 成功: 全プロダクトのテーブル構造は見える(メタデータ)
DESCRIBE datadog_log_archives.online_service_datadog;

-- ❌ 失敗: 他プロダクトのS3バケットにはアクセスできない(実データ)
SELECT * FROM datadog_log_archives.online_service_datadog LIMIT 10;
-- エラー: Access Denied (S3バケットへの権限なし)

-- ✅ 成功: 自プロダクトのログにもアクセス可能
SELECT * FROM datadog_log_archives.payment_service_datadog LIMIT 10;

重要:

  • メタデータは見えるが、実データは見えない = セキュアなデータディスカバリー
  • テーブル定義やスキーマは全員が見られるが、S3の実データは権限がないと読めない
  • 横断分析が必要な場合は、共通ログテーブルを用意し、そのS3バケットへのアクセスを許可

プロダクト単位でのアクセス制御

設計方針:

  • プロダクト単位でワークグループとIAMロールを分離
    • 事業部単位ではなく、プロダクト単位で制御
    • 理由: 組織変更(事業部再編、チーム移動等)を考慮し、プロダクトを軸とする

運用方針:

  • 必要になった際に作成・追加:
    • 全プロダクト分を事前作成せず、分析ニーズが発生した際に作成
    • 理由①: 使用頻度が低い(Athenaでの分析は日常的ではなく、必要時のみ実施)
    • 理由②: プロダクト数が多く、事前作成すると管理が煩雑
    • 理由③: IAMロールをIAMグループに追加できる上限は10個まで(AWS制約 IAM クォータ
    • 分析を開始するプロダクトのみ、ワークグループとIAMロールを作成

プロダクト別IAMロール構成(参考実装)

以下は、プロダクト別IAMロールの実装例です。実際の運用では、分析ニーズが発生した際に作成・追加します。

payment-service用IAMロール(実装例)

payment-service Athenaアクセスポリシー(Terraform)
hcl
resource "aws_iam_role_policy" "payment_service_athena_access" {
  name = "athena-data-access"
  role = aws_iam_role.payment_service_analyst.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      # Layer 1: ワークグループアクセス
      {
        Effect = "Allow"
        Action = [
          "athena:StartQueryExecution",
          "athena:GetQueryExecution",
          "athena:GetQueryResults",
          "athena:StopQueryExecution",
          "athena:GetWorkGroup"
        ]
        Resource = "arn:aws:athena:ap-northeast-1:${var.account_id}:workgroup/analytics-payment-service"
      },

      # Layer 2: データカタログアクセス(全テーブル公開)
      {
        Effect = "Allow"
        Action = [
          "glue:GetDatabase",
          "glue:GetTable",
          "glue:GetPartitions"
        ]
        Resource = [
          "arn:aws:glue:ap-northeast-1:${var.account_id}:catalog",
          # 全データベース・全テーブルのメタデータ参照可能
          "arn:aws:glue:ap-northeast-1:${var.account_id}:database/*",
          "arn:aws:glue:ap-northeast-1:${var.account_id}:table/*/*"
        ]
      },

      # Layer 3: S3データアクセス(厳密に制御)← ★ここが重要★
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:ListBucket"
        ]
        Resource = [
          # 自プロダクトのログバケット
          "arn:aws:s3:::payment-service-logs/*",
          "arn:aws:s3:::payment-service-logs",
          # 自プロダクトの分析結果バケット
          "arn:aws:s3:::payment-service-analytics/*",
          "arn:aws:s3:::payment-service-analytics",
          # 自プロダクトのDatadogログ
          "arn:aws:s3:::datadog-archives-prod/payment-service/*"
        ]
      },

      # クエリ結果の書き込み(自分のプレフィックスのみ)
      {
        Effect = "Allow"
        Action = ["s3:PutObject", "s3:GetObject"]
        Resource = "arn:aws:s3:::athena-query-results/payment-service/*"
      }
    ]
  })
}

横断分析が必要な場合:

  • 共通ログ(CloudTrail、VPCフローログ等)へのアクセスが必要な場合は、個別にIAMポリシーを拡張
  • 管理者用の特権ロールを使用

横断分析用IAMロール(管理者)

用途: 全プロダクトのデータや共通ログへの横断分析が必要な場合のみ使用する特権ロール

注意:

  • 個人情報・機密情報への広範なアクセス権限を持つため、必要最小限のユーザーにのみ付与
  • 監査ログの定期的なレビューが必須
管理者用ポリシー(横断分析用)
hcl
resource "aws_iam_role_policy" "data_engineer_athena_access" {
  name = "athena-full-access"
  role = aws_iam_role.data_engineer.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      # 全ワークグループにアクセス可能
      {
        Effect = "Allow"
        Action = [
          "athena:StartQueryExecution",
          "athena:GetQueryExecution",
          "athena:GetQueryResults",
          "athena:StopQueryExecution",
          "athena:GetWorkGroup"
        ]
        Resource = "arn:aws:athena:ap-northeast-1:${var.account_id}:workgroup/analytics-*"
      },

      # 全データベース・テーブルにアクセス可能
      {
        Effect = "Allow"
        Action = [
          "glue:GetDatabase",
          "glue:GetTable",
          "glue:GetPartitions",
          "glue:CreateTable",
          "glue:UpdateTable",
          "glue:DeleteTable"
        ]
        Resource = [
          "arn:aws:glue:ap-northeast-1:${var.account_id}:catalog",
          "arn:aws:glue:ap-northeast-1:${var.account_id}:database/*",
          "arn:aws:glue:ap-northeast-1:${var.account_id}:table/*/*"
        ]
      },

      # 全プロダクトのS3バケットにアクセス可能
      {
        Effect = "Allow"
        Action = ["s3:GetObject", "s3:ListBucket", "s3:PutObject"]
        Resource = [
          "arn:aws:s3:::*-service-logs/*",
          "arn:aws:s3:::*-service-analytics/*",
          "arn:aws:s3:::datadog-archives-*/*",
          "arn:aws:s3:::athena-query-results/*"
        ]
      }
    ]
  })
}

最小権限の原則

  1. ワークグループ: プロダクトごとに専用ワークグループ(コスト追跡)
  2. データカタログ: 全テーブルのメタデータ参照可能(データディスカバリー促進)
  3. S3アクセス: 自プロダクトのバケットのみ(実データの厳密な制御)← ★最重要★
    • 個人情報・機密情報を含む可能性があるため、リソースごとに分析対象を制限
    • 横断分析が必要な場合は、個別にIAMポリシーを拡張
  4. クエリ結果: 自プロダクトのプレフィックスのみ書き込み可能

設計思想:

  • メタデータは公開(データディスカバリー促進)
  • 実データは厳密に制御(個人情報・機密情報保護)
  • プロダクト単位で制御(組織変更を考慮)
  • 必要になった際に作成・追加(管理の簡素化、IAMグループ上限対策)

クロスアカウントアクセスについて

FastDoctorでは、クロスアカウントアクセスは使用しません。

理由:

  • セキュリティリスク: 本番環境の機密データ(個人情報、医療情報、決済情報等)を他のアカウントから参照できると、セキュリティインシデントのリスクが高まる
  • 管理の複雑化: クロスアカウントIAMロールやS3バケットポリシーの管理が複雑になり、誤設定のリスクが高い
  • 監査の困難性: どのアカウントからアクセスされたかの追跡が複雑になる

4. セキュリティ設計

データ暗号化(保存時・転送時)

S3バケットの暗号化

  • SSE-S3: デフォルトの暗号化(AES256)
  • SSE-KMS: KMSキーによる暗号化(監査ログが必要な場合)
  • クライアント側暗号化: 高度なセキュリティ要件が必要な場合

転送時の暗号化

  • HTTPS必須: S3バケットポリシーでHTTPS通信のみ許可
  • VPCエンドポイント: インターネット経由を避ける

VPCエンドポイント利用方針

Athena用VPCエンドポイント

  • プライベートアクセス: VPC内からのみAthenaにアクセス
  • インターネット不要: NAT Gateway不要でコスト削減
  • セキュリティグループ: 必要なサービスからのアクセスのみ許可

アクセス制御の基本方針

データアクセス制御

  • IAMポリシー: ロールベースのアクセス制御
  • S3バケットポリシー: バケットレベルの制御

クエリ制御

  • ワークグループ: プロジェクトごとにワークグループを分離
  • データ使用量制限: ワークグループごとにスキャンデータ量の上限設定

監査ログ

CloudTrail統合

  • Athenaの操作記録: すべてのAthena APIコールをCloudTrailで記録
  • S3アクセスログ: ログバケットのアクセスを記録
  • Glue操作ログ: Data Catalogの変更履歴を記録

5. キャパシティ設計

FastDoctorでは、Athenaのサービスクォータ、データ量、クエリ実行数を考慮したキャパシティ設計を行います。

AWSサービスクォータ(制限値)

Athenaのクォータ

重要な制限値:

項目デフォルト値調整可否影響範囲
同時実行DDLクエリ数20クエリ/アカウント✅ 申請可能CREATE TABLE、ALTER TABLE等
同時実行DMLクエリ数25クエリ/アカウント✅ 申請可能SELECT、INSERT等
アクティブなDDLクエリ数20クエリ/ワークグループ❌ 固定DDL操作の並列度
アクティブなDMLクエリ数25クエリ/ワークグループ❌ 固定SELECT等の並列度
クエリ文字列サイズ262,144バイト(256KB)❌ 固定SQLクエリの最大長
クエリ実行タイムアウト30分(デフォルト)✅ ワークグループ設定で変更可能最大30分まで

パーティション関連:

  • 1テーブルあたりのパーティション数: 制限なし(ただし10,000パーティション以上で性能劣化)
  • MSCK REPAIR TABLEで追加可能なパーティション数: 3,000パーティション/実行

参考: Amazon Athena のクォータ

対策

DDLクエリ数対策:

  • Terraform適用: テーブル作成は順次実行(並列度1-2)
  • 理由: DDLクエリは20クエリ/アカウントの制限があるため、terraform applyで大量のテーブルを一度に作成する場合は注意

パーティション設計とクエリ上限設定

パーティション設計の上限

パーティション数による影響:

パーティション数Glue API応答時間影響
〜1,000<1秒影響なし
1,000〜10,0001-3秒軽微な遅延
10,000〜50,0003-10秒クエリプランニングが遅延
50,000〜10秒以上大幅な遅延、推奨しない

FastDoctorのパーティション設計例:

ログソースパーティション粒度パーティション数(3年)評価
ALBログyear/month/day/hour26,280パーティション⚠️ 要注意
Datadogログyear/month/day1,095パーティション✅ 問題なし
CloudTrailyear/month/day1,095パーティション✅ 問題なし
VPCフローログyear/month/day1,095パーティション✅ 問題なし

対策:

  • 時間粒度パーティション(hour)は避ける: 日粒度(day)までに留める
  • パーティションプロジェクション使用: Glue APIを呼ばずにパーティション解決
  • 古いパーティションの削除: 保存期間を超えたデータは定期削除

ワークグループのスキャン上限設定

FastDoctorでは、プロダクト別にワークグループを作成し、クエリごとのデータスキャン上限を設定します(bytes_scanned_cutoff_per_query)。

必須機能: クエリ自動停止

ワークグループの設定で、上限を超えるクエリを自動的にキャンセルします。

  • 設定項目: bytes_scanned_cutoff_per_query(範囲: 10MB~7EB)
  • 動作: クエリが上限を超えた場合、実行を自動停止(この動作は変更不可)
  • 推奨値: 1TB/クエリ(誤操作による高額請求を防止)
  • 課金: キャンセルされたクエリもスキャンしたデータ量に応じて課金される

実装例: ワークグループ設定指針のTerraformコード例を参照

参考: AWS公式ドキュメント - Configure per-query and per-workgroup data usage controls

上限超過時の対応:

  1. クエリ失敗: ユーザーにエラーメッセージが返される
  2. 原因調査: パーティション指定漏れ、不要なフルスキャン等を確認
  3. 上限見直し: 正当な分析で上限に達する場合は設定値を引き上げ

環境別のキャパシティ設定

FastDoctorでは環境(AWSアカウント)ごとに異なるキャパシティ設定を行います。

開発環境(infra-dev)

特徴:

  • テーブル定義のテスト用
  • データ量は少ない(サンプルデータ)
  • クエリ実行頻度は低い

設定:

  • スキャン上限: 10GB/クエリ(低く設定してコスト抑制)
  • 同時実行数: デフォルトクォータ内(5-10クエリ/時間程度)

本番環境(production)

特徴:

  • 実際のログデータ分析
  • データ量は大きい(数TB規模)
  • クエリ実行頻度は中程度

設定:

  • スキャン上限: 1TB/クエリ(プロダクトごとに設定)
  • 同時実行数: デフォルトクォータ内(20-50クエリ/時間程度)

キャパシティ拡張が必要になるケース

ケース1: 同時実行クエリ数の超過

症状:

  • クエリが頻繁にキューイングされる
  • CloudWatchメトリクス QueryQueueTime が増加

対応:

  1. クエリ実行時間の分散: 定期レポートの実行時間をずらす
  2. クォータ引き上げ申請: AWS Support Centerから申請

申請例:

項目: 同時実行DMLクエリ数
現在: 25クエリ/アカウント
希望: 50クエリ/アカウント
理由: 複数プロダクトでの定期レポート実行により、ピーク時にキューイングが発生

ケース2: パーティション数の増加

症状:

  • SHOW PARTITIONS が遅い(10秒以上)
  • クエリプランニングに時間がかかる

対応:

  1. パーティションプロジェクション導入: Glue APIを呼ばずにパーティション解決
  2. 古いパーティション削除: 保存期間を超えたデータを削除
  3. パーティション粒度の見直し: 時間粒度→日粒度に変更

ケース3: データ量の急増

症状:

  • S3ストレージコストが急増
  • クエリ実行時間が長くなる

対応:

  1. データ圧縮: Gzip、Parquet等への変換
  2. 不要なログの削除: 分析対象外のログを削除
  3. ライフサイクルポリシー見直し: Glacier移行期間の短縮

モニタリング項目と実装方法

キャパシティ設計が適切か定期的に確認するためのモニタリング項目と、その具体的な実装方法を説明します。

アプローチ:

  • Datadog優先: 既存のDatadogインフラを活用し、CloudWatchメトリクスをDatadogに統合
  • CloudWatch補完: Datadog未対応の一部機能はCloudWatchで実装

モニタリング項目一覧

FastDoctorの段階的監視戦略:

  1. Phase 1(必須): スキャン量の監視から開始
  2. Phase 2(オプション): 運用しながら必要になった監視項目を追加
優先度項目メトリクス閾値アクション
🔴 必須スキャン量ProcessedBytes>80GB/5分パーティション設計見直し、クエリ最適化
🟡 オプションクエリ実行時間EngineExecutionTime>10分インデックス・パーティション最適化
🟡 オプション同時実行数QueryQueueTime>30秒クエリ実行時間の分散、クォータ引き上げ
🟡 オプションパーティション数Glue API GetPartitions>10,000パーティションプロジェクション導入

Phase 1で監視する理由:

  • スキャン量: コストに直結するため、異常なスキャンを早期検知(最優先)

Phase 2で追加検討する項目:

  • クエリ実行時間: タイムアウト(30分上限)の懸念がある場合、または日常的に利用する場合に追加
  • 同時実行数: クエリ実行が頻繁になり、キューイングが発生する場合
  • パーティション数: データ量が増加し、パーティション管理が必要になった場合

Datadogでの監視

FastDoctorでは既にDatadogを使用しているため、AthenaのメトリクスもDatadogで監視します。

DatadogでのAthena監視方法:

  1. CloudWatchインテグレーション: CloudWatchメトリクスを自動収集
  2. カスタムメトリクス: パーティション数などをLambdaから送信
  3. Datadog Monitors: アラート設定
  4. Datadog Dashboards: 可視化
DatadogのCloudWatchインテグレーション設定

DatadogのAWSインテグレーションでAthenaメトリクスを収集します。Datadog側でAWSインテグレーションを設定し、以下のネームスペースを有効化してください:

  • AWS/Athena
  • AWS/S3
  • AWS/Glue(オプション)
Datadogで監視するメトリクス

FastDoctorの段階的監視戦略に基づく監視対象メトリクス:

優先度メトリクスDatadog対応取得方法用途
🔴 Phase 1(必須)
スキャン量aws.athena.processed_bytes✅ 対応CloudWatch経由で自動収集コスト監視
🟡 Phase 2(オプション)
クエリ実行時間aws.athena.engine_execution_time✅ 対応CloudWatch経由で自動収集パフォーマンス監視
キュー待機時間aws.athena.query_queue_time✅ 対応CloudWatch経由で自動収集同時実行制限の検知
バケットサイズaws.s3.bucket_size_bytes✅ 対応CloudWatch経由で自動収集ストレージ容量監視
パーティション数-❌ 非対応CloudWatchで実装を検討
Lambda + Glue API + CloudWatch PutMetricData
パーティション数管理

パーティション数の監視について:

  • Datadogにはパーティション数メトリクスが存在しないため、CloudWatchでの実装を検討
  • Lambda + Glue API + CloudWatch PutMetricDataでカスタムメトリクスを作成
  • 必要に応じてCloudWatch Alarmで監視

公式ドキュメント:

Datadog Monitorsでのアラート設定

注意: 以下の閾値は初期設定の例です。実際の運用状況に応じて調整してください。 監視設定の検証と最適化については SRE-1656 で実施します。

Phase 1(必須): 最初に設定すべきモニター

  1. データスキャン量の監視(5分あたり)
    • メトリクス: aws.athena.processed_bytes
    • Critical閾値: 80GB/5分(約$4.8/時間)※運用しながら調整
    • Warning閾値: 50GB/5分(約$3/時間)※運用しながら調整
    • 評価期間: last_5m

Phase 2(オプション): 運用しながら必要に応じて追加

  1. クエリ実行時間の監視

    • メトリクス: aws.athena.engine_execution_time
    • Critical閾値: 10分(600,000ms)※運用しながら調整
    • Warning閾値: 5分(300,000ms)※運用しながら調整
    • 評価期間: last_5m
    • 追加条件: タイムアウト(30分上限)の懸念がある場合、または日常的に利用する場合
  2. クエリキューイング時間の監視

    • メトリクス: aws.athena.query_queue_time
    • Critical閾値: 30秒(運用状況に応じて調整)
    • Warning閾値: 15秒(運用状況に応じて調整)
    • 追加条件: クエリ実行が頻繁になり、キューイングが発生する場合
  3. S3バケットサイズの監視

    • メトリクス: aws.s3.bucket_size_bytes
    • Critical閾値: 見積もりの120%(運用状況に応じて調整)
    • Warning閾値: 見積もりの100%(運用状況に応じて調整)
    • 追加条件: データ量が急増する懸念がある場合

6. コスト最適化の基本方針

スキャンデータ量削減の考え方

Athenaの課金モデル:

  • スキャンしたデータ量: $5.00 per TB(ap-northeast-1)
  • クエリ失敗: 課金なし(構文エラー等)
  • メタデータ操作: 無料

コスト削減テクニック

  1. パーティションプルーニング: 必要な日付のパーティションのみスキャン
  2. カラム選択: SELECT * を避け、必要なカラムのみ指定
  3. データ圧縮: Parquet等の列指向フォーマット使用
  4. クエリ結果の再利用: 同じクエリは結果をキャッシュ
sql
-- ❌ コストが高い
SELECT * FROM logs WHERE date >= '2024-01-01';

-- ✅ コストを削減
SELECT user_id, request_time, status_code
FROM logs
WHERE year=2024 AND month=1 AND day BETWEEN 1 AND 7;

クエリ結果の再利用

結果のキャッシング

  • 24時間以内: 同一クエリは自動的にキャッシュから返却(追加課金なし)
  • CTAS活用: 頻繁に使うクエリ結果をテーブル化
sql
-- CTASで結果をテーブル化
CREATE TABLE analytics.daily_summary
WITH (
  format = 'PARQUET',
  external_location = 's3://analytics/daily_summary/'
)
AS
SELECT
  date_trunc('day', timestamp) as day,
  COUNT(*) as request_count,
  AVG(response_time) as avg_response_time
FROM logs
WHERE year=2024 AND month=1
GROUP BY date_trunc('day', timestamp);

キャッシュ戦略詳細

Athenaのクエリ結果キャッシュを効果的に活用することで、コスト削減とクエリ性能向上を同時に実現できます。

Athenaの結果キャッシュ仕組み

Athenaは以下の条件を満たす場合、自動的にクエリ結果をキャッシュから返却します:

キャッシュヒット条件:

  1. 有効期間内: デフォルト60分、ワークグループ設定で最大7日間まで延長可能
  2. クエリの一致:
    • 100KB未満のクエリ: コメントと空白の違いは無視される
    • 100KB以上のクエリ: 完全一致が必須
  3. 同じワークグループ: 同じワークグループからの実行
  4. 参照テーブル数: クエリで参照するテーブルが20個以下
  5. 決定論的クエリ: rand(), now() 等の非決定論的関数を含まない
  6. SELECT/EXECUTEステートメント: DDL文などはキャッシュ対象外

重要な注意点:

  • Athenaはキャッシュ有効期間中、ソースデータ(S3)の変更をチェックしません
  • S3のデータが更新されても、キャッシュが有効な間は古いデータが返される可能性があります
  • リアルタイム性が求められる場合は、キャッシュ有効期間を短く設定するか、クエリ文字列を変更してキャッシュを無効化してください

キャッシュ利用のメリット:

  • コストゼロ: キャッシュヒット時はスキャンデータ量が0
  • 高速レスポンス: 数秒でクエリ結果を取得可能
  • S3アクセスなし: S3へのアクセスが発生しない

参考: Reusing query results - Amazon Athena

キャッシュを活用した運用パターン

パターン1: 定期レポートのキャッシュ活用

前提: ワークグループのキャッシュ有効期間を24時間に設定

シナリオ: 毎朝9:00に日次レポートを生成し、チームメンバーが確認

1. 9:00: スケジュールクエリが実行(初回、キャッシュなし)
   → S3スキャン、コスト発生
2. 9:15: メンバーAが同じレポートを確認
   → キャッシュヒット、コスト0
3. 11:00: メンバーBが同じレポートを確認
   → キャッシュヒット、コスト0
4. 翌日9:00: 再度スケジュールクエリ実行
   → キャッシュ期限切れ、新しいデータでスキャン

コスト削減: 3回実行で1回分のコストのみ

注意: デフォルトのキャッシュ有効期間は60分です。このパターンを使用する場合は、ワークグループ設定で期間を延長してください。

パターン2: クエリIDを使った結果再利用
python
import boto3

client = boto3.client('athena')

# 初回クエリ実行
response = client.start_query_execution(
    QueryString='SELECT * FROM logs WHERE dt = "2024-01-01"',
    WorkGroup='analytics-payment-service'  # プロダクト別ワークグループ
)
query_id = response['QueryExecutionId']

# クエリIDを保存(例: DynamoDB、S3等)
save_query_id(query_id)

# 後続の処理で同じ結果が必要な場合
cached_query_id = get_query_id()
result = client.get_query_results(QueryExecutionId=cached_query_id)
パターン3: CTASによる中間テーブル作成

頻繁に使用する集計結果をテーブル化し、再利用:

CTAS(Create Table As Select)の実装例
sql
-- ステップ1: 重い集計をCTASで実行(1回のみ)
CREATE TABLE analytics.daily_user_summary
WITH (
  format = 'PARQUET',
  external_location = 's3://analytics/daily_user_summary/',
  partitioned_by = ARRAY['dt']
)
AS
SELECT
  user_id,
  COUNT(*) as request_count,
  AVG(response_time) as avg_response_time,
  dt
FROM logs
WHERE year=2024 AND month=1
GROUP BY user_id, dt;

-- ステップ2: 軽量なクエリで再利用(何度でも)
SELECT * FROM analytics.daily_user_summary
WHERE dt = '2024-01-15' AND request_count > 100;

コスト比較:

  • 元テーブルから毎回集計: 500GB スキャン × 10回 = 5TB = $25
  • CTAS作成 + 軽量クエリ: 500GB + (10MB × 10回) = 500.1GB = $2.50

キャッシュを最大限活用するベストプラクティス

1. クエリの標準化

クエリテキストを完全一致させるため、標準化されたクエリを使用:

sql
-- ❌ キャッシュミス(空白が異なる)
SELECT  *  FROM  logs  WHERE  dt='2024-01-01'

-- ❌ キャッシュミス(改行が異なる)
SELECT *
FROM logs
WHERE dt='2024-01-01'

-- ✅ キャッシュヒット(標準フォーマット)
SELECT * FROM logs WHERE dt = '2024-01-01'

推奨: クエリをファイルやテンプレートとして管理し、同じフォーマットで実行

2. パラメータ化されたクエリの管理
python
# クエリテンプレートを定義
QUERY_TEMPLATE = """
SELECT user_id, COUNT(*) as cnt
FROM logs
WHERE dt = '{date}'
GROUP BY user_id
"""

# 同じフォーマットでクエリを生成
query = QUERY_TEMPLATE.format(date='2024-01-01')
3. ワークグループの統一

同じレポートを実行するユーザーは同じワークグループを使用:

4. 結果の有効期限管理

キャッシュの有効期限を考慮したスケジューリング(例: ワークグループ設定で24時間に延長した場合):

日次レポート: 毎朝6:00に実行

チームメンバーは6:00〜翌日6:00まで同じ結果を参照(キャッシュ活用)

注: デフォルトの60分では不十分な場合、ワークグループ設定で延長してください(最大7日間)

キャッシュが効かないケース

以下の場合、キャッシュは利用されず、新たにクエリが実行されます:

  1. キャッシュ有効期間経過: 前回実行からキャッシュ有効期間(デフォルト60分、最大7日間)が経過
  2. クエリ変更: クエリテキストが1文字でも異なる
  3. データ変更: 元データ(S3)が変更された
  4. 異なるワークグループ: 別のワークグループから実行
  5. パーティション追加: 新しいパーティションが追加された
sql
-- 例: パーティション追加でキャッシュ無効化
-- 1. 2024-01-01のパーティションでクエリ実行 → キャッシュ作成
SELECT * FROM logs WHERE dt = '2024-01-01';

-- 2. 新しいパーティション追加
ALTER TABLE logs ADD PARTITION (dt='2024-01-02') ...;

-- 3. 再度同じクエリ実行 → テーブルメタデータ変更によりキャッシュミス
SELECT * FROM logs WHERE dt = '2024-01-01';

キャッシュ戦略の選択指針

ユースケース推奨戦略理由
定期レポート(日次)クエリの標準化 + 24時間キャッシュコスト効率的、シンプル
アドホック分析なし(都度実行)クエリが毎回異なる
ダッシュボードCTAS + 軽量クエリ高速レスポンス、安定したコスト
API経由の定型クエリクエリID保存 + 結果再利用アプリケーション統合が容易
重い集計(週次・月次)CTAS + パーティション再集計を避けコスト削減

クエリ結果の保存先設計

クエリ結果の保存先を適切に設計することで、結果の管理とアクセスを効率化:

s3://{env}-athena-query-results/
├── 2024/01/01/
│   ├── query-abc123.csv
│   └── query-abc123.csv.metadata
├── 2024/01/02/
└── 2024/01/03/

コスト監視

Athenaのコスト監視は、FastDoctorで実施している週次の環境別コストモニタリングで異常を検知できます。

監視内容:

  • 週次でAWS環境別(infra-dev, develop, staging, production)のコスト確認
  • Athenaのクエリコスト、S3ストレージコストを含む全体コストをレビュー
  • 急激な増加や異常値を検知した場合、Datadogの監視データと照らし合わせて原因調査

Datadogとの連携:

  • リアルタイムアラート: Datadogのスキャン量・クエリ実行時間モニター
  • 週次レビュー: 環境別コストモニタリングで全体傾向を把握
  • 異常検知時: Datadogのメトリクスで詳細調査(どのワークグループ、どのクエリが原因か)

7. パフォーマンス設計の基本方針

パーティション設計の原則

適切な粒度

  • 細かすぎる: パーティション数が多すぎるとメタデータ取得が遅延
  • 粗すぎる: スキャンデータ量が増加
  • 推奨: 1パーティションあたり数GB〜数十GB
# ❌ 細かすぎる(時間単位)
s3://bucket/logs/year=2024/month=01/day=01/hour=00/

# ✅ 適切(日単位)
s3://bucket/logs/year=2024/month=01/day=01/

# ✅ 適切(月単位、データ量が少ない場合)
s3://bucket/logs/year=2024/month=01/

パーティション追加方法

sql
-- 手動でパーティション追加
ALTER TABLE logs ADD PARTITION (year=2024, month=1, day=15)
LOCATION 's3://bucket/logs/year=2024/month=01/day=15/';

-- MSCK REPAIR(Hive形式パーティション)
MSCK REPAIR TABLE logs;

ファイルサイズ最適化の考え方

推奨ファイルサイズ

  • 最小: 128MB以上(小さいファイルが多いとオーバーヘッド増加)
  • 最適: 128MB〜1GB
  • 最大: 数GB(大きすぎると並列処理効率が低下)

小さいファイル問題の解決

sql
-- CTAS/INSERTでファイル統合
CREATE TABLE logs_optimized
WITH (
  format = 'PARQUET',
  external_location = 's3://bucket/logs_optimized/',
  bucketed_by = ARRAY['user_id'],
  bucket_count = 10
)
AS SELECT * FROM logs;

ワークグループ設定指針

FastDoctorでのワークグループ構成

FastDoctorでは、プロダクト(サービス)単位でワークグループを作成します。

理由:

  1. コスト可視化: プロダクト別にAthenaのクエリコストを追跡し、事業部別に按分
  2. リソース管理: プロダクトごとにデータスキャン上限を設定し、コスト暴走を防止(詳細は「5. キャパシティ設計 - ワークグループのスキャン上限設定」を参照)
  3. 権限分離: プロダクトチームごとに使用するワークグループを制限可能

構成例:

プロダクト別ワークグループのTerraformコード例
hcl
# プロダクト別ワークグループの定義
locals {
  products = {
    "payment-service" = {
      scan_limit  = 1099511627776  # 1TB
      cost_center = "CC-001"
    }
    "online-service" = {
      scan_limit  = 536870912000   # 500GB
      cost_center = "CC-002"
    }
  }
}

# プロダクトごとにワークグループを作成
resource "aws_athena_workgroup" "products" {
  for_each = local.products

  name = "analytics-${each.key}"

  configuration {
    enforce_workgroup_configuration    = true
    publish_cloudwatch_metrics_enabled = true

    result_configuration {
      output_location = "s3://athena-query-results/${each.key}/"

      encryption_configuration {
        encryption_option = "SSE_S3"
      }
    }

    # プロダクトごとのデータスキャン上限設定
    bytes_scanned_cutoff_per_query = each.value.scan_limit
  }

  tags = {
    Product    = each.key
    CostCenter = each.value.cost_center
    ManagedBy  = "Terraform"
  }
}

注意事項:

  • データベースアクセス制御はワークグループとは独立: データベース・テーブルへのアクセスはIAMロールで制御します(ワークグループに紐づけない)
  • コスト按分の詳細: AWSコスト集計自動化システム設計書を参照

ワークグループの設定項目

  • クエリ結果の場所: 環境ごとの専用S3バケット
  • スキャン上限: コスト暴走を防ぐための上限設定(1TB/クエリ)
  • CloudWatch Metrics: メトリクス公開の有効化
  • 設定の強制: enforce_workgroup_configuration = trueでセキュリティポリシーを統一
  • 結果キャッシュ有効期間: デフォルト60分、AWS CLIまたはコンソールで最大7日間まで延長可能

ビューの活用方法

Athenaのビュー(View)は、複雑なクエリを簡素化し、データアクセス制御を強化し、クエリの再利用性を向上させる強力な機能です。

ビューの基本概念

ビューは、SQLクエリの結果を仮想テーブルとして保存する仕組みです:

sql
CREATE VIEW analytics.daily_error_summary AS
SELECT
  dt,
  service,
  COUNT(*) as error_count,
  COUNT(DISTINCT user_id) as affected_users
FROM logs.application_logs
WHERE level = 'ERROR'
GROUP BY dt, service;

-- ビューを使用したクエリ
SELECT * FROM analytics.daily_error_summary
WHERE dt = '2024-01-01' AND error_count > 100;

ビューの特徴:

  • データコピーなし: 元テーブルへの参照のみ
  • リアルタイム: 常に最新データを参照
  • クエリ時にスキャン: ビュー参照時に元テーブルをスキャン

ビューのユースケース

1. クエリ簡素化

複雑なJOINや集計を隠蔽し、シンプルなクエリで利用:

複雑なJOINを隠蔽したビューの例
sql
-- 複雑なクエリをビューで隠蔽
CREATE VIEW analytics.enriched_access_logs AS
SELECT
  a.request_time,
  a.client_ip,
  a.request_uri,
  a.status_code,
  a.response_time,
  u.user_id,
  u.user_tier,
  s.service_name
FROM logs.alb_access_logs a
LEFT JOIN logs.user_mapping u ON a.client_ip = u.last_known_ip
LEFT JOIN logs.service_mapping s ON regexp_extract(a.request_uri, '/([^/]+)/', 1) = s.service_code;

-- シンプルに利用
SELECT service_name, AVG(response_time)
FROM analytics.enriched_access_logs
WHERE user_tier = 'premium'
GROUP BY service_name;
2. データアクセス制御(カラムレベル)

特定のカラムのみを公開するビューを作成:

sql
-- 機密情報を除外したビュー
CREATE VIEW logs.user_activity_safe AS
SELECT
  user_id,
  action_type,
  timestamp,
  service_name
  -- email, ip_address, phone_numberは除外
FROM logs.user_activity;

アクセス制御: AthenaはGRANT/REVOKE構文をサポートしていません。アクセス制御はIAMポリシーまたはAWS Lake Formationで設定します。詳細は「3. 権限設計」を参照してください。

3. データマスキング

個人情報をマスキングして提供:

sql
CREATE VIEW logs.user_logs_masked AS
SELECT
  user_id,
  CONCAT(SUBSTRING(email, 1, 3), '***@', SUBSTRING(email, POSITION('@' IN email) + 1)) as email_masked,
  CONCAT(SUBSTRING(ip_address, 1, 7), '.xxx.xxx') as ip_masked,
  action_type,
  timestamp
FROM logs.user_logs;
4. スキーマバージョニングの統合

複数バージョンのスキーマを統合:

スキーマバージョンを統合したビューの例
sql
-- v1とv2のスキーマを統合したビュー
CREATE VIEW logs.alb_access_logs AS
SELECT
  time,
  client_ip,
  request_uri,
  status_code,
  'v1' as schema_version
FROM logs.alb_access_logs_v1
WHERE dt < '2024-04-01'

UNION ALL

SELECT
  timestamp as time,
  source_ip as client_ip,
  uri as request_uri,
  status as status_code,
  'v2' as schema_version
FROM logs.alb_access_logs_v2
WHERE dt >= '2024-04-01';
5. 頻繁に使用するフィルタの定義

よく使うフィルタ条件をビューで定義:

sql
-- 本番環境のエラーログのみ
CREATE VIEW logs.production_errors AS
SELECT *
FROM logs.application_logs
WHERE environment = 'production' AND level IN ('ERROR', 'CRITICAL');

-- シンプルに利用
SELECT service, COUNT(*) as error_count
FROM logs.production_errors
WHERE dt = '2024-01-01'
GROUP BY service;

ビューとCTASの使い分け

観点ビュー(VIEW)CTAS(テーブル化)
データ保存なし(参照のみ)あり(S3に保存)
コストクエリ時に毎回スキャンCTAS作成時のみスキャン
鮮度常に最新作成時点のスナップショット
パフォーマンス元テーブルに依存高速(データコピー済み)
用途アクセス制御、簡素化重い集計の再利用

選択指針:

  • ビュー: データ鮮度が重要、軽量なクエリ、アクセス制御が目的
  • CTAS: 重い集計の結果を再利用、パフォーマンス重視

ビュー作成のベストプラクティス

1. 命名規則

ビューとテーブルを区別できる命名:

{database}.{purpose}_view

例:
- analytics.daily_summary_view
- logs.error_logs_filtered_view
- logs.user_activity_masked_view
2. ビューのドキュメント化

ビューの目的をコメントで記述:

sql
-- コメント付きビュー作成
CREATE VIEW analytics.high_value_users_view
COMMENT 'Premium tier users with high activity in the last 30 days'
AS
SELECT ...;
3. パーティションを考慮した設計

ビューでもパーティションプルーニングを活用:

sql
-- パーティションを維持したビュー
CREATE VIEW logs.recent_errors AS
SELECT *
FROM logs.application_logs
WHERE level = 'ERROR' AND dt >= DATE_SUB(CURRENT_DATE, 7);

-- クエリ時にもパーティションプルーニングが効く
SELECT * FROM logs.recent_errors WHERE dt = '2024-01-15';
4. ビューの入れ子は最小限に

ビューからビューを参照すると複雑化し、パフォーマンスが低下:

sql
-- ❌ 避けるべき(入れ子が深い)
CREATE VIEW v3 AS SELECT * FROM v2;
CREATE VIEW v2 AS SELECT * FROM v1;
CREATE VIEW v1 AS SELECT * FROM raw_table;

-- ✅ 推奨(直接参照)
CREATE VIEW v1 AS SELECT * FROM raw_table;

ビューの権限管理

テーブルとビューで異なる権限を設定:

ビュー権限管理のIAMポリシー例
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["glue:GetTable"],
      "Resource": [
        "arn:aws:glue:*:*:table/logs/user_activity_safe"
      ]
    },
    {
      "Effect": "Deny",
      "Action": ["glue:GetTable"],
      "Resource": [
        "arn:aws:glue:*:*:table/logs/user_activity"
      ]
    }
  ]
}

Terraformでのビュー管理

ビューもTerraformで管理可能(DDLファイルとして):

Terraformでのビュー作成コード例
hcl
# ビュー定義をファイルとして管理
resource "null_resource" "create_views" {
  provisioner "local-exec" {
    command = <<EOT
      aws athena start-query-execution \
        --query-string "$(cat ${path.module}/views/error_summary_view.sql)" \
        --result-configuration OutputLocation=s3://${var.results_bucket}/ \
        --work-group ${var.workgroup_name}
    EOT
  }
}

views/error_summary_view.sql:

sql
CREATE OR REPLACE VIEW analytics.error_summary_view AS
SELECT
  dt,
  service,
  COUNT(*) as error_count
FROM logs.application_logs
WHERE level = 'ERROR'
GROUP BY dt, service;

8. 運用方針

モニタリング

監視項目とアラート設定については、「5. キャパシティ設計 - モニタリング項目と実装方法」を参照してください。

段階的監視戦略:

  • Phase 1(必須): データスキャン量の監視
  • Phase 2(オプション): 運用しながら必要に応じてクエリ実行時間、キューイング時間、S3ストレージ、パーティション数を追加

参考メトリクス:

  • ProcessedBytes: スキャンデータ量(コスト直結)
  • EngineExecutionTime: クエリ実行時間(パフォーマンス)
  • TotalExecutionTime: 合計実行時間(待機時間含む)
  • QueryQueueTime: クエリのキュー待機時間
  • QueryPlanningTime: クエリ計画の処理時間

: クエリの成功/失敗率は、CloudWatchメトリクスとして提供されていません。必要な場合はCloudTrailログまたはAthena Query Historyから集計してください。

定期メンテナンス

パーティション管理

  • 自動パーティション追加: AWS Glue Crawlerまたはパーティションプロジェクション
  • 不要パーティションの削除: 保持期間を過ぎたパーティションの削除

クエリ結果のクリーンアップ

統計情報の更新

コストベースオプティマイザ(CBO)とは

Athenaの**コストベースオプティマイザ(CBO: Cost-Based Optimizer)**は、AWS Glue Data Catalogに格納された統計情報を使用して、クエリの実行計画を最適化する機能です。

仕組み:

  1. AWS Glue Data Catalogにテーブルの統計情報(カラムごと)を保存
  2. クエリ実行時、Athenaがこの統計情報を参照
  3. 複数の実行計画を評価し、最も効率的なプランを自動選択

統計情報の内容:

  • Distinct値の数(NDV: Number of Distinct Values)
  • NULL値の数
  • 最大値・最小値
CBOのメリット
  1. パフォーマンス向上: AWS TPC-DSベンチマークで最大2倍高速化、全体で平均11%のパフォーマンス改善
  2. JOIN順序の自動最適化: 統計情報に基づいて最適なJOIN順序を自動選択(手動最適化不要)
  3. 集計のプッシュダウン: フィルタや集計を早い段階で実行し、処理データ量を削減
  4. 運用負荷の軽減: クエリを手動で最適化する必要がなくなる
CBOのデメリット
  1. 統計情報生成のコスト: AWS Glue課金が発生、大規模テーブルでは生成に時間がかかる
  2. 対応テーブル形式の制限: HiveテーブルとIcebergテーブルのみ対応(Athena for Sparkでは利用不可)
  3. 統計情報のメンテナンス負荷: データ変更時に統計情報を再生成する必要がある(古い統計情報では最適化効果が低下)
  4. 全テーブルに統計情報が必要: クエリに含まれるすべてのテーブルに統計情報が必要(1つでも欠けるとCBOが機能しない)
CBOの使用判断基準

推奨する場合:

  • 複雑なJOINを含むクエリを頻繁に実行
  • 大規模テーブルの分析(数百GB以上)
  • 定期レポートなどで同じクエリを繰り返し実行
  • ✅ データ量が時間とともに増加している

不要な場合:

  • ❌ シンプルなSELECT(JOINなし)
  • ❌ 小規模テーブル(数GB以下)
  • ❌ アドホッククエリのみ(たまにしか使わない)
  • ❌ 統計情報生成コストを避けたい
FastDoctorでの推奨方針

基本方針:

  • デフォルトでは統計情報を生成しない(コスト最適化)
  • 以下の条件に当てはまる場合のみ、使用を検討してください:
    1. 複雑なJOINを含む定期レポートで、パフォーマンス改善が必要
    2. 大規模テーブル(1TB以上)の分析で、クエリ時間が10分を超える
    3. コスト削減効果が統計情報生成コストを上回ると判断できる場合

参考:

トラブルシューティングの基本フロー

クエリが遅い場合

  1. 実行計画の確認: EXPLAINで実行計画を確認
  2. パーティション: パーティションプルーニングが効いているか確認
  3. ファイルサイズ: 小さいファイルが多数ないか確認
  4. 実行統計の取得: EXPLAIN ANALYZEで実際の実行統計を確認
sql
-- 実行計画の確認
EXPLAIN SELECT * FROM logs WHERE year=2024 AND month=1;

-- 実際の実行統計
EXPLAIN ANALYZE SELECT * FROM logs WHERE year=2024 AND month=1;

クエリが失敗する場合

  1. 構文エラー: エラーメッセージを確認
  2. 権限エラー: IAMロール/S3バケットポリシーを確認
  3. スキーマ不一致: テーブル定義とデータ形式の整合性確認
  4. データ破損: 特定のファイルが読み込めない場合

コストが高騰した場合

  1. CloudWatch Metricsで確認: どのワークグループでコストが増えているか
  2. クエリ履歴の確認: Athenaコンソールの「Recent queries」タブ、またはaws athena list-query-executionsで最近のクエリを確認
  3. パーティション設定: パーティションプルーニングが効いているか確認
  4. 不要なクエリ: 定期実行クエリの見直し

9. 関連ドキュメント

分析対象別ドキュメント

詳細な実装方法やクエリ例は、各分析対象別のドキュメントを参照してください:

AWS公式ドキュメント

10. 今後の検討課題(TODO)

以下は、システムの成長や利用状況に応じて将来的に検討すべき課題です。

10.1 データカタログのモニタリングと監査

実施検討のトリガー:

  • テーブル数が100を超えた時
  • 単一テーブルのパーティション数が10,000を超えた時
  • Glue APIの無料枠(月間100万リクエスト)を超えそうな時
  • S3とメタデータの不一致が頻発する時

実施内容:

  • テーブル数の監視: 不要なテーブルが増えていないか定期確認
  • パーティション数の監視: 1テーブルあたり数万パーティションを超えていないか確認
    • 超過時の影響: Glue API応答遅延、クエリ実行前のメタデータ取得遅延
    • 対応: 古いパーティションの削除、パーティション粒度の見直し
  • Crawler実行コストの監視: 不要なCrawlerが定期実行されていないか確認
    • CloudWatch Metricsでの監視
    • 実行履歴の定期レビュー
  • メタデータ変更の監視: 予期しないスキーマ変更がないか確認
    • CloudTrailでのGlue API呼び出しログ分析
    • スキーマバージョニングの導入検討

頻度: 四半期に1回(初期段階)→ 月次(テーブル数増加時)

10.2 監視設定の検証と最適化

実施タスク: SRE-1656

検証内容:

  • 監視閾値の妥当性検証: 実際のクエリ実行パターンに基づく閾値の調整
    • データスキャン量の閾値(80GB/5分が適切か)
    • クエリ実行時間の閾値(10分が適切か)
  • 監視期間の最適化: 5分間隔が適切か、より短い/長い期間が必要か
  • アラート通知先の確認: 適切なチャネルへの通知設定
  • Phase 2監視項目の必要性判断: 運用状況に応じてキューイング時間・S3バケットサイズ監視の追加判断

実施時期: 運用開始後、クエリ実行パターンが安定した時点(目安: 運用開始1-2週間後)

10.3 コスト最適化の強化

検討事項:

  • クエリ結果のキャッシュ戦略の見直し
  • データ圧縮形式の最適化(Parquet、ORC等への移行)
  • パーティション設計の見直し
  • 頻繁にアクセスするデータのS3ストレージクラス最適化

10.4 パフォーマンス改善

検討事項:

  • ビューやCTASを利用したデータマート作成
  • 頻繁に使用されるクエリの事前集計
  • Athena Federated Queryの活用(RDS等への直接クエリ)

10.5 ガバナンス強化

検討事項:

  • Lake Formationによる細粒度アクセス制御(カラムレベル)
  • データカタログへのタグ付け強化
  • クエリ監査レポートの自動生成
  • データ品質チェックの自動化

10.6 運用自動化

検討事項:

  • 定期実行クエリのStep Functions化
  • クエリ結果の自動配信(SNS/SES連携)
  • 異常検知とアラート自動化
  • コスト超過時の自動通知・制限