S3のIntelligent-TieringとGlacierで年間コスト40%削減できた話

「S3の請求が全体の28%?さすがにまずい」と気づいてから3ヶ月。Intelligent-TieringとGlacierの使い分けで実際に削減できた過程と、やってみて初めてわかった落とし穴をまとめました。

先日、月次のAWSコストレビューでS3の請求が全体の28%を占めているのに気づいて、「さすがにこれはまずい」と思って本格的に最適化に動いた。結果として3ヶ月で約40%削減できたんだけど、その過程でIntelligent-TieringとGlacierの使い分けについて相当深く掘ったので、実際に動かして気づいたことを書いておく。

うちのチームはデータ分析基盤を運用していて、S3に生ログ・加工済みデータ・機械学習用特徴量・過去レポートなどが混在している状況だった。最初は「なんとなく全部STANDARDでいいか」くらいの認識だったんだけど、それが後々かなり痛い請求につながった。

2026年時点のS3ストレージクラス全体像

2026年現在、S3のストレージクラスは以下の構成になっている。2025年後半にGlacier Instant Retrievalの取り出し料金が改定されていて、これが今回の最適化に大きく影響した。

ストレージクラスGB単価(東京)/月最小保存期間取り出し遅延取り出し料金/GB
S3 Standard$0.025なしミリ秒$0
S3 Standard-IA$0.013830日ミリ秒$0.01
S3 One Zone-IA$0.01130日ミリ秒$0.01
S3 Intelligent-Tiering$0.025〜$0.0045なし層次第自動移行手数料あり
S3 Glacier Instant$0.00590日ミリ秒$0.03
S3 Glacier Flexible$0.004590日数分〜時間$0.01〜0.03
S3 Glacier Deep Archive$0.002180日12〜48時間$0.0025

Intelligent-Tieringは内部的に複数の層を持っていて、アクセスパターンを自動学習して最適な層に移動してくれる。ただし1オブジェクトあたり$0.0025/月のモニタリング手数料がかかるので、小さいファイルが大量にある場合は逆にコスト増になるので注意。

# 現状のバケット別ストレージクラス分布を確認する
aws s3api list-objects-v2 \
  --bucket your-data-bucket \
  --query 'Contents[].{Key:Key,Size:Size,StorageClass:StorageClass}' \
  --output json | \
  jq 'group_by(.StorageClass) | 
      map({class: .[0].StorageClass, 
           count: length, 
           total_gb: ([.[].Size] | add) / 1024/1024/1024})'

これを実行してみたら、うちのバケットではSTANDARDに12TBが眠っていて、アクセスログを確認すると直近90日でアクセスがあったのは全体の15%程度だとわかった。

[
  {
    "class": "STANDARD",
    "count": 2847321,
    "total_gb": 12847.3
  },
  {
    "class": "INTELLIGENT_TIERING",
    "count": 0,
    "total_gb": 0
  }
]

12TBのうち85%が実質的に「置いてあるだけ」の状態だったわけで、この結果を見た瞬間に「これは早急に手を打たないと」となった。

Intelligent-Tiering導入の判断基準と実装

Intelligent-Tieringを使うべきかどうか、正直最初はかなり懐疑的だった。「自動最適化って本当に効くの?」「モニタリング手数料で逆に高くなるんじゃ?」という疑問があって、しばらく様子見していたんだけど、実際に検証したら想像以上に効いた。

判断のフローとして整理したのがこれ:

flowchart TD
    A[オブジェクトのアクセスパターンを分析] --> B{オブジェクトサイズ}
    B -->|128KB未満| C[STANDARD or Standard-IA\n手数料対効果が合わない]
    B -->|128KB以上| D{アクセス頻度の予測可能性}
    D -->|予測可能・頻繁| E[STANDARD]
    D -->|予測可能・低頻度| F{最小保存期間OK?}
    D -->|予測不可能・変動| G[Intelligent-Tiering ✅]
    F -->|30日以上| H[Standard-IA or Glacier Instant]
    F -->|30日未満| E
    G --> I{アーカイブ層有効化?}
    I -->|アクセス完全停止の可能性あり| J[Archive Access層も有効化]
    I -->|たまにアクセスあり| K[Frequent/Infrequent層のみ]

実装はS3バケットのLifecycle Policy + Intelligent-Tieringの組み合わせ。CDKで書くとこんな感じになる:

import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cdk from 'aws-cdk-lib';

