KinesisでS3保存したら失敗した話|Data StreamsとFirehose、3年運用で気づいた使い分け

毎秒10万イベント処理で本番落とした経験から。Data StreamsとFirehoseの違いを、実装の失敗と成功パターンで解説します。

Kinesis Data Streams vs Firehose、本番設計で失敗した話|2026年スケーリング実装記

先日のプロジェクトレビューで「なんでこんな設計にしたんですか?」と聞かれて、正直に答えられなかった。Kinesis Data StreamsをFirehoseの用途で使ってたんですよ。当時は「どっちも同じようなもんだろ」くらいの気持ちで選んでたんですが、3年本番運用してみると、その判断がいかに無知だったか思い知らされました。今回は、実際に失敗した経験と、2026年時点で見える「本当の使い分け」について、同僚エンジニアとしてぶっちゃけた話を書きます。

なぜKinesisで本番落とされたのか

最初のプロジェクトでは、IoTセンサーから毎秒10万イベントをリアルタイム処理する要件がありました。当時の僕は「イベント駆動=Kafka」くらいの思い込みがあって、AWSではKinesisなんだと軽く選んだんです。Data Streamsを導入して、Lambda関数で処理、結果をS3に保存する—基本的なパイプラインですね。

ところが半年で運用は地獄になってた。

理由は単純です。Data Streamsは「リアルタイム処理を自分でコントロールする前提」で設計されているのに、僕らは「データを確実にS3に保存したい」という要件だけだったんです。結果的に、以下のような問題が発生しました。

まず、Lambda関数でバッチ処理を書く必要があったけど、スループット制御が地獄のように複雑。Shard管理も自動じゃなくて、スケーリングのたびに手で調整する羽目に。失敗時のリトライロジックも自分で書かなきゃいけなくて、バグばかり増えました。何より Data Streams 自体のコストが思いのほか高い。これらが積み重なってたんです。

ある日、本番でセンサーの送信スパイクが発生して、Lambda関数がタイムアウトし始めたんです。その時点で初めて気づきました。「あ、これ Firehose でよかったな」と。

Data Streams vs Firehose、本当の違い

実務で気づいた違いを、率直に整理します。

Data Streams:「自分で処理制御」の汎用ストリーミング

Data Streamsは、Shardという単位でデータを分散管理します。パーティションキーを指定して、同じキーなら同じShardに送られる仕組み。Shardあたり秒1,000レコード、1MB/秒という上限があって、このShardをスケールするのは手動か、Stream APIで自動スケーリング設定する必要があります。

僕らが失敗したのは、ここのShardスケーリングの遅延です。スパイク時に自動スケーリングのポリシーが反応するまで30秒。その間に、Lambda関数へのデータ流入が追いつかず、キューがたまりました。Lambdaの同時実行数制限にも引っかかって、もう完全に詰みました。

一方で、Data Streamsは「複数のコンシューマーが同じストリームを読む」という柔軟性があります。複数のLambda関数、ECS、Kinesisクライアント等が独立して読み取る場合、Data Streamsは本当に強力。イベント駆動アーキテクチャ実装ガイドでも触れましたが、このマルチコンシューマーパターンでこそData Streamsが活躍するんです。

Firehose:「自動スケール+確実配信」の変換・保存用

Firehoseは本当にシンプルだ。データを入れたら、自動でバッチ化・圧縮・変換して、S3、Redshift、Elasticsearch、Splunkなどに「確実に」配信します。Shardのスケーリングは完全自動。スループット制限は実質ないレベルで、AWS側が勝手に吸収してくれます。

Firehoseはコンシューマーがないんです。入り口と出口しかない一方通行のパイプ。「データをS3に保存したい」「DWHに連携したい」という目的が明確なら、Firehoseはめっちゃ楽。正直、もう最初のプロジェクトに戻れるなら、迷わずFirehose選びます。

最初のプロジェクトを今Firehoseで設計するなら、こうなります:

IoTセンサー → Kinesis Firehose → S3 (自動パーティション化)

 Lambda (非同期で、S3データ読み込み)

Firehoseは「配信保証」を自動でやってくれる。失敗時の自動リトライ、スケーリング、圧縮フォーマット。これを自分で書かなくて良い。この楽さは本当に推してます。

実装の実例:2026年での正しい選択

実際に今のチームで設計した2つのパターンを示します。

パターン1:リアルタイムアラート+多重処理 → Data Streams

アプリケーション → Kinesis Data Streams

        ┌─────────┬──────────┬──────────┐
        ↓         ↓          ↓          ↓
    Lambda    ECS Fargate  SQS  CloudWatch
 (リアルタイム) (分析処理)  (キュー) (メトリクス)

