Redshift Serverlessを本番投入して月コストが2倍になった話と、その後8ヶ月で60%削減した記録

「Serverlessだから放置でいい」と思ったら予算アラートが炸裂。WPU設定・クエリ最適化・ストレージ戦略を見直して月コスト60%削減を達成するまでの実録です。

Redshift Serverlessを本番投入して最初に気づいた「設定しっぱなし地獄」

半年ほど前、うちのチームで分析基盤をプロビジョニング型からRedshift Serverlessに完全移行した。きっかけはシンプルで、クラスター管理に疲れたのと、利用時間が一日のうち4〜6時間程度しかない分析ワークロードに対してずっとインスタンスを起動しておくのが無駄だったから。

移行してみると最初はすごく快適だった。スケーリングを気にしなくていい、ノードの管理がない。でも3週間ほど経ったある日、Budgetアラートが飛んできた。月のRedshiftコストが予想の2.3倍になっていた。

当時の反省を一言で言うと、「Serverlessだから放置でいい」という思い込みが命取りだった。Serverlessというネーミングの罠にまんまとハマった形だ。その後8ヶ月かけてWPU設定・クエリ最適化・ストレージ戦略を見直し、最終的に月コストを60%以上削減できた。その過程で得た知見をまとめておきたい。

同じチームで運用しているAurora→Redshift Zero-ETL統合の実装記も合わせて読んでもらえると、データフロー全体像が掴みやすいと思う。


うちのチームのアーキテクチャ全体像

構成を整理するとこんな感じだ。BIツールのQuickSight・DataGrip・社内ダッシュボードから複数のワークグループにアクセスしている。

graph TB
    subgraph VPC["VPC (10.0.0.0/16)"]
        subgraph AZ_A["AZ: ap-northeast-1a"]
            subgraph Private_A["Private Subnet"]
                WG1["Redshift Serverless\nWorkgroup: analytics-prod"]
                WG2["Redshift Serverless\nWorkgroup: analytics-dev"]
            end
        end
        subgraph AZ_B["AZ: ap-northeast-1c"]
            subgraph Private_B["Private Subnet"]
                WG1B["(Multi-AZ Standby)"]
            end
        end
        subgraph Ingestion["データ取り込みレイヤー"]
            ZeroETL["Aurora→Redshift\nZero-ETL Integration"]
            Firehose["Kinesis Data Firehose"]
            Glue["AWS Glue\n(dbt実行環境)"]
        end
    end

    subgraph S3_Layer["S3 Data Lake"]
        S3_Raw["s3://raw-data/"]
        S3_Processed["s3://processed-data/"]
        S3_External["External Schema\n(Redshift Spectrum)"]
    end

    subgraph BI["BIツール・分析クライアント"]
        QS["Amazon QuickSight\n(SPICE + Direct Query)"]
        DataGrip["DataGrip\n(開発者用)"]
        Dashboard["社内ダッシュボード\n(FastAPI + psycopg3)"]
    end

    subgraph Monitoring["モニタリング"]
        CW["CloudWatch\nMetrics/Alarms"]
        CA["Cost Anomaly\nDetection"]
    end

    subgraph NS["Redshift Serverless Namespace"]
        NS1["Namespace: analytics"]
        NS1 --- WG1
        NS1 --- WG2
    end

    ZeroETL --> WG1
    Firehose --> S3_Raw
    S3_Raw --> Glue
    Glue --> WG1
    S3_Processed --> S3_External
    S3_External --> WG1

    WG1 --> QS
    WG1 --> DataGrip
    WG1 --> Dashboard
    WG2 --> DataGrip

    WG1 --> CW
    WG1 --> CA

ポイントは、本番と開発でワークグループを分離してWPU上限を別々に設定していること。最初はNamespace一つにWorkgroup一つという構成だったけど、開発者が重いクエリを流すと本番のQuickSightが詰まるという地獄を経験してから分けた。これ、やらかしてから気づくやつ。


WPU設計が9割を決める話

WPUの基本と最初にやった失敗

Redshift ServerlessのWPU(Redshift Processing Unit)は8〜512の範囲で設定できる。2026年7月時点では最大512WPUまで使えるようになっている。

で、最初の失敗はデフォルト設定のまま放置していたことだ。当時の設定を確認したら、最大WPUが512になっていた。実際のワークロードは平均32〜64WPU程度しか使っていないにもかかわらず、スパイクに備えて上限を高くしたままにしていた。ここが罠で、Redshift Serverlessは使用したWPU × 時間で課金されるため、ベースコストはそれほど変わらないが、想定外のクエリが走ると一気に上振れする。正直、「念のため高めに設定しておこう」という発想が一番危ない。