const dataBucket = new s3.Bucket(this, 'DataBucket', {
  bucketName: 'team-data-optimized-bucket',
  intelligentTieringConfigurations: [
    {
      name: 'ArchiveIntelligentTiering',
      archiveAccessTierTime: cdk.Duration.days(90),      // 90日未アクセスでArchive層へ
      deepArchiveAccessTierTime: cdk.Duration.days(180), // 180日でDeep Archive層へ
      prefix: 'processed/',                              // processed/配下のみ対象
    },
  ],
  lifecycleRules: [
    {
      id: 'MoveRawLogsToGlacier',
      prefix: 'raw-logs/',
      transitions: [
        {
          storageClass: s3.StorageClass.INTELLIGENT_TIERING,
          transitionAfter: cdk.Duration.days(0), // 即座にIT移行
        },
      ],
      enabled: true,
    },
    {
      id: 'ArchiveOldReports',
      prefix: 'reports/',
      transitions: [
        {
          storageClass: s3.StorageClass.GLACIER_INSTANT_RETRIEVAL,
          transitionAfter: cdk.Duration.days(90),
        },
        {
          storageClass: s3.StorageClass.GLACIER,
          transitionAfter: cdk.Duration.days(365),
        },
      ],
      enabled: true,
    },
    {
      id: 'DeleteOldVersions',
      noncurrentVersionExpiration: cdk.Duration.days(30),
      enabled: true,
    },
  ],
});

一点、地味にしんどいポイントとして覚えておいてほしいのが、Intelligent-Tieringのアーカイブ層はデフォルトで無効になっているということ。明示的に設定しないと有効にならない。これを知らなくて最初の1ヶ月は恩恵が半分くらいしか受けられなかった。設定した気になっていたのに実は効いていなかったというのは結構しんどかった。

AWS構成:S3コスト最適化アーキテクチャ全体像

実際に構築した構成はこうなっている。データの特性に応じて複数バケットに分割して、それぞれに適したストレージクラス戦略を適用している:

graph TB
    subgraph Producers[データ生成者]
        APP[アプリケーション]
        ETL[ETLジョブ]
        ML[MLパイプライン]
    end

    subgraph S3Buckets[S3バケット群]
        subgraph HotData[ホットデータ層]
            RAW[raw-ingestion-bucket\nSTANDARD\n即時アクセス必要]
            PROC[processed-data-bucket\nIntelligent-Tiering\nアクセス変動大]
        end

        subgraph WarmData[ウォームデータ層]
            FEAT[feature-store-bucket\nStandard-IA\n週次アクセス]
            SNAP[snapshots-bucket\nGlacier Instant\n月次参照]
        end

        subgraph ColdData[コールドデータ層]
            ARCH[archive-reports-bucket\nGlacier Flexible\n四半期参照]
            COMP[compliance-bucket\nGlacier Deep Archive\n法定保存7年]
        end
    end

    subgraph Lifecycle[自動移行管理]
        LCP[Lifecycle Policy\nCDK管理]
        IT[Intelligent-Tiering\n自動最適化]
        SA[Storage Lens\nコスト可視化]
    end

    subgraph Monitoring[コスト監視]
        CW[CloudWatch\nストレージメトリクス]
        BUDGET[AWS Budgets\nアラート]
        CA[Cost Anomaly\nDetection]
    end

    APP --> RAW
    ETL --> PROC
    ML --> FEAT
    RAW -->|30日後| PROC
    PROC -->|IT自動最適化| IT
    FEAT -->|90日後| SNAP
    SNAP -->|365日後| ARCH
    ARCH -->|2555日後| COMP
    LCP --> RAW
    LCP --> PROC
    LCP --> FEAT
    SA --> CW
    CW --> BUDGET
    CW --> CA

S3 Storage Lensは2025年から無料ティアでも結構詳細なメトリクスが見られるようになったので、まず有効化しておくことを強くすすめる。どのバケットで何のストレージクラスが何TBあるかが一目瞭然になって、「あ、ここが無駄だ」というのが見えやすくなった。個人的にはこれが今回の最適化で一番費用対効果が高い一手だったと思っている。

Glacier運用で実際にハマったこと

Glacierは「安くなる」というイメージだけ持って突き進むと、取り出し時のコストで痛い目を見る。実際、うちもやらかした。

半年前に機械学習の学習データを「もう使わないだろう」と判断してGlacier Flexibleに移行したら、3ヶ月後に突然「このデータでモデルを再学習したい」という話になって、緊急で取り出す羽目になった。Glacier Flexibleのエクスペディア取り出しは$0.033/GBかかるうえに、取り出しリクエスト料金も別でかかる。1TBで約$33の出費だった。正直、あのときは「Glacierに入れたのが間違いだった」と思った。

取り出し速度と料金の関係をまとめるとこうなる:

取り出しオプション所要時間料金(Flexible)料金(Deep Archive)
エクスペディア1〜5分$0.033/GB非対応
スタンダード3〜5時間$0.01/GB$0.0025/GB
バルク5〜12時間$0.0025/GB$0.00025/GB

この経験から学んで、今は取り出し頻度の可能性に応じて選択するルールを決めた:

# データ移行先クラス判断ロジック(簡略版)
def decide_storage_class(
    access_frequency_per_month: float,
    last_accessed_days: int,
    size_gb: float,
    retrieval_urgency: str  # 'immediate', 'hours', 'days'
) -> str:
    """
    S3ストレージクラス自動判断ロジック
    実際にLambdaでS3 Storage Lensのデータを元に動かしている
    """
    # 128KB未満の小さいファイルはIT手数料が割に合わない
    if size_gb < 0.000128:
        if access_frequency_per_month > 1:
            return 'STANDARD'
        return 'STANDARD_IA'

    # アクセスパターンが予測不可能な場合
    if access_frequency_per_month > 0 and last_accessed_days < 90:
        return 'INTELLIGENT_TIERING'

    # 完全に低頻度だが即時取り出しが必要
    if retrieval_urgency == 'immediate' and last_accessed_days >= 90:
        return 'GLACIER_INSTANT_RETRIEVAL'

    # 数時間の遅延OK
    if retrieval_urgency == 'hours' and last_accessed_days >= 180:
        return 'GLACIER'

    # コンプライアンス・長期保存(数日の遅延OK)
    if last_accessed_days >= 365:
        return 'DEEP_ARCHIVE'

    return 'STANDARD_IA'