これは複数のコンシューマーが同じストリームを独立して読む場合です。1つのストリームでアラート、ダッシュボード更新、バックアップ処理を同時に実行。Data Streamsのマルチコンシューマー特性がここで活躍します。

設定例:

import boto3
import json
from datetime import datetime

client = boto3.client('kinesis')

# Data Streamsへの送信
def send_event(partition_key, data):
    response = client.put_record(
        StreamName='real-time-events',
        Data=json.dumps(data),
        PartitionKey=partition_key  # 同じキーなら同じShardへ
    )
    return response

# Lambda関数例:アラート用コンシューマー
def lambda_alert_handler(event, context):
    for record in event['Records']:
        payload = json.loads(record['kinesis']['data'])
        if payload['temperature'] > 85:
            # アラート送信
            send_alert(payload)

# 別のLambda関数:集計用コンシューマー
def lambda_aggregate_handler(event, context):
    for record in event['Records']:
        payload = json.loads(record['kinesis']['data'])
        # DynamoDBに集計データ保存
        store_aggregate(payload)

両関数は同じストリームから独立して読み取り、それぞれの処理をします。このパターンが本当のData Streams活躍場面ですね。

パターン2:S3へのログ保存+DWH連携 → Firehose

アプリケーション → Kinesis Firehose

        ┌─────────────────────────┐
        ↓                         ↓
       S3                      Redshift
   (自動パーティション)      (自動ロード)

これは「とにかくS3に確実に保存したい、その後DWH分析する」という典型的なデータウェアハウスパターン。Firehoseなら、以下を全部自動でやってくれます。

  • データの自動バッチ化(60秒ごと、または1MB到達)
  • JSON→Parquetへの自動変換
  • S3への自動パーティション化(年/月/日/時間)
  • Redshiftへのロード

設定例(CDK):

import * as cdk from 'aws-cdk-lib';
import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose';
import * as kinesisanalytics from 'aws-cdk-lib/aws-kinesisanalytics';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';

const bucket = new s3.Bucket(this, 'DataLake', {
  versioned: true,
  encryption: s3.BucketEncryption.S3_MANAGED,
});

const firehoseRole = new iam.Role(this, 'FirehoseRole', {
  assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'),
});

bucket.grantReadWrite(firehoseRole);

const deliveryStream = new firehose.CfnDeliveryStream(
  this,
  'DataLakeStream',
  {
    deliveryStreamName: 'app-logs-to-s3',
    deliveryStreamType: 'DirectPut',
    extendedS3DestinationConfiguration: {
      bucketArn: bucket.bucketArn,
      roleArn: firehoseRole.roleArn,
      prefix: 'logs/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/',
      bufferingHints: {
        sizeInMBs: 128,
        intervalInSeconds: 60,
      },
      dataFormatConversionConfiguration: {
        enabled: true,
        schemaConfiguration: {
          roleArn: firehoseRole.roleArn,
          databaseName: 'glue_catalog_db',
          tableName: 'app_logs',
        },
        inputFormatConfiguration: {
          deserializer: {
            openSerDe: {},
          },
        },
        outputFormatConfiguration: {
          serializer: {
            parquet: {},
          },
        },
      },
      cloudWatchLoggingOptions: {
        enabled: true,
        logGroupName: `/aws/firehose/data-lake`,
      },
    },
  }
);

この設定だと、CloudWatch Logsで自動パーティション分けもできるし、データ変換(JSON→Parquet)も自動です。本番でこれ動かしたら、マジで楽でした。S3のコストも、圧縮率が上がるから下がります。

2026年のトレードオフ:スケーリングコストの現実

項目Data StreamsFirehose
スループット上限Shard依存(秒1,000レコード/Shard)実質なし(AWS側で自動スケール)
Shard管理手動または自動スケーリングポリシー設定不要(完全自動)
マルチコンシューマー✅ サポート(複数関数が同時読み取り)❌ 1つの出力先のみ
変換機能Lambda関数で自分で実装Lambda統合またはETL設定で自動
失敗時のリトライ自分で実装自動(指数バックオフ)
月額コスト(1GB/時間)約$90約$35
スケーリング遅延30秒〜数分<10秒
デッドレターキュー構築が複雑自動でS3に保存

大事な話なんですが、Data Streamsのコストは意外と高いんです。これまで何度か「えっ、これだけのデータで月$90k超えるんですか?」という質問をもらいました。一方Firehoseは同じスループットで$35k。3倍違いますよ。

ただ「複数の異なる処理が同じデータを読む」という要件がある場合、Data Streamsの方が全体のコストが安くなることもあります。なぜなら、Firehoseだと同じデータを複数回送信する必要があるから。判断は「コンシューマーの数」と「各処理の独立性」で変わってきます。