実際のWPU使用量と請求額の推移がこちら。2026年1月がまさに「事件」の月だ。

xychart-beta
  title "月別WPU使用量(RPU-hours)と請求額(万円)の推移"
  x-axis ["2025/11", "2025/12", "2026/1", "2026/2", "2026/3", "2026/4", "2026/5", "2026/6"]
  y-axis "RPU-hours" 0 --> 12000
  bar [9800, 10200, 11400, 8200, 6100, 4800, 4200, 3900]
  line [9800, 10200, 11400, 4800, 4200, 3900]

2026年1月がピークで、そこから本格的に最適化を始めた。2026年6月時点で約60%削減できている。グラフで見ると一目瞭然で、「なんでもっと早くやらなかったんだ」という気持ちになる。

ワークグループごとのWPU上限設定

見直した設定がこちら。

ワークグループ変更前 最大WPU変更後 最大WPU用途備考
analytics-prod512128QuickSight・ダッシュボードピーク時間帯に追加調整
analytics-dev51232開発者クエリコスト上限として機能
analytics-batch新設 256256dbt・夜間バッチ時間帯制限と組み合わせ

バッチ専用のワークグループを分離したのが地味に一番効いた。夜間のdbtバッチが昼間の分析クエリと競合して、お互いのWPUを食い合っていた状態が解消された。「分けるだけ」でここまで変わるのか、という感じ。

WPU使用量のモニタリング設定

現状把握なしに最適化はできないので、まずこのクエリを毎日CloudWatch Logs Insightsに流して異常なコンピューティング消費がないかチェックしている。

-- WPU使用状況のモニタリングクエリ
SELECT 
  DATE_TRUNC('hour', start_time) AS hour,
  workgroup_name,
  SUM(compute_seconds) / 3600.0 AS compute_hours,
  COUNT(*) AS query_count,
  AVG(execution_time) / 1000.0 AS avg_exec_sec,
  MAX(execution_time) / 1000.0 AS max_exec_sec
FROM sys_query_history
WHERE start_time >= DATEADD(day, -7, GETDATE())
  AND status = 'success'
GROUP BY 1, 2
ORDER BY 1 DESC, compute_hours DESC;

CloudWatchのカスタムダッシュボードと組み合わせた監視設計についてはCloudWatch vs Datadog 2026の比較記事も参考になるかもしれない。


クエリ最適化で体感した「劇的すぎる」改善

実際に発見した犯人クエリ

WPU設定の次にやったのがクエリの棚卸し。sys_query_historysys_query_detailを使って、過去30日間でコンピューティングを一番食っているクエリを抽出した。

-- 高コストクエリの特定
SELECT 
  query_id,
  LEFT(query_text, 100) AS query_preview,
  user_name,
  compute_seconds,
  execution_time / 1000.0 AS exec_sec,
  returned_rows,
  scanned_rows,
  ROUND(100.0 * scanned_rows / NULLIF(returned_rows, 0), 1) AS scan_to_return_ratio
FROM sys_query_history
WHERE start_time >= DATEADD(day, -30, GETDATE())
  AND status = 'success'
  AND compute_seconds > 100
ORDER BY compute_seconds DESC
LIMIT 20;

上位に入ってきたクエリのひとつがこれだった(実際のテーブル名は変えてある)。

-- 最適化前: 全テーブルスキャンが走っていた問題クエリ
SELECT 
  u.user_id,
  u.name,
  COUNT(o.order_id) AS order_count,
  SUM(o.amount) AS total_amount
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.created_at >= '2026-01-01'
GROUP BY u.user_id, u.name;

これで毎回スキャン行数が1.2億行を超えていた。EXPLAINで実行計画を確認したら、ordersテーブルへのフルスキャンが走っていた。一見なんでもなさそうなクエリがこんなに暴れていたのか、と正直引いた。

ソートキーと分散キーの見直し

-- 最適化後のテーブル定義
CREATE TABLE orders (
  order_id    BIGINT NOT NULL,
  user_id     BIGINT NOT NULL,
  amount      DECIMAL(12,2),
  status      VARCHAR(20),
  created_at  TIMESTAMP NOT NULL,
  updated_at  TIMESTAMP
)
DISTSTYLE KEY
DISTKEY (user_id)              -- JOINキーに合わせて分散
SORTKEY (created_at, user_id); -- WHERE句で頻出するカラムをソートキーに

