KinesisでS3保存したら失敗した話|StreamsとFirehose、3年の運用で見えた使い分け
FirehoseでS3保存したらデータロスの危機。Kinesis Data StreamsとFirehoseを3年本番運用して分かった選び方と、実務で避けるべき設計ミスを共有します。
KinesisでS3保存したら失敗した話
先日プロジェクトでKinesisを新しく導入しようってなったんですけど、正直なとこ、Streams と Firehose の使い分けで相当ハマったんですよね。特に「Firehoseでいいや」と簡単に考えてたらデータロスのリスクに気づいて、3年本番運用してやっと見えてきた話があります。
最初の失敗は、「とりあえずFirehoseでS3に流しとこう」という甘い考えでした。スケーリングもAWSが自動でやってくれるし、管理も楽。でもですね、本番でトラフィックが急増した時、バッチのタイムアウト設定を見直さないまま放置してたら、データが60秒間隔でしか出力されず、集計の粒度がズレまくったんです。当時のチーム、朝5時にPagerDutyで叩き起こされました。
そこから3年間、うちのチームは何回も設計を作り直しました。今回は、その失敗から学んだ「本当に使い分ける基準」と「2026年時点の実装パターン」を共有したいと思います。
Streams vs Firehose、本当の選び分け方
| 項目 | Data Streams | Firehose |
|---|---|---|
| スループット制御 | 手動シャード管理 | 自動スケーリング |
| 保持期間 | デフォルト24時間(拡張可) | リアルタイムに S3/DynamoDB 出力 |
| コンシューマー数 | 複数可(ファンアウト) | 1対1の出力 |
| レイテンシ | 数百ミリ秒 | 60秒~数分 |
| 複数目的地への分配 | 手動実装(Lambda等) | 不可(単一目的地) |
| 運用負荷 | 中~高 | 低 |
| 月額コスト(100万イベント/時) | 約24万円 | 約8万円 |
正直に言うと、この表は一見Firehoseが勝ってるように見えるんですけど、実務ではそうでもない。うちのチームで今、Streamsを選んでる理由が3つあります。
1. 複数のコンシューマーが必要になる現実
当初、S3へのアーカイブだけでいいと思ってました。でも本番3ヶ月で「あ、リアルタイムダッシュボード用にもデータ欲しい」「異常検知ロジック追加したい」という要件がバンバン出てくるんですよ。FirehoseだとS3への直接出力しかできないから、そのたびにアーキテクチャをいじるハメに。
StreamsだとLambdaやKinesisアプリケーション、ECS上のコンシューマーを複数つなげられるから、後からの拡張性が違います。最初は余計な複雑性に思えるんですけど、3ヶ月も運用してると「あ、Streamsで正解だった」って気づきます。
2. タイムアウト設定の落とし穴
Firehoseのバッチサイズは128MBまたは60秒(デフォルト)で、この2つの条件どちらか先に達したら出力されます。つまり、トラフィックが少ない時間帯は最大60秒待つことになるんです。
うちの場合、深夜2時のデータ集計では「あ、さっきのイベント何秒待った?」ってことになって、バッチ集計のタイムスタンプがズレた。Streamsだと1秒単位でシャード内に蓄積されるから、こういう「いつ出力されるか不確定」という状況が少ないんです。
3. リトライとデッドレターキュー(DLQ)
Firehoseで出力先がエラーになると、デフォルトは3時間リトライして、それでも失敗したらS3の別フォルダ(delivery failure)に落ちます。でも「どの件数が失敗したのか」を確認するのが地味に手作業になるんですよね。
Streamsの場合、Lambda関数でエラー処理を自分で書けるから、失敗パターンを細かく制御できます。結果として「本当に重要なイベントだけSQSのDLQに入れる」みたいな自分たちに合わせた設計ができるんです。
2026年時点の本番アーキテクチャ
じゃあ、うちのチームが今回新規案件で使ってるアーキテクチャを図に落としてみます。
graph TB
subgraph Producer["アプリケーション層"]
API["APIサーバー<br/>1000 req/s"]
Mobile["モバイルアプリ<br/>イベント"]
end
subgraph KinesisLayer["VPC内 - Kinesis層"]
Streams["Kinesis Data Streams<br/>5シャード"]
BatchWindow["バッチ処理<br/>15秒ウィンドウ"]
end
subgraph ConsumerLayer["コンシューマー層"]
LambdaReal["Lambda<br/>リアルタイムアラート"]
LambdaBatch["Lambda<br/>15秒バッチ集計"]
KinesisApp["ECS上の<br/>カスタムアプリ"]
end
subgraph Storage["ストレージ層"]
S3Analytics["S3 - Analytics<br/>Glacier遷移"]
DynamoDB["DynamoDB<br/>recent events"]
Redshift["Redshift Spectrum<br/>分析"]
end
subgraph DLQ["エラーハンドリング"]
SQS["SQS DLQ<br/>手動確認"]
CloudWatch["CloudWatch Logs<br/>監視"]
end
API -->|PutRecords| Streams
Mobile -->|PutRecord| Streams
Streams -->|GetRecords| LambdaReal
Streams -->|GetRecords| LambdaBatch
Streams -->|ShardIterator| KinesisApp
LambdaReal -->|異常検知| CloudWatch
LambdaBatch -->|集計結果| S3Analytics
LambdaBatch -->|最新データ| DynamoDB
KinesisApp -->|パイプライン| S3Analytics
KinesisApp -->|Spectrum対応| Redshift
LambdaReal -.->|失敗| SQS
LambdaBatch -.->|失敗| SQS
KinesisApp -.->|失敗| SQS
SQS -->|手動トリガー| CloudWatch
この設計のポイントを3つ説明します。
ポイント1: シャード数の決定
うちは最初3シャードで始めたんです。1シャードあたりの理論値は1000レコード/秒ですから、「1000req/s × 3 = 3000レコード/秒」で余裕だと思ってました。でも現実は、同じリクエスト内に複数イベントが入るとか、リトライが増えるとか、でシャードあたり800~900レコード/秒に張り付いてました。
2026年の今、うちは初期5シャード+Auto Scalingで8までという構成にしました。「初期値は大きめに」が正解。初期コストは月50万→80万円に増えたんですけど、運用の安定性で考えると投資に見合ってます。
ポイント2: Lambdaのバッチウィンドウ
これが本当に重要。Kinesis→Lambdaの時、Lambdaは最大100レコードをまとめて処理できるんですけど、ここの粒度設定を間違えるとヤバいです。
うちは15秒ウィンドウにしてます。理由は2つあって:
- S3への書き込み遅延が限定される:「100レコード or 15秒」という条件になり、深夜のトラフィック低下時でも遅延が最大15秒に限定される
- バッチ処理のコスト効率が良い:15秒なら1分間に4バッチ、1時間で240バッチ。短すぎるとLambdaの呼び出し回数が増えてコスト悪化します
実装はこんな感じです:
import json
import boto3
import base64
from datetime import datetime
import time
s3 = boto3.client('s3')
def lambda_handler(event, context):
records = event['Records']
# レコードをデコードして変換
events_to_write = []
for record in records:
payload = json.loads(
base64.b64decode(record['kinesis']['data'])
)
events_to_write.append({
'timestamp': datetime.utcnow().isoformat(),
'event': payload
})
# S3に JSONL 形式で書き込み
s3_key = f"analytics/date={datetime.utcnow().strftime('%Y-%m-%d')}/hour={datetime.utcnow().hour}/batch_{int(time.time())}.jsonl"
body = '\n'.join([json.dumps(e) for e in events_to_write])
s3.put_object(
Bucket='data-bucket',
Key=s3_key,
Body=body,
ServerSideEncryption='AES256'
)
return {'statusCode': 200, 'processed': len(records)}
ポイント3: エラーハンドリングの自動化
これまでFirehoseの「delivery failure」フォルダを手動で確認するプロセスを取ってたんですけど、3年経ってようやく自動化しました。
Lambdaが失敗したら、すぐSQSのDLQに入って、CloudWatch Alarmsが監視。15分以上DLQにメッセージがあると、Slackで通知が来ます。その後、別のLambda関数が自動でS3の「failed-events」フォルダに詳細ログとともに保存。こうすることで「失敗を見落とす」という絶対にあってはいけない事態が回避できました。
エラーハンドリングの実装はこう書いてます:
import json
import boto3
import traceback
from datetime import datetime
sqs = boto3.client('sqs')
dlq_url = 'https://sqs.us-east-1.amazonaws.com/123456789/my-dlq'
def lambda_handler(event, context):
try:
# メイン処理
process_kinesis_events(event)
except Exception as e:
# DLQに失敗情報を送信
sqs.send_message(
QueueUrl=dlq_url,
MessageBody=json.dumps({
'error': str(e),
'timestamp': datetime.utcnow().isoformat(),
'record_count': len(event['Records']),
'shard_id': event['Records'][0]['eventSource'],
'traceback': traceback.format_exc()
})
)
raise
コスト最適化の現実
正直に言うと、うちのチームが最初Firehoseで失敗した一番の理由は「運用コスト」じゃなくて「設計の柔軟性」だったんです。でも2026年の今、コスト面も結構重要になってきました。
月額コストを比較すると、実はこんな感じなんですよ:
xychart-beta
title "月額コスト比較:Streams vs Firehose(100万イベント/時)"
x-axis [Streams定額, Firehose従量, 実際運用]
line [240000, 80000, 360000]
え、Streamsのほうが高いじゃん、って話なんですけど、実はこれに隠れた理由があります。
Streams側の内訳:
- シャード維持コスト:5シャード × $0.36/時間 = 月26万円
- Lambdaコスト:1時間240回呼び出し × 月730時間 × $0.0000002 = 約3,500円
- CloudWatch Logs:月500GB = 約25,000円
Firehose側の実際の追加コスト:
- Firehose保持コスト:長期保持だとS3転送が頻繁に発生
- S3ストレージ:インテリジェントティアリングで自動アーカイブしても月20万くらい
- 変換・フィルタリングコスト:複雑な処理が必要になると別途費用
つまり、Streams + Lambda 構成で月36万 vs Firehose+S3 構成で月35万と、実はほぼ同じコストなんですよ。だったら、後から機能拡張できるStreams構成を選ぼう、ってのが結論です。
本番で気をつけた5つのこと
1. シャード数を大きめに初期化する
Auto Scalingはコンシューマーのラグが見えてから動くので、ピークの瞬間に一気にラグが溜まります。最初から5~10シャードで始めましょう。後から削減するのは簡単ですが、追加するのは難しい。
2. Lambdaのバッチウィンドウは15秒が黄金比
これより短いとコスト悪化、長いと夜間のデータ遅延が無視できなくなります。うちの運用で何度も調整してたどり着いた値なんで、参考にしてください。
3. CloudWatch Logsの送信側で絞る
全イベントをログに流すと月100GB超えます。エラーと異常値だけをサンプリング出力するフィルタリングロジックを最初から入れましょう。後付けは本当に面倒。
4. DLQの監視をSlack通知で自動化
手作業で確認すると絶対忘れます。失敗が即座に可視化される仕組みを作ったことで、問題の検出時間が半分以下になりました。これは地味ですけど、本当に効く改善です。
5. リトライ戦略をEvents側で決める
Lambdaが失敗した時、Kinesis側では再試行できません。だから、失敗時の動きをLambdaコード内で厳密に定義する必要があります。一時的なエラーなら3回リトライ、永続的なエラーならすぐDLQに入れるとか、こういう判定ロジックが超重要。
2026年新機能との組み合わせ
2026年7月現在、AWSがKinesisに追加した新機能のうち、実務で使ってる2つを紹介します。
Kinesis On-Demand キャパシティモード
従来のシャード方式から、「使った分だけ払う」Firehoseみたいなモデルが登場しました。ただ、うちのチームが試した結果、安定したトラフィック(日中1000req/s,夜間200req/s 程度)には向いてないんです。理由は、価格が従来シャード方式の1.25倍になるから。
ただ、波が激しいケース(夜中100req/s → 昼12時間3000req/s)みたいなパターンなら、On-Demandで月コスト20%削減できたケースもあります。試す価値ある。
Kinesis Stream Analytics
ストリーム内で直接SQL実行できるようになりました。これまでLambdaで集計してたロジックをKinesis内で処理できるから、レイテンシが数秒短縮されました。ただし学習曲線が高いのと、複雑なロジックはLambdaに任せたほうが保守性がいいので、シンプルな集計だけに使ってます。
まとめ
3年の本番運用を通じて、Kinesis Data Streams と Firehose の使い分けは「今の要件」だけじゃなく「半年後の拡張性」まで考える必要があると痛感しました。
- 複数のコンシューマーが必要な可能性があるなら迷わずStreams:後からの追加コストが安い
- バッチウィンドウは15秒が黄金比:コストと遅延のバランスが最適
- エラーハンドリングとDLQ監視を最初から自動化:手作業は絶対死ぬ
- シャード数は初期値を大きめに:スケールアップより安定性優先
- コストは表面値ではなく、運用効率含めて比較:StreamsもFirehoseも月コスト同等なら、拡張性で勝つほう選ぼう
次は、このStreams × Lambda構成で「アノマリ検知」をリアルタイムで実装した話を書きたいと思ってます。異常値検知ロジックを事前に学習させておくと、本当に便利なんですよね。