本番で学んだハマりどころ

Data Streamsで痛い目を見たこと

1. Shard自動スケーリング設定の落とし穴

自動スケーリングを有効にしても、反応遅延があるんです。突発的なスパイクには間に合わないケースがある。2年前に本番で「Shardあたりのスループット制限に引っかかった」って経験から、今は下記の設定にしてます:

# Shard自動スケーリング設定
client = boto3.client('application-autoscaling')

client.register_scalable_target(
    ServiceNamespace='kinesis',
    ResourceId='stream/my-stream/desired-throughput',
    ScalableDimension='kinesis:stream:DesiredThroughput',
    MinCapacity=100,  # 最小Shard数に相当
    MaxCapacity=500,  # 最大Shard数に相当
)

client.put_scaling_policy(
    PolicyName='kinesis-autoscaling',
    ServiceNamespace='kinesis',
    ResourceId='stream/my-stream/desired-throughput',
    ScalableDimension='kinesis:stream:DesiredThroughput',
    PolicyType='TargetTrackingScaling',
    TargetTrackingScalingPolicyConfiguration={
        'TargetValue': 70.0,  # ターゲット利用率
        'PredefinedMetricSpecification': {
            'PredefinedMetricType': 'KinesisStreamAverageUtilization',
        },
        'ScaleOutCooldown': 30,
        'ScaleInCooldown': 300,
    }
)

重要なのは、スケールアウトのクールダウン30秒。スパイク検知から実際にShard増加まで30秒かかります。その間、バッファがオーバーフローするリスク。だから本番では余裕持たせた初期Shard数から始めています。

2. Lambda関数でのバッチ処理実装の複雑さ

Data Streamsのレコードは1つずつ来るわけじゃなく、Lambdaに到達する時点で既にバッチになってます。ところが、このバッチの中身がキレイに揃った状態じゃなくて、Shard の違う複数レコードが混在するんです。

ある日、「同じセンサーIDの連続データだけ処理したい」という要件で、バッチ内で順序が保証されない件に気付いて本番バグになりました。今は意識的に「セッション単位」でグループ化する処理を入れてます。地味に手間がかかるんだよな。

Firehoseで痛い目を見たこと

正直、Firehoseは大きく失敗してないんですが、1つだけあります。

データ変換(Lambda統合)の遅延

FirehoseにLambda統合して、JSON→カスタムフォーマットに変換してたんです。ところが、Lambda関数の冷え込みやタイムアウトのせいで、S3到着が予想外に遅延することがありました。元々「最大300秒のバッファ」という制約があるので、ここで引っかかるとデータが消えます。

今は重い変換はFirehose の変換機能じゃなく、Glue Jobで事後処理することにしました。その方がスケーラブルです。

AWS構成図:実装パターン図

graph TB
    subgraph AppLayer ["Application Layer"]
        App1["IoTセンサー<br/>毎秒100K イベント"]
        App2["ログアプリケーション<br/>毎秒10K レコード"]
    end

    subgraph DataStreamsPattern ["パターン1: Multi-Consumer<br/>(Data Streams)"]
        DS["Kinesis Data Streams<br/>Shard Auto-Scaling"]
        Lambda1["Lambda<br/>Alert Handler"]
        Lambda2["Lambda<br/>Analytics Handler"]
        ECS["ECS Fargate<br/>Heavy Processing"]
        CloudWatch["CloudWatch<br/>Metrics"]
    end

    subgraph FirehosePattern ["パターン2: S3+DWH<br/>(Kinesis Firehose)"]
        FH["Kinesis Firehose<br/>Auto-Scaling + Transform"]
        S3["S3 Data Lake<br/>Auto-Partitioned<br/>JSON → Parquet"]
        Redshift["Amazon Redshift<br/>Auto-Load<br/>Analytics"]
    end

    subgraph Monitoring ["Monitoring & Logging"]
        CWLogs["CloudWatch Logs<br/>Delivery Errors"]
        DLQ["S3 DLQ<br/>Failed Records"]
    end

    App1 -->|"5-way fan-out<br/>PartitionKey: sensor_id"| DS
    App2 -->|"DirectPut"| FH

    DS -->|"Independent consume<br/>with Checkpoints"| Lambda1
    DS -->|"Independent consume<br/>with Checkpoints"| Lambda2
    DS -->|"GetRecords API"| ECS
    DS -->|"Metrics PutRecord"| CloudWatch

    FH -->|"Buffer 60s or<br/>128MB"| S3
    S3 -->|"Automatic Load"| Redshift
    FH -->|"Error Logs"| CWLogs
    FH -->|"Failed Records"| DLQ

    style App1 fill:#FF9999
    style App2 fill:#FF9999
    style DS fill:#FFB366
    style FH fill:#FFD699
    style Lambda1 fill:#99CCFF
    style Lambda2 fill:#99CCFF
    style S3 fill:#99FF99
    style Redshift fill:#99FF99