テーブルを再構築してクエリを再実行したところ、スキャン行数が1.2億行 → 380万行に激減した。実行時間は18秒 → 1.4秒。体感でも明らかに違う。テーブル定義って本当に大事で、後から直すのは骨が折れるので最初から設計しておきたかった、というのが正直なところだ。

AQUA・自動最適化との付き合い方

2026年時点のRedshift ServerlessにはAQUA(Advanced Query Accelerator)と自動テーブル最適化(ATO)が組み込まれている。ATOについては、VACUUMとANALYZEを自動でやってくれるので基本的にはお任せでいいんだけど、問題があって。

ATOが深夜に動くタイミングと夜間バッチのdbt実行が被ると、WPU消費が跳ね上がる。これを避けるために、メンテナンストラックのスケジュールをdbtと被らない時間帯に明示的に設定した。

-- 統計情報の手動確認(ATOが追いついていない場合)
SELECT 
  schema_name,
  table_name,
  stats_off,
  tbl_rows,
  estimated_visible_rows
FROM svv_table_info
WHERE stats_off > 10  -- 統計情報が10%以上ずれているテーブル
ORDER BY stats_off DESC
LIMIT 20;

stats_offが高いテーブルは手動でANALYZEをかけておく運用にした。地味だけどクエリプランの精度が上がって、想定外の遅いクエリが減った。自動化に頼りきらず、たまは自分の目で確認するのが大事だと思う。


Redshift Spectrum + Intelligent-TieringでS3コストも最適化

ホットデータとコールドデータの分離戦略

ストレージコストの観点では、Redshift内部に保持するデータとS3に外出しするデータの境界設計が重要だ。うちのチームでは以下の方針で整理した。

flowchart LR
    subgraph Redshift["Redshift Serverless (マネージドストレージ)"]
        Hot["直近90日間のデータ\n高頻度アクセス\n集計済みテーブル"]
    end

    subgraph S3_Spectrum["S3 + Redshift Spectrum"]
        Warm["90日〜1年\nアクセス頻度: 週1回程度\nParquet形式"] 
        Cold["1年以上\nアクセス頻度: 月1回以下\nGlacier Instant Retrieval"]
    end

    subgraph Query["クエリエンジン"]
        Spectrum_Engine["Redshift Spectrum Engine"]
    end

    Hot -->|"UNLOAD (日次)"| Warm
    Warm -->|"S3 Lifecycle Policy"| Cold
    Warm --> Spectrum_Engine
    Cold --> Spectrum_Engine
    Hot --> Spectrum_Engine
-- External Schemaの作成(Redshift Spectrum用)
CREATE EXTERNAL SCHEMA spectrum_analytics
FROM DATA CATALOG
DATABASE 'analytics_glue_db'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftSpectrumRole'
CREATE EXTERNAL DATABASE IF NOT EXISTS;

-- PartitionedなExternal Tableの例
CREATE EXTERNAL TABLE spectrum_analytics.orders_historical (
  order_id    BIGINT,
  user_id     BIGINT,
  amount      DECIMAL(12,2),
  status      VARCHAR(20),
  created_at  TIMESTAMP
)
PARTITIONED BY (year INT, month INT)
STORED AS PARQUET
LOCATION 's3://analytics-data-lake/orders/historical/';

この構成にしてから、Redshiftの内部ストレージ使用量が68%削減された。S3の費用はかかるけど、Redshiftのマネージドストレージ単価との差が大きいので全体コストは下がる。個人的にはこのホット/コールド分離が、コスト削減施策の中でいちばん投資対効果が高かった。

S3 Intelligent-Tieringとの組み合わせ

S3側はIntelligent-Tieringを有効化して自動的にコールドデータをGlacier Instant Retrievalに移動させている。設定はS3 Intelligent-TieringとGlacierで年間コスト40%削減した話で詳しく書いたのでそちらも参照してほしい。

Redshift Spectrumからのクエリ時間はマネージドストレージより遅いけど、月1回程度しかアクセスしない歴史データに対してそのコストは十分許容範囲だ。


2026年版の新機能活用と今後の課題

Zero-ETL統合のさらなる活用

2026年前半にRedshift ServerlessのZero-ETL統合が安定してきた。うちのチームでもAurora PostgreSQLからのZero-ETL統合を使っており、これ以前はGlueジョブ経由で4〜6時間かかっていたデータ反映が15分以内になった。

ただし正直まだ検証中の部分もあって、Aurora側でDDL変更(カラム追加など)が走ると統合が一時停止するケースがある。本番で2回ほど気づかずにデータが古いまま朝の会議に臨んでしまったことがあって、CloudWatchアラームで「zero-etl-status: ERROR」を検知する監視を追加した。あの朝の会議は思い出したくない。

