EXERCISE 90分

ストーリー

高橋アーキテクト
SpeedShopが急成長している。現在月間100万PVだが、半年後には月間1000万PVを見込んでいる。10倍のトラフィックに耐えるアーキテクチャを設計してほしい

高橋アーキテクトが新たなミッションを提示した。

高橋アーキテクト
スケーリング、ステートレス化、DB分離、非同期化。全てを組み合わせた総合的な設計力が問われる

ミッション概要

ミッションテーマ難易度
Mission 1現状のボトルネック分析初級
Mission 2ステートレス化の設計中級
Mission 3データベーススケーリング戦略中級
Mission 4非同期処理の導入中級
Mission 5オートスケーリング設計上級
Mission 6総合アーキテクチャ図上級

Mission 1: 現状のボトルネック分析(10分)

以下の現状アーキテクチャのボトルネックを洗い出してください。

現状アーキテクチャ:
- App Server: 1台(Node.js、セッションをメモリに保存)
- Database: PostgreSQL 1台(読み書き両方)
- ファイル保存: ローカルディスク
- メール送信: API内で同期実行
- キャッシュ: なし
解答例
ボトルネック問題影響
セッションのメモリ保存水平スケーリング不可スケールアウトできない
DB 1台読み書き集中負荷増で応答遅延
ローカルファイル保存サーバー固有の状態スケールアウト不可
同期メール送信API応答遅延ユーザー体験の悪化
キャッシュなし全リクエストがDBへ不要なDB負荷
単一サーバーSPOF障害で全サービス停止

改善の優先順位:

  1. キャッシュ導入(最も効果が大きい)
  2. セッション外部化(スケーリングの前提条件)
  3. メール送信の非同期化(簡単に実装可能)
  4. Read Replica導入
  5. ファイルストレージ外部化
  6. 水平スケーリング + ロードバランサー

Mission 2: ステートレス化の設計(15分)

現状のステートフルなアプリケーションをステートレスに変換する設計を行ってください。

対象

  1. セッション管理(メモリ → ?)
  2. ファイルアップロード(ローカルディスク → ?)
  3. 定期バッチ処理(各サーバーのcron → ?)
解答例
// 1. セッション管理: Redis に外部化
// Before
app.use(session({ store: new MemoryStore() }));

