Chuyển đến nội dung chính

レッスン 15: 実稼働環境への展開 — 1,000 万件の電子メールを送信するケーススタディ

エンドツーエンドのケーススタディ: マーケティング キャンペーンのために 1,000 万通の電子メールを送信するシステムの設計と実装。インフラストラクチャのセットアップ、Kubernetes のデプロイメント、CI/CD、負荷テスト、カオス シナリオ、コスト分析、および運用上の教訓を学びました。

🏗️ アーキテクチャ — レッスン 15 レッスン 15: 実稼働環境のデプロイ — ケーススタディ 1,000 万件のメールを送信

数百万の電子メールを送信する通知システムを設計する

パート 5: 到達性、監視、および運用

xdev.asia

はじめに

このシリーズは現実的な問題で終わります。電子商取引会社は、トランザクション フローを正常に実行し続けながら、4 時間で 1,000 万件のフラッシュ セール メールを送信する必要があります。この記事では、これまでのすべての要素を組み合わせて、運用可能な設計を作成します。


1. 問題と入力の仮定

ビジネス要件

  • 最大 4 時間で 1,000 万件の電子メールを送信します。
  • 名前、割引コード、ロケールによる基本的なパーソナライゼーションがあります。
  • 購読解除、オープン追跡、クリック追跡があります。
  • OTP および注文確認には影響しません。

容量目標

10,000,000 / 14,400 giây ≈ 694 emails/giây
Peak headroom x2 -> thiết kế cho ~1,400 emails/giây

運用アーキテクチャ

  • 1 つの主要プロバイダー: Amazon SES。
  • 1 つのバックアップ プロバイダー: SendGrid。
  • 2 つの個別のワーカー プール: トランザクション マーケティングとバルク マーケティング。
  • レート制限とスケジューリング用の 1 つの Redis クラスター。
  • イベント駆動型バックボーンとしての 1 つの Kafka クラスター。
  • メタデータと分析の取り込み用の 1 つの PostgreSQL プライマリ + リード レプリカ。

2. 全体的な運用アーキテクチャ

Admin UI / Campaign API
        │
        ▼
Campaign Planner
        │
        ├── Recipient Snapshot Service
        ├── Batch Planner
        └── Kafka topics
                │
                ▼
          Bulk Worker Pool
                │
        ┌───────┴────────┐
        ▼                ▼
   Amazon SES        SendGrid Fallback
        │                │
        └───────┬────────┘
                ▼
        Webhook Ingestion
                │
                ▼
         Status Aggregator
                │
                ▼
      PostgreSQL + Grafana/Prometheus

主な成分

成分役割
キャンペーンプランナースナップショットとバッチ ジョブを作成する
カフカ生産者と消費者の分離
レディスリミッター、再試行スケジュール、分散ロック
バルクワーカーキャンペーントラフィックをレンダリングして送信
トランザクションワーカー重要なトラフィックを確保
Webhook の取り込み配送/返送/苦情イベントを受け取る

3. 推奨される Kubernetes インフラストラクチャ

ワークロードを名前空間とデプロイメントごとに分離する

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bulk-email-workers
spec:
  replicas: 12
  selector:
    matchLabels:
      app: bulk-email-workers
  template:
    metadata:
      labels:
        app: bulk-email-workers
    spec:
      containers:
        - name: worker
          image: ghcr.io/xdev/notification-workers:2026.04.01
          env:
            - name: WORKER_GROUP
              value: bulk
            - name: KAFKA_CONSUMER_GROUP
              value: bulk-workers
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2"
              memory: "2Gi"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080

最初のサイズ提案

サービス数量メモ
API / プランナー3 ポッド基本血圧
バルクワーカー12~40ポッドラグによる自動スケール
トランザクションワーカー4~8ポッド予約容量
Webhook プロセッサ3~6ポッドコールバックバーストによるスケール
レディス3 ノードセンチネル/クラスター
カフカ3 ブローカーレプリケーション係数 3

4. CI/CD とリリース戦略

パイプラインが存在するはずです

  1. テンプレート レンダリング、リミッター、プロバイダー アダプターの単体テスト。
  2. Kafka、Redis、PostgreSQL との統合テスト。
  3. スモーク テストはサンドボックス プロバイダー経由で電子メールを送信します。
  4. Canary を新しいワーカー バージョンにデプロイします。
  5. 送信失敗率が増加した場合は、すぐにロールバックします。

なぜ労働者にはカナリアが必要なのでしょうか?

レンダラーまたはプロバイダー アダプターの 1 つの小さなバグにより、1,000 万件の電子メールが 1,000 万件のエラーに変わる可能性があります。 Canary 1 ~ 5% のトラフィックは、キャンペーンが広範囲に影響を受ける前に回帰を検出するのに役立ちます。


