Redshift Serverless運用で月50万円→18万円に削減した、WPU管理とクエリ最適化の失敗から学んだ話
Redshift Serverlessが思ったより高い理由、知ってますか?1年運用で気づいた3つの落とし穴と、実装した削減パターンを赤裸々に解説します。
なぜRedshift Serverlessを選んだのか、そして後悔した
うちのチームがRedshift Serverlessを本番導入したのは、「管理の手間がなくなる」という謳い文句に惚れたのが大きい。従来のRedshift Provisioned Clusterから移行する話が出たときは、正直なところ「いける!」と思ってた。でもね、実際に3ヶ月運用してみたら、その甘さに気づかされた。
最初の2週間は順調だった。スケーリングが自動だし、アイドル時間が少ないし、見た目的にはすごく効率的。ところがね、月の請求書を見て心臓が止まりそうになった。WPU(Redshift Processing Units)の課金が思ってたより遥かに高い。チーム内でも「こんなことなら、Provisioned Clusterのままの方が安かったんじゃ?」という声が出始めた。
WPU管理が本当のボトルネック
Redshift Serverlessの課金体系って、一見シンプルに見えるけど実は結構複雑なんだ。WPUは「1時間単位での最大使用量 × 時間数」で課金される。つまり、1つのクエリが大量のWPUを消費すれば、その時間帯全体が影響を受ける。うちの場合、毎晩のバッチジョブが凶悪で、ピーク時に128 WPUを使うんだけど、それがある1時間のコスト全体を決定してしまう。
これに気づいたときは本気で考え直した。「Serverlessって名前だから、何もしなくても勝手に最適化されるんだろう」と思ってたのが間違い。実際には以下の3つをガッチリ管理する必要がある:
- クエリパフォーマンスの最適化
- WPU割り当てのチューニング
- クエリキャッシング戦略
最初の3ヶ月で、これらを全くやってなかったから、月50万円近くいってた。それを2ヶ月かけて対策したら、月18万円まで落とせた。その過程を正直に話す。
AWS構成図:Redshift Serverless + 周辺サービス
graph TB
subgraph AWS["AWS Environment"]
subgraph Data["Data Layer"]
S3["Amazon S3<br/>Data Lake"]
Glue["AWS Glue<br/>ETL/Catalog"]
end
subgraph Compute["Compute & Query"]
RS["Redshift Serverless<br/>Workgroup"]
Cache["Result Cache<br/>24h TTL"]
Stats["Query Stats<br/>CloudWatch"]
end
subgraph Monitor["Monitoring & Cost"]
CW["CloudWatch<br/>Metrics"]
CE["Cost Explorer<br/>WPU Analysis"]
QH["Query History<br/>Performance Insights"]
end
end
subgraph Client["Client Layer"]
BI["BI Tools<br/>Tableau/Superset"]
App["Applications<br/>Python/Node"]
Reports["Scheduled Reports"]
end
S3 -->|"COPY/Spectrum"| RS
Glue -->|"Catalog"| RS
RS -->|"Query Results"| Cache
RS -->|"Metrics"| Stats
Cache -->|"Cache Hit/Miss"| CW
Stats -->|"Query Data"| QH
CW -->|"WPU Usage"| CE
RS -->|"Data"| BI
RS -->|"APIs"| App
RS -->|"Scheduled"| Reports
BI -->|"Monitor"| CW
style RS fill:#FF9900,color:#fff
style Cache fill:#36C5F0,color:#fff
style S3 fill:#569A31,color:#fff
style CE fill:#FF9900,color:#fff
クエリパフォーマンス最適化で劇的に変わった
実装したのはまず、スロークエリの可視化。CloudWatch Metrics + Performance Insightsを使って、毎日のクエリプロファイルを取得した。
-- 実装したのはこんなビュー
SELECT
query_id,
user_name,
query_text,
start_time,
end_time,
status,
elapsed_time,
returned_rows,
query_queue_time,
execution_time,
(CAST(elapsed_time AS FLOAT) / 1000000) AS elapsed_seconds
FROM stl_query
WHERE user_name != 'rdsdb'
AND returned_rows > 0
AND start_time >= CURRENT_DATE - 1
ORDER BY elapsed_time DESC
LIMIT 100;
これを毎朝見てたら、本当にびっくりする結果が出てきた。データ品質管理のためのバッチが、毎日3時間かかってた。理由はシンプルだった。テーブル設計が最悪だったんだ。
テーブル設計の失敗パターン
うちは100億行のイベントテーブルを、DISTKEY なしで作ってた。つまり、スキャン時に全ノードにバラバラに分散された行をスキャンしてる。これって、分散DBの最悪なパターンなんですよね。
| 項目 | 修正前 | 修正後 |
|---|---|---|
| DISTKEY | なし(ハッシュ分散) | user_id(均等分散) |
| SORT KEY | なし | created_at |
| クエリ時間 | 30分 | 8分 |
| WPU使用量 | 128 | 88 |
実装はこんな感じ:
-- 修正前(地獄)
CREATE TABLE events (
event_id BIGINT,
user_id BIGINT,
event_type VARCHAR,
created_at TIMESTAMP,
data SUPER
);
-- 修正後(天国)
CREATE TABLE events (
event_id BIGINT,
user_id BIGINT,
event_type VARCHAR,
created_at TIMESTAMP,
data SUPER
)
DISTKEY (user_id) -- 重要!user_idでドメイン分割
SORT KEY (created_at);
この変更だけで、クエリが30分から8分に短縮された。WPU消費も40下がった。地味に便利じゃなくて、本当に重要な工夫だ。
WPU割り当ての真実
次に気づいたのは、「Serverless = WPUは無制限」という思い込み。実際には、Redshift Serverlessでも以下の制約がある:
- デフォルト最大WPU:128(設定で増やせるけど、課金がヤバい)
- Workgroupごとに上限を設定可能
- ただし設定しすぎると、クエリ実行中にずっと高WPUで課金される
うちが実装したのは、時間帯別のWPU最大値設定。業務パターンに合わせて、自動的にキャパシティを調整する仕組みだ。
# boto3でWPU最大値を動的に設定
import boto3
import pytz
from datetime import datetime
redshift = boto3.client('redshift-serverless', region_name='us-east-1')
def adjust_wpu_limit():
now = datetime.now(pytz.timezone('Asia/Tokyo'))
hour = now.hour
# 業務時間(9-18時)は128 WPU
# 深夜バッチ(23-04時)は64 WPU(キューして実行)
# その他は32 WPU
if 9 <= hour < 18:
max_wpu = 128
elif 23 <= hour or hour < 4:
max_wpu = 64
else:
max_wpu = 32
redshift.update_workgroup(
WorkgroupName='analytics-prod',
WorkgroupConfig={
'EnhancedVpcRouting': True,
'MaximumCapacity': max_wpu,
'PubliclyAccessible': False
}
)
print(f"WPU limit adjusted to {max_wpu} at {now}")
if __name__ == '__main__':
adjust_wpu_limit()
これをEventBridge + Lambdaで毎1時間実行させた。結果として、深夜の無駄な高WPU消費が約40%削減された。シンプルだけど、効果は絶大。
クエリキャッシングが想像以上に効いた
これは正直、予想外だった。Redshift Serverlessには「Result Cache」という機能があって、同じクエリの結果を24時間キャッシュしてくれる。デフォルトでは無効なんだけど、有効にしたら月のWPU消費が一気に15%下がった。
BI ToolsとReportsって、実は同じクエリを何回も叩くんですよね。特に毎日の定型レポートなんて、フォーマットは変わらずに日付だけ変わる。こういう場面ではキャッシュが爆発的に効く。
# パラメータ化クエリでキャッシュを活用
from redshift_connector import connect
def get_daily_report(date):
# この構造だと、日付パラメータだけ異なる同じクエリは
# すべてクエリプランレベルで同一とみなされキャッシュヒット
query = """
SELECT
user_id,
COUNT(*) as event_count,
SUM(revenue) as daily_revenue
FROM events
WHERE created_at::DATE = %s
GROUP BY 1
ORDER BY daily_revenue DESC;
"""
conn = connect(
host='default.xxxxx.us-east-1.redshift-serverless.amazonaws.com',
database='analytics',
user='admin',
password='xxxxxx'
)
cursor = conn.cursor()
cursor.execute(query, (date,))
return cursor.fetchall()
重要なのは、ResultCacheはクエリの構造が同じ場合にしか効かないってこと。つまり、パラメータだけ異なるクエリ形式にする必要がある。ここがアプリ側との設計調整で意外と手間なんだ。
コスト管理の自動化
3ヶ月で月50万円→18万円に削減できたのは、この3つに加えて、Cost管理の自動化が大きかった。AWS Cost Explorerで毎日のWPU消費を追跡するパイプラインを作った:
import boto3
import json
from datetime import datetime, timedelta
ce = boto3.client('ce', region_name='us-east-1')
cloudwatch = boto3.client('cloudwatch', region_name='us-east-1')
def track_redshift_wpu_cost():
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
today = datetime.now().strftime('%Y-%m-%d')
response = ce.get_cost_and_usage(
TimePeriod={
'Start': yesterday,
'End': today
},
Granularity='DAILY',
Metrics=['UnblendedCost'],
Filter={
'And': [
{'Dimensions': {'Key': 'SERVICE', 'Values': ['Amazon Redshift']}},
{'Tags': {'Key': 'Environment', 'Values': ['production']}}
]
}
)
cost = float(response['ResultsByTime'][0]['Total']['UnblendedCost']['Amount'])
# CloudWatchカスタムメトリクスに記録
cloudwatch.put_metric_data(
Namespace='Analytics/RedshiftServerless',
MetricData=[{
'MetricName': 'DailyWPUCost',
'Value': cost,
'Unit': 'None',
'Timestamp': datetime.now()
}]
)
# 閾値超過時にアラート
if cost > 10000: # $10,000/日超過時
sns = boto3.client('sns')
sns.publish(
TopicArn='arn:aws:sns:us-east-1:xxxxx:redshift-alerts',
Subject=f'Redshift Cost Alert: ${cost:.2f} on {yesterday}',
Message=f'Daily WPU cost exceeded threshold. Please review queries.'
)
if __name__ == '__main__':
track_redshift_wpu_cost()
これを毎日実行させて、費用が異常値を超えたら即座に気づくようにした。朝の会議で「昨日の請求額が〇〇円」ってのが一目瞭然になるのは、かなり便利。
パフォーマンス改善の数値化
8週間で実装した改善の効果をグラフにまとめてみた。
xychart-beta
x-axis ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6", "Week 7", "Week 8"]
y-axis "Daily Cost ($)" 15000 to 55000
line [52000, 50000, 48000, 42000, 35000, 25000, 20000, 18000]
実装の流れはこんな感じだった:
- Week 1-2: テーブル設計修正(DISTKEY + SORT KEY) → 30%削減
- Week 3-4: WPU時間帯別調整(EventBridge + Lambda) → さらに15%削減
- Week 5-6: Result Cacheの有効化とクエリ最適化 → さらに20%削減
- Week 7-8: 監視と自動化の定着で安定化 → さらに細かい調整で5%削減
合計で64%のコスト削減。最初の見積もりと実績の差がこんなに出るなんて、正直ショックだった。
正直なところ、Serverlessなのか疑問
ここまで最適化を重ねてると思うんだけど、「これって本当にServerlessの利点を活かしてるのか?」って疑問が湧く。WPUの管理、クエリの最適化、キャッシング戦略…すべてProvisioned Clusterでもやる作業なんですよね。
ただ、1つだけ違うのは、スケーリングが柔軟ってこと。月末の大バッチや、突発的なレポート需要が来たときに、インフラを増強する承認プロセスが要らない。その点はすごく楽だ。
うちの場合、Provisioned Clusterだと月50万円かかってた。Serverlessで18万円なら、正直いい。ただし「Serverlessなら何もしなくていい」という期待は完全に間違い。人間の最適化が絶対必要だと痛感した。
失敗から学んだ設計の勘どころ
1. テーブル設計が8割
DISTKEYとSORT KEYを正しく設定するだけで、クエリ時間が3分の1に短縮される。ぶっちゃけ、これが最高のROI。テーブルを作るときに「このカラムはどれくらいの頻度でJOINされるのか」「WHERE句に使われるのか」を考え抜くことが全て。
2. 監視があれば問題は早期発見できる
Performance Insights + CloudWatch Logs を毎日眺めてると、異常なクエリがすぐ分かる。月一度の請求書を見るまで気づかないのが最悪。早期発見が早期対策につながる。
3. キャッシングはパラメータ化が前提
Result Cacheは便利だけど、クエリ構造が同じじゃないと効かない。つまり、アプリ側でパラメータ化クエリを強制する規約が必要。既存のアプリケーションを変更するのって地味に手間なんだが、やる価値はある。
4. バッチはスケジュール分散
全バッチを深夜0時に集中させると、その時間帯のWPUが跳ね上がる。複数時間帯に分散させるだけで、ピークWPUが30%下がる。こういう工夫は地味だけど、継続的な効果が出る。
まとめ
Redshift Serverlessは「Serverless」という名前に騙されやすい。実際には以下の3点を徹底管理する必要がある:
-
テーブル設計とクエリの最適化:これなしに語るべからず。DISTKEYとSORT KEYで大半が決まる。
-
WPU管理と時間帯別チューニング:業務パターンに合わせて最大WPUを動的に調整。Lambda + EventBridgeの自動化で楽になる。
-
Result Cacheの活用と日々の監視:同じクエリが何度も叩かれるなら、キャッシュは必須。Performance Insightsで毎日確認。
正直、初期段階で月50万円も使ってたのは無駄だった。でも3ヶ月で18万円に削減できたから、その過程で得た学びは大きい。次のプロジェクトでは、最初からこの設計で始める。
うちのチームは今、Redshift Serverlessで安定稼働している。ただし「Serverlessだから放置」というわけではなく、毎週1回はパフォーマンスレビューをやってる。その手間を考えると、BigQuery や Athena を選ぶのも十分検討の余地があると思う。実際、BigQuery との比較で悩んでるプロジェクトもある。
とにかく、Serverlessを選ぶなら、テーブル設計と監視の覚悟を決めてから始めるべき。その先に、はじめて本当の「運用負荷の削減」が見えてくる。