# Zero-ETL統合ステータスの監視Lambda(簡略版)
import boto3
import json

def lambda_handler(event, context):
    client = boto3.client('redshift-serverless', region_name='ap-northeast-1')
    
    # Zero-ETL統合のステータス確認
    integrations = client.list_integrations()
    
    sns_client = boto3.client('sns')
    alerts = []
    
    for integration in integrations.get('Integrations', []):
        status = integration.get('Status', '')
        name = integration.get('IntegrationName', '')
        
        if status not in ['ACTIVE']:
            alerts.append({
                'integration': name,
                'status': status,
                'errors': integration.get('Errors', [])
            })
    
    if alerts:
        sns_client.publish(
            TopicArn='arn:aws:sns:ap-northeast-1:123456789012:data-pipeline-alerts',
            Subject='[ALERT] Redshift Zero-ETL Integration Error',
            Message=json.dumps(alerts, ensure_ascii=False, indent=2)
        )
    
    return {'statusCode': 200, 'alerts': len(alerts)}

AI駆動型クエリ最適化(Amazon Q in Redshift)

2026年のアップデートでAmazon QがRedshiftコンソールに統合されて、クエリの最適化提案をAIが出してくれるようになった。実際に試してみたところ、JOIN順序の提案やソートキーの推奨が思ったより精度高くて驚いた。

ただし全部鵜呑みにすると危ない場面もある。データ分布が偏ったテーブルに対して「分散キーをこれにしろ」という提案が来たんだけど、そのカラムはNULLが30%含まれていて実際には性能が悪化した。提案はあくまで参考にしつつ、EXPLAINで自分でプランを確認する習慣は崩さない方がいい。AIが悪いというより、テーブルの実態を知っているのは自分たちだ、という話だと思う。

皆さんのチームではAmazon Qのクエリ最適化提案、どれくらい信頼して使ってますか?ここは結構好みが分かれそう。

dbt × Redshift Serverlessの相性

dbt Core 2.0との組み合わせは基本的に好調だ。dbt-redshiftアダプターが2026年版でauto_refreshオプションに対応したので、マテリアライズドビューの自動更新管理がしやすくなった。

でも一点だけ困っていること。dbtのモデル数が400を超えてきたあたりから、full refresh時のWPU消費が予想外に大きくなっている。モデルの依存グラフを見直してインクリメンタル化を進めているところで、正直まだ完全には解決できていない。dbt Core 2.0移行後の本番知見も参考にしてほしい。

実際の最適化成果まとめ

8ヶ月間の取り組みで得られた数値をまとめると:

最適化項目BeforeAfter削減率
月間WPU消費 (RPU-hours)11,4003,900-66%
月間コスト (概算)約50万円約18万円-64%
平均クエリ実行時間8.2秒2.1秒-74%
Redshiftマネージドストレージ4.2TB1.3TB-69%
dbt full refresh時間42分18分-57%

月32万円の削減は、正直思ってた以上だった。


まとめ

Redshift Serverless最適化で本当に効いたことを整理すると:

  1. WPU上限設定をワークロード別に細かく設定する — デフォルト512WPUのままにしない。本番・開発・バッチでワークグループを分離してそれぞれ上限を設ける
  2. ソートキー・分散キーの設計が9割 — 自動最適化に任せきりにせず、実際のクエリパターンをsys_query_historyで分析してテーブル設計に反映する
  3. ホット/コールドデータの分離でストレージコストを大幅削減 — Redshift Spectrum + S3 Intelligent-Tieringの組み合わせは投資対効果が高い
  4. Zero-ETL統合は便利だがステータス監視を怠らない — DDL変更で統合が停止するケースがあるため、CloudWatchアラームとLambdaで自動検知する仕組みが必要
  5. dbtのインクリメンタルモデル化はコスト削減と直結する — full refreshの頻度と対象を絞り込むだけでWPU消費が劇的に変わる

次のアクションとしては、Amazon Q in Redshiftの推奨をCI/CDパイプラインに組み込んで、プルリクエスト時にクエリ最適化の提案が自動で出るようにする実験を始めたいと思っている。ここはまだ試行錯誤中だけど、うまくいったら別記事で書く予定。

Redshift Serverlessを「放置」から「管理」に変えると、コストと性能の両方が劇的に改善する。特にWPU設計とソートキー見直しは今すぐやれる施策なので、まだ手をつけていない方はぜひ試してみてほしい。

U

Untanbaby

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

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

関連記事