5. k6 による負荷テスト

キャンペーン調整部分の負荷テストを行わずに運用トラフィックをテストすることは不可能です。

キャンペーン API の k6 の例

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  scenarios: {
    create_campaigns: {
      executor: 'ramping-vus',
      startVUs: 1,
      stages: [
        { duration: '1m', target: 20 },
        { duration: '3m', target: 100 },
        { duration: '2m', target: 0 },
      ],
    },
  },
};

export default function () {
  const payload = JSON.stringify({
    campaign_id: `camp-${__VU}-${__ITER}`,
    template_id: 'flash_sale_v2',
    segment_id: 'active_users_30d',
    priority: 'normal',
  });

  const response = http.post('https://api.example.com/campaigns', payload, {
    headers: { 'Content-Type': 'application/json' },
  });

  check(response, {
    'campaign accepted': (r) => r.status === 202,
  });

  sleep(1);
}

API 以外に何をテストするか

  • プロバイダーがスロットルするとキューのバックログが増加します。
  • ラグが増加した場合のワーカーの自動スケーリング。
  • Redis は、同時アクセスが多い場合の遅延を制限します。
  • プロバイダーのフラッシュ イベント後の Webhook 取り込みバースト。

6. カオスシナリオをシミュレートする必要がある

状況期待
SES ペイ 429 は 15 分間続きます減速 + SendGrid への部分的なフェイルオーバー
Redis によりレイテンシが増加するワーカーは劣化しますが、大量に複製はされません。
ワーカー ポッドの 20% が死亡バッチ リースが回収され、再開されます。
Webhook プロセッサのダウンタイムイベントはバッファリングされ、状態は失われません。
キャンペーンのテンプレートのバグキャンペーンは一時停止されていますが、他のトラフィックはまだ安全です

カオス テストの目標

システムが不滅であることを証明するためではなく、障害が発生したときに、制御され、観察可能で、回復可能な方法で障害が発生することを確認するためです。


7. 予備的なコスト分析

大きなコスト要素

カテゴリー見積もり
Amazon SES は 1,000 万件のメールを送信約1,000ドル
SendGrid フォールバック リザーブプランに応じて数百ドルから数千ドル
Kubernetes コンピューティングクラウドと自動スケール ウィンドウに依存します
カフカ/Redis/PostgreSQL固定基本コスト
可観測性Prometheus/Grafana 管理またはセルフホスト

コストの最適化

  • SES を大容量ワークロードのメインプロバイダーとして使用します。
  • 災害シナリオに十分なレベルでのみフォールバック プロバイダーを有効にしてください。
  • メインの PostgreSQL に負荷全体を負担させることなく、負荷の高い分析を非同期パイプラインに分離します。
  • テンプレートのレンダリング キャッシュを最適化して、ワーカーの CPU を削減します。

8. 生産から学んだ教訓

正しい決断

  • トランザクションワーカーとバルクワーカーを最初から分離します。
  • メッセージを識別します message_id 冪等に対して安定。
  • 大規模なキャンペーンを実行する前に、ダッシュボード キャンペーンの ETA と苦情率を作成します。
  • ドメイン/IP を当初の予想よりも慎重にウォームアップします。

痛みはあるが貴重な教訓

  1. ワーカーの理論上のスループットは、プロバイダーを介した実際のスループットほど重要ではありません。
  2. 同じドメイン/IP を使用している場合、不適切なマーケティング キャンペーンによりトランザクション トラフィックの評判も損なわれる可能性があります。
  3. ジッターなしで再試行すると、すぐに自分自身による DDoS に変わります。
  4. どの電子メールが実際に配信されたかを知るには、Webhook 調整が必要です。

9. 1,000 万電子メールキャンペーンの稼働チェックリスト

  • SPF、DKIM、DMARC ドメインが認証され、調整されています。
  • セグメントがクリーンアップされ、抑制リストが適用されました。
  • 設定されたプロバイダー/ドメイン/IP に応じたレート制限。
  • ダッシュボード、アラート、ランブックが用意されています。
  • フォールバックプロバイダーはテスト済みです。
  • 小規模なカナリア キャンペーンは正常に実行されました。
  • オンコールローテーションは、ページのしきい値とキャンペーンを一時停止する方法を正確に認識します。

概要

1,000 万件のメール送信の問題は、スケール ワーカーだけの問題ではありません。これは、イベント駆動型アーキテクチャ、配信可能性、レート制御、システム監視、および操作手順が同時に発生する問題です。これらのレイヤーを組み合わせて設計すると、大規模なキャンペーンはギャンブルではなく、予測可能で制御可能なワークロードになります。

大規模な電子メール通知プラットフォームを設計するための、高レベルの設計から運用展開まで、中核となるナレッジ チェーンを学習しました。