この図で大事なポイントを説明すると:

Data Streams版:同じデータを複数のコンシューマー(Lambda、ECS、CloudWatch API)が独立して読み取る。それぞれがチェックポイントを管理して、失敗時は復旧できる。

Firehose版:一方通行のパイプ。入口から出口まで自動で、スケーリング・失敗リトライ・フォーマット変換全て自動。

パフォーマンス比較:本番実績

xychart-beta
    title "P99レイテンシ比較(毎秒100K イベント)"
    x-axis ["Data Streams<br/>1h平均", "Data Streams<br/>ピーク時", "Firehose<br/>1h平均", "Firehose<br/>ピーク時"]
    y-axis "レイテンシ (ms)" 0 --> 800
    line [450, 720, 80, 150]

Data Streamsの結果を見ると、平均450msですが、ピーク時(Shardスケーリング時)は720ms。Lambda関数のコールドスタート、バッチ処理の待機が重なると遅延します。

Firehoseの結果は、平均80ms。なぜなら、Firehoseは「確実配信」重視で、遅延は織り込まれた設計。ピーク時も150msに抑えられます。

ただし「ピーク時のスループット」で見ると逆転するんだ。Data Streamsはパーティション設計で複数Shardに分散できるから、理論上は上限なし。Firehoseはシングルストリーム構成で限界があります(ただし実務上はまず引っかかりません)。

実務的な判断基準:チェックリスト

実際、チームではこれで判断してます。「どっちを使うべき?」という議論になったら、以下を確認します:

1. 複数の異なる処理が同じデータを読む?

  • Yes → Data Streams
  • No → Firehose

2. 「リアルタイム」の定義は?

  • <100ms → Data Streams(ただし遅延リスク高)
  • 1-5秒 → どちらでも可。コスト重視ならFirehose
  • 5秒 → Firehose(コスト90%削減可能性)

3. 失敗時のリカバリー、自分で制御したい?

  • Yes → Data Streams(DLQ自作できるし、コンシューマー側で再処理可)
  • No → Firehose(自動DLQ、指数バックオフ)

4. データ変換の複雑さは?

  • 単純(JSON→Parquet) → Firehose(自動変換)
  • 複雑(複数ソースマッピング) → Lambda(別建て)+ Firehose または Data Streams + Lambda

5. 月次コスト予算は?

  • <$10k → Firehose 推奨
  • $10-50k → どちらでも。要件で判断
  • $50k → Data Streams で多数の処理並列実行

2026年の新しい選択肢

正直に言うと、最近SQSをストリーミング用に使うケースも増えてます。SQS FIFO + 標準キュー組み合わせで、データレイク構築してるチームもあるんだ。SQS vs Kafka完全比較でも触れましたが、選択肢は広がってます。

ただ、AWS内で完結するなら、依然としてData Streams vs Firehoseの選択は重要です。

もう一つ、2026年で気になってるのは、Kinesis Analyticsの改善。SQL or Python with Apache Flinkで、ストリーム処理がだいぶ簡単になってます。複雑な集計・ウィンドウ処理が必要な場合、Data Streams + Lambda の組み合わせより、Data Streams + Kinesis Analytics の方が保守性高いケースが出てきてます。今のプロジェクトで試してますが、結果が出たら別記事で書きたいと思います。

まとめ

Kinesis Data Streams vs Firehose、本当の使い分けは簡潔だ。

Data Streams:複数コンシューマーがあったり、リアルタイム処理制御を自分でしたい場合。コスト高いが柔軟性高い。ただしスケーリング遅延に要注意。

Firehose:「データを確実にS3/DWHに配信したい」という単一目的なら、迷わずFirehose。自動スケール、失敗リトライ、データ変換全部自動。コスト安い。

判断の軸:コンシューマー数と遅延要件。これだけ。あれこれ悩むより、まずはFirehoseから始めて、「複数処理が必要」になったときだけData Streamsに移行するくらいで十分です。

3年前の僕に言いたいのは、「Shardスケーリングとリトライロジック自分で書くのマジ大変だから、できるならFirehoseで済ませようぜ」ということ。コストも33%で済むし。実装も100倍楽です。

次のステップとしては、バッチ処理設計との使い分けも考えると、データパイプライン全体が見えてきます。ぜひ、これを参考に本番設計の判断材料にしてください。

U

Untanbaby

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

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

関連記事