// After
app.use(session({
  store: new RedisStore({
    client: redisClient,
    prefix: 'session:',
    ttl: 1800, // 30分
  }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
}));

// 2. ファイルアップロード: S3 に外部化
// Before
const storage = multer.diskStorage({
  destination: '/uploads/',
});

// After
const storage = multerS3({
  s3: s3Client,
  bucket: 'speedshop-uploads',
  key: (req, file, cb) => {
    cb(null, `uploads/${Date.now()}-${file.originalname}`);
  },
});

// 3. 定期バッチ処理: 専用ワーカーサービスに分離
// Before: 各サーバーの crontab
// * * * * * node /app/scripts/cleanup.js

// After: 専用ワーカー(1台のみ)
// docker-compose.yml
// services:
//   worker:
//     image: speedshop-worker
//     deploy:
//       replicas: 1  # バッチ処理は1台のみ
//     command: node worker.js

Mission 3: データベーススケーリング戦略(20分)

SpeedShopのデータベース構成を設計してください。

前提情報

  • 現在のDB負荷: 読み取り80%、書き込み20%
  • テーブル: users(50万件)、products(5万件)、orders(200万件)、order_items(500万件)
  • 最も重いクエリ: 注文履歴検索、商品ランキング集計
解答例
// Phase 1: Read Replica 導入(即効性あり)
const dbConfig = {
  primary: {
    host: 'db-primary.internal',
    role: 'write + critical reads',
  },
  replicas: [
    { host: 'db-replica-1.internal', role: 'general reads' },
    { host: 'db-replica-2.internal', role: 'general reads' },
  ],
};

// ルーティングルール
const routingRules = {
  // Primary(書き込み + 整合性が必要な読み取り)
  primary: [
    'INSERT/UPDATE/DELETE (全テーブル)',
    '注文確定直後の注文情報取得',
    '在庫数の確認(正確な値が必要)',
  ],
  // Replica(読み取り)
  replica: [
    '商品一覧/詳細の取得',
    '注文履歴の参照',
    '検索クエリ',
    'ランキング集計',
  ],
};

// Phase 2: 重いクエリの分離
const queryOptimization = {
  // ランキング集計 → Materialized View + 定期更新
  ranking: {
    strategy: 'Materialized View',
    refreshInterval: '10分ごと',
    query: 'REFRESH MATERIALIZED VIEW CONCURRENTLY product_rankings',
  },

  // 注文履歴検索 → 適切なインデックス
  orderHistory: {
    index: 'CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC)',
  },
};

// Phase 3: 将来的なシャーディング計画(必要時のみ)
const shardingPlan = {
  target: 'orders, order_items',
  shardKey: 'user_id',
  strategy: 'hash-based',
  timing: '注文数が1000万件を超えた時点で検討',
};

Mission 4: 非同期処理の導入(15分)

注文処理フローを同期/非同期に分離してください。

現在の注文処理フロー(全て同期)

  1. 注文データ作成(DB)
  2. 決済処理(外部API)
  3. 在庫引き当て(DB)
  4. 確認メール送信(外部API)
  5. レシートPDF生成(CPU負荷)
  6. 在庫アラート確認(条件付き外部API)
  7. ポイント付与(DB)
  8. 分析イベント送信(外部API)
解答例
// 同期(ユーザーが待つ必要がある処理)
async function placeOrderSync(data: OrderData): Promise<OrderResult> {
  // 1. 注文データ作成
  const order = await orderRepo.create(data);

  // 2. 決済処理(失敗したら注文キャンセル)
  try {
    await paymentService.charge(order.totalAmount, data.paymentMethod);
  } catch (error) {
    await orderRepo.cancel(order.id);
    throw new PaymentFailedError(error.message);
  }

  // 3. 在庫引き当て(失敗したら返金 + キャンセル)
  try {
    await inventoryService.reserve(order.items);
  } catch (error) {
    await paymentService.refund(order.id);
    await orderRepo.cancel(order.id);
    throw new OutOfStockError(error.message);
  }

  return { orderId: order.id, status: 'confirmed' };
  // ここまで約700ms
}

// 非同期(バックグラウンドで処理)
async function publishOrderEvents(orderId: string): Promise<void> {
  await messageQueue.publish('order.confirmed', { orderId });
}

// ワーカー側の処理
class OrderConfirmedWorker {
  async handle(event: OrderConfirmedEvent): Promise<void> {
    const order = await orderRepo.findById(event.orderId);

    // 4. 確認メール送信
    await emailService.sendConfirmation(order);

    // 5. レシートPDF生成
    await receiptService.generate(order);

    // 6. 在庫アラート確認
    for (const item of order.items) {
      const stock = await inventoryService.getStock(item.productId);
      if (stock < 10) {
        await alertService.lowStock(item.productId, stock);
      }
    }

    // 7. ポイント付与
    await pointService.award(order.userId, order.totalAmount);

    // 8. 分析イベント
    await analyticsService.trackPurchase(order);
  }
}

効果: レスポンスタイム 2000ms以上 → 約700ms(65%改善)


Mission 5: オートスケーリング設計(15分)

SpeedShopのオートスケーリング設定を設計してください。

要件

  • 通常時: 月間100万PV(約4 RPS平均)
  • ピーク時: セール時に通常の20倍(約80 RPS平均、バースト500 RPS)
  • コスト最適化: 閑散期にスケールダウン
解答例
const autoScalingConfig = {
  appServer: {
    minInstances: 2,      // 最小2台(冗長性確保)
    maxInstances: 20,     // 最大20台
    desiredInstances: 3,  // 通常時3台

    scaleOutPolicy: {
      metric: 'CPUUtilization',
      threshold: 60,      // CPU 60%超えで追加
      cooldown: 180,      // 3分のクールダウン
      step: 2,            // 2台ずつ追加
    },
    scaleInPolicy: {
      metric: 'CPUUtilization',
      threshold: 25,      // CPU 25%以下で削減
      cooldown: 600,      // 10分のクールダウン(慎重に)
      step: 1,            // 1台ずつ削減
    },

    // セール時の事前スケーリング
    scheduledScaling: [
      {
        name: 'sale-preparation',
        schedule: 'cron(55 23 * * *)' , // セール前日23:55
        desiredCapacity: 15,
      },
      {
        name: 'post-sale-normalize',
        schedule: 'cron(0 6 * * *)',    // 翌朝6:00
        desiredCapacity: 3,
      },
    ],
  },

  workerServer: {
    minInstances: 1,
    maxInstances: 10,
    // キューの深さに基づくスケーリング
    scaleOutPolicy: {
      metric: 'QueueDepth',
      threshold: 1000,    // キューに1000メッセージ以上
      step: 2,
    },
  },
};

Mission 6: 総合アーキテクチャ図(15分)

10倍トラフィックに耐えるSpeedShopの最終アーキテクチャをテキストで描いてください。

解答例
=== SpeedShop スケーラブルアーキテクチャ ===

[CloudFront CDN]
  │ 静的アセット配信、APIレスポンスキャッシュ

[Application Load Balancer]
  │ ラウンドロビン + ヘルスチェック

[App Server × 2-20台] (Auto Scaling Group)
  │ ステートレス設計
  │ JWT認証 or Redis Session
  ├──→ [Redis Cluster]
  │      セッション、キャッシュ、ジョブキュー

  ├──→ [Message Queue (SQS)]
  │      │ 注文イベント、メール送信、分析
  │      ▼
  │    [Worker × 1-10台] (Auto Scaling Group)
  │      メール、PDF生成、ポイント付与

  └──→ [Database]
         ├─ Primary (Write + Critical Read)
         ├─ Replica 1 (Read)
         └─ Replica 2 (Read)

[S3]
  ユーザーアップロード、レシートPDF

[CloudWatch]
  メトリクス監視、アラート、Auto Scalingトリガー

キャパシティ:
  通常時: App 3台 + Worker 1台 = 約200 RPS
  ピーク時: App 15台 + Worker 5台 = 約3000 RPS
  最大: App 20台 + Worker 10台 = 約5000 RPS

達成度チェック

ミッションテーマ完了
Mission 1ボトルネック分析[ ]
Mission 2ステートレス化[ ]
Mission 3DBスケーリング[ ]
Mission 4非同期処理導入[ ]
Mission 5オートスケーリング[ ]
Mission 6総合アーキテクチャ[ ]

チェックリスト

  • 現状のボトルネックを体系的に分析できる
  • ステートレス化の具体的な手法を実装できる
  • Read Replicaを含むDB構成を設計できる
  • 同期/非同期の処理分離を判断・設計できる
  • オートスケーリングポリシーを設計できる

次のステップへ

お疲れさまでした。スケーラブルなアーキテクチャ設計の実践力が身についたはずです。

次のセクションでは、Step 4の理解度チェックです。


推定所要時間: 90分