# 実行例
print(decide_storage_class(
    access_frequency_per_month=0,
    last_accessed_days=400,
    size_gb=500.0,
    retrieval_urgency='days'
))
# -> 'DEEP_ARCHIVE'

このロジックをLambdaに組み込んで、毎週S3 Inventory + Storage Lensのデータを元にオブジェクトのストレージクラス適正化レポートを自動生成するようにした。VPCフローログの費用削減でもやったパターンと似た発想で、まずコストの可視化から始めるのが大事だと思っている。

もう一つハマったのが、Glacier Flexibleに移行したオブジェクトはコピー操作で別クラスに変更できないという制約。移行したいときはRestore → 待機 → コピー → 元削除の手順が必要で、大量オブジェクトを扱う場合は相当な時間がかかる。これも実際に踏んで初めて知ったんだけど、最初から「ここは絶対Glacierで問題ない」というデータにだけ適用するのが鉄則だと痛感した。

3ヶ月の削減効果と正直な評価

実際の削減効果を数値で見てみる。

xychart-beta
    title "S3月次コスト推移(万円)"
    x-axis [1月, 2月, 3月, 4月, 5月, 6月]
    y-axis "コスト(万円)" 0 --> 80
    bar [68, 71, 69, 52, 45, 41]
    line [68, 71, 69, 52, 45, 41]

1〜3月が最適化前、4月からIntelligent-Tiering移行を開始、5月にGlacier移行も追加した結果、6月の請求は41万円まで落ちた。ピーク時(2月)の71万円と比べると42%削減。年換算にすると約360万円の差になる計算だ。

施策別の内訳はこんな感じになる:

施策対象データ量月次削減効果
STANDARD → Intelligent-Tiering8TB約15万円
STANDARD → Glacier Instant3TB約8万円
STANDARD → Glacier Deep Archive5TB約9万円
不要オブジェクト削除2TB約5万円
合計18TB約37万円

正直まだ検証中の部分もあるんだけど、Intelligent-Tieringの効果はアクセスパターンが安定するまで2〜3ヶ月かかる印象がある。最初の1ヶ月はモニタリング手数料だけかかって恩恵が少なく感じるかもしれないけど、焦らず続けることが大事。ここで「効果ないじゃん」と判断してやめてしまうのがいちばんもったいない。

あとこれはコスト最適化の話とも関連するんだけど、S3に大量データがあるということはそれだけAthenaのクエリコストも高くなりやすい。BigQuery・Athena・Redshiftのコスト比較でも触れたけど、S3のファイルフォーマット(Parquet化)とパーティション設計を合わせて見直すと相乗効果が出やすい。

またコスト監視についてはAWS Budgets・Cost Anomaly Detectionの実運用の記事も参考になると思う。S3のストレージクラス移行後は予期しないアクセスパターン変化でコストが跳ね上がることもあるので、アラートは必ず設定しておいた方がいい。

皆さんの環境ではS3のストレージクラス、どこまで意識して運用してますか?「とりあえずSTANDARDで放置」みたいになっていないか、一度S3 Storage Lensで現状を確認してみるだけでも結構衝撃的な結果が出るかもしれない。

まとめ

3ヶ月の最適化を通じて得た知見を整理すると:

  • Intelligent-Tieringは「アクセスパターンが読めないデータ」に最も効く。128KB以上のオブジェクトで、読まれるか読まれないかわからないデータは積極的に適用すべき
  • Glacierの選択は取り出し要件から逆算する。Flexible/Deep Archiveはコスト最安だが、緊急取り出しが発生すると一気に割高になる。Glacier Instantは「安いが即時取り出せる」という絶妙なポジション
  • S3 Storage Lensを最初に有効化する。どこに何がどれだけあるかが見えないと戦略が立てられない。無料ティアでも十分な情報が取れる
  • 小さいファイルはIntelligent-Tieringのモニタリング手数料負けする。128KB未満が多い場合はまずファイル結合(コンパクション)を検討
  • Lifecycle Policyは段階的に適用。一気に全データを移行するより、まず影響が限定的なプレフィックスで試して効果を測定してから拡大する

次のアクションとしては、まずS3 Storage Lensを有効化してバケット別のアクセスヒートマップを確認することから始めてほしい。その後、アクセスがほぼない90日以上更新されていないオブジェクトをリストアップして、Glacier Instant Retrievalへの移行を試してみると即効性が高い。コスト削減の実感が出てきたら、Intelligent-Tieringの段階的展開に進むというステップがおすすめだ。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事