T-CREATOR

Claude Code SRE 実務:レート制限・キューイング・指数バックオフの実装指針

Claude Code SRE 実務:レート制限・キューイング・指数バックオフの実装指針

Claude Code API を活用したアプリケーション開発において、安定した運用を実現するためには、適切なエラーハンドリングと負荷制御が不可欠です。本記事では、SRE(Site Reliability Engineering)の観点から、レート制限・キューイング・指数バックオフという 3 つの重要な技術について、実装方法とベストプラクティスを解説します。

これらの技術を適切に組み合わせることで、API の利用制限を守りながら、高い信頼性を持つシステムを構築できます。実際のコード例を交えながら、段階的に理解を深めていきましょう。

背景

Claude Code API の特性と制約

Claude Code API は、強力な AI 機能を提供する一方で、システムの安定性を保つために利用制限が設けられています。これらの制限は、すべてのユーザーに公平なサービスを提供し、サーバーの過負荷を防ぐために必要です。

API 利用時には、以下のような制限が存在します。

#制限の種類説明典型的な制限値
1RPM(Requests Per Minute)1 分あたりのリクエスト数50-100 件/分
2TPM(Tokens Per Minute)1 分あたりのトークン数40,000-100,000 トークン/分
3同時接続数同時に処理できるリクエスト数5-10 件
4日次制限1 日あたりの総リクエスト数プランによって変動

SRE の基本概念

SRE は、システムの信頼性を高めるためのエンジニアリング手法です。外部 API を利用する際には、以下の原則が重要になります。

まず、可用性の確保です。システムが常に利用可能な状態を保つために、エラーに対する適切な対処が必要ですね。

次に、グレースフルデグラデーションという概念があります。システムに問題が発生した場合でも、完全に停止するのではなく、機能を制限しながら動作を継続させる設計思想です。

そして、バックプレッシャーの管理も欠かせません。システムに過度な負荷がかかることを防ぐため、適切な流量制御を行います。

以下の図は、Claude Code API を利用する際の基本的なリクエストフローを示しています。

mermaidflowchart LR
    app["アプリケーション"] -->|APIリクエスト| limiter["レート制限層"]
    limiter -->|制限内| queue["キュー"]
    limiter -->|制限超過| wait["待機"]
    queue -->|順次処理| api["Claude Code API"]
    api -->|成功| response["レスポンス"]
    api -->|エラー| retry["リトライ判定"]
    retry -->|指数バックオフ| queue
    response --> app

この図から理解できる要点は以下の通りです。

  • リクエストは必ずレート制限層を通過してから処理される
  • 制限を超えたリクエストは待機状態となり、システムを保護する
  • エラー発生時はリトライ判定を経て、適切なタイミングで再試行される

API 利用における実務的な課題

実際の開発現場では、単純に API を呼び出すだけでは不十分です。大規模なデータ処理や複数ユーザーからの同時アクセスに対応するために、より高度な制御機構が求められます。

特に、バッチ処理や並行処理を行う場合、意図せず制限値を超えてしまうリスクが高まります。また、一時的なネットワークエラーやサーバー側の問題に対しても、適切に対処する必要があるでしょう。

課題

API レート制限超過の問題

Claude Code API を利用する際、最も頻繁に遭遇する問題がレート制限超過です。制限を超えると、以下のような HTTP ステータスコードが返却されます。

エラーコード: HTTP 429 Too Many Requests

エラーメッセージ:

text{
  "error": {
    "type": "rate_limit_error",
    "message": "Rate limit exceeded. Please retry after 60 seconds."
  }
}

発生条件:

  • 1 分間に許可された回数以上の API リクエストを送信した場合
  • トークン数の上限を超えるリクエストを連続して送信した場合
  • 同時接続数の制限を超えた場合

このエラーが発生すると、アプリケーションの処理が停止し、ユーザー体験が著しく低下します。特に、複数のリクエストを並行処理している場合、一つのエラーが連鎖的に他のリクエストにも影響を与えることがあるでしょう。

リトライストームのリスク

より深刻な問題として、リトライストームがあります。これは、エラーが発生した際に、すべてのクライアントが同時にリトライを行うことで、さらに負荷が増大する現象です。

以下の図は、リトライストームが発生するメカニズムを示しています。

mermaidflowchart TB
    start["複数クライアント<br/>同時リクエスト"] --> error["レート制限エラー<br/>429発生"]
    error --> immediate["全クライアントが<br/>即座にリトライ"]
    immediate --> overload["さらに負荷増大"]
    overload --> cascade["カスケード障害"]
    cascade --> system_down["システム全体の<br/>パフォーマンス低下"]

    style error fill:#ff6b6b
    style cascade fill:#ff6b6b
    style system_down fill:#c92a2a

図で理解できる要点:

  • 同時に発生したエラーが、同時リトライを引き起こす
  • リトライによってさらに負荷が増大し、悪循環に陥る
  • 最終的にシステム全体のパフォーマンスが低下する

データ損失とユーザー体験の悪化

適切なエラーハンドリングが実装されていない場合、以下のような問題が発生します。

まず、処理中のデータが失われる可能性があります。エラー発生時にリクエストが単純に破棄されると、ユーザーが入力したデータや処理結果が消失してしまいます。

次に、ユーザーへのフィードバックが不足することも問題です。エラーが発生しても適切なメッセージが表示されない場合、ユーザーは何が起きているのか理解できません。

さらに、予測不可能な動作により、システムへの信頼が失われます。時には成功し、時には失敗するという不安定な挙動は、ユーザーの満足度を大きく低下させるでしょう。

スケーラビリティの制約

システムの規模が拡大するにつれて、以下のような課題も顕在化します。

#課題影響深刻度
1並行処理の増加レート制限に達しやすくなる★★★
2複数環境の管理開発・ステージング・本番で制限を共有★★☆
3ピークタイムの対応特定時間帯に負荷が集中★★★
4マイクロサービス間の調整複数サービスで制限を分け合う必要★★☆

これらの課題に対処するためには、単なるエラーハンドリングだけでなく、システム全体のアーキテクチャレベルでの対策が必要です。

解決策

レート制限の実装アプローチ

レート制限は、API へのリクエスト数を制御し、制限値を超えないようにする仕組みです。実装には主に以下の 2 つのアルゴリズムが使用されます。

トークンバケットアルゴリズムは、一定の速度でトークンが補充されるバケツを想定し、リクエストごとにトークンを消費する方式です。トークンがある場合のみリクエストを許可するため、瞬間的なバーストトラフィックにも対応できます。

スライディングウィンドウアルゴリズムは、一定時間内のリクエスト数をカウントし、制限値を超えないように制御します。時間窓が移動するため、より正確なレート制限が可能ですね。

キューイングシステムの設計

キューイングは、リクエストを一時的に保管し、順次処理することで、システム全体の負荷を平準化します。

キューの種類には、以下のようなものがあります。

まず、FIFO(First In First Out)キューです。先に到着したリクエストから順に処理する、最もシンプルな方式となります。

次に、優先度付きキューがあります。リクエストに優先度を設定し、重要度の高いものから処理します。

そして、遅延キューも有効です。指定した時間が経過するまで処理を遅延させることで、バックオフを実現します。

指数バックオフの戦略

指数バックオフは、リトライ間隔を徐々に延ばしていく手法です。基本的な計算式は以下の通りです。

text待機時間 = 基本待機時間 × (2 ^ リトライ回数) + ランダムジッター

この方式により、リトライストームを防ぎ、システムに回復の時間を与えることができます。

**ジッター(jitter)**を加えることも重要です。複数のクライアントが完全に同じタイミングでリトライすることを防ぐため、待機時間にランダムな揺らぎを加えます。

以下の図は、3 つの技術を組み合わせた総合的なアーキテクチャを示しています。

mermaidflowchart TB
    client["クライアント"] --> rate["レート制限<br/>トークンバケット"]
    rate -->|許可| queue["キューイング<br/>FIFO/優先度付き"]
    rate -->|拒否| wait1["一時待機"]
    wait1 --> rate

    queue --> process["API呼び出し処理"]
    process --> check["結果判定"]

    check -->|成功| success["成功レスポンス"]
    check -->|429エラー| backoff["指数バックオフ<br/>計算"]
    check -->|その他エラー| error_handle["エラーハンドリング"]

    backoff --> delay["遅延キュー<br/>待機時間適用"]
    delay --> queue

    error_handle --> retry_check{"リトライ<br/>可能?"}
    retry_check -->|Yes| backoff
    retry_check -->|No| failure["失敗レスポンス"]

    success --> client
    failure --> client

    style rate fill:#4dabf7
    style queue fill:#51cf66
    style backoff fill:#ff8787

図で理解できる要点:

  • レート制限で事前にリクエスト数を制御し、API への負荷を抑制
  • キューイングで処理順序を管理し、並行度を適切に保つ
  • エラー発生時は指数バックオフで段階的に待機時間を延長
  • 3 つの層が連携して、堅牢なシステムを構成

実装における設計原則

効果的な実装を行うためには、以下の原則を守ることが重要です。

グレースフルデグラデーションを実装します。一部の機能が失敗しても、システム全体は動作を継続できるように設計しましょう。

可観測性の確保も欠かせません。ログやメトリクスを適切に記録し、システムの状態を常に監視できるようにします。

設定の外部化により、環境に応じて制限値やリトライ設定を柔軟に変更できるようにすることが大切です。

テスタビリティも考慮します。レート制限やリトライのロジックを独立したモジュールとして実装し、単体テストが容易に行えるようにしましょう。

具体例

レート制限の実装

まず、トークンバケットアルゴリズムを使ったレート制限の実装から見ていきます。

型定義とインターフェース

TypeScript で型安全な実装を行うため、まず必要な型を定義します。

typescript// レート制限の設定を定義する型
interface RateLimiterConfig {
  maxRequests: number; // 最大リクエスト数
  windowMs: number; // 時間窓(ミリ秒)
  refillRate: number; // トークン補充レート(リクエスト/秒)
}

// レート制限の状態を管理する型
interface RateLimiterState {
  tokens: number; // 現在利用可能なトークン数
  lastRefillTime: number; // 最後にトークンを補充した時刻
}

この型定義により、設定と状態を明確に分離し、型安全性を確保します。

トークンバケットの実装

次に、トークンバケットアルゴリズムの本体を実装します。

typescriptclass TokenBucketRateLimiter {
  private config: RateLimiterConfig;
  private state: RateLimiterState;

  constructor(config: RateLimiterConfig) {
    this.config = config;
    // 初期状態では最大トークン数を設定
    this.state = {
      tokens: config.maxRequests,
      lastRefillTime: Date.now(),
    };
  }

  // トークンを補充するメソッド
  private refillTokens(): void {
    const now = Date.now();
    const timePassed =
      (now - this.state.lastRefillTime) / 1000; // 秒に変換
    const tokensToAdd = timePassed * this.config.refillRate;

    // 最大値を超えないようにトークンを補充
    this.state.tokens = Math.min(
      this.config.maxRequests,
      this.state.tokens + tokensToAdd
    );
    this.state.lastRefillTime = now;
  }
}

このクラスは、時間経過に応じてトークンを補充し、常に最新の状態を維持します。

リクエスト許可判定の実装

トークンが利用可能かどうかを判定し、リクエストの可否を決定します。

typescriptclass TokenBucketRateLimiter {
  // ... 前のコードに続く

  // リクエストが許可されるかチェックするメソッド
  async tryAcquire(): Promise<boolean> {
    this.refillTokens();

    if (this.state.tokens >= 1) {
      // トークンを1つ消費
      this.state.tokens -= 1;
      return true;
    }

    // トークンが不足している場合は拒否
    return false;
  }

  // 次にトークンが利用可能になるまでの時間を計算
  getWaitTime(): number {
    this.refillTokens();

    if (this.state.tokens >= 1) {
      return 0; // すぐに利用可能
    }

    // 次のトークンが補充されるまでの時間(ミリ秒)
    const tokensNeeded = 1 - this.state.tokens;
    return (tokensNeeded / this.config.refillRate) * 1000;
  }
}

tryAcquireメソッドは非同期処理として実装し、将来的な拡張性を確保しています。

キューイングシステムの実装

次に、リクエストをキューに入れて順次処理するシステムを構築します。

キューアイテムの型定義

キューに格納するアイテムの構造を定義します。

typescript// キューアイテムの型定義
interface QueueItem<T> {
  id: string; // 一意のID
  task: () => Promise<T>; // 実行するタスク
  priority: number; // 優先度(数値が大きいほど高優先度)
  retryCount: number; // リトライ回数
  addedAt: number; // キューに追加された時刻
  resolve: (value: T) => void; // Promise解決用
  reject: (error: Error) => void; // Promise拒否用
}

// キュー設定の型
interface QueueConfig {
  concurrency: number; // 同時実行数
  maxSize: number; // キューの最大サイズ
  timeout: number; // タスクのタイムアウト(ミリ秒)
}

各アイテムに Promise の制御関数を含めることで、非同期処理を適切に管理できます。

優先度付きキューの実装

優先度に基づいてタスクを処理するキューを実装します。

typescriptclass PriorityQueue<T> {
  private queue: QueueItem<T>[] = [];
  private config: QueueConfig;
  private running: number = 0;

  constructor(config: QueueConfig) {
    this.config = config;
  }

  // キューにタスクを追加
  async add(
    task: () => Promise<T>,
    priority: number = 0
  ): Promise<T> {
    // キューが満杯の場合はエラー
    if (this.queue.length >= this.config.maxSize) {
      throw new Error('Queue is full');
    }

    return new Promise<T>((resolve, reject) => {
      const item: QueueItem<T> = {
        id: `task-${Date.now()}-${Math.random()}`,
        task,
        priority,
        retryCount: 0,
        addedAt: Date.now(),
        resolve,
        reject,
      };

      // 優先度順に挿入
      this.insertByPriority(item);
      this.processQueue();
    });
  }
}

addメソッドは、新しいタスクを Promise でラップし、完了時に適切に解決されるようにします。

キュー処理ロジックの実装

キューからタスクを取り出して実行する処理を実装します。

typescriptclass PriorityQueue<T> {
  // ... 前のコードに続く

  private insertByPriority(item: QueueItem<T>): void {
    // 優先度が高い順に並べる(降順)
    const index = this.queue.findIndex(
      (existing) => existing.priority < item.priority
    );

    if (index === -1) {
      this.queue.push(item);
    } else {
      this.queue.splice(index, 0, item);
    }
  }

  private async processQueue(): Promise<void> {
    // 同時実行数の制限チェック
    if (this.running >= this.config.concurrency) {
      return;
    }

    // キューが空の場合は終了
    const item = this.queue.shift();
    if (!item) {
      return;
    }

    this.running++;

    try {
      // タイムアウト付きでタスクを実行
      const result = await this.executeWithTimeout(item);
      item.resolve(result);
    } catch (error) {
      item.reject(error as Error);
    } finally {
      this.running--;
      // 次のタスクを処理
      this.processQueue();
    }
  }
}

このロジックにより、同時実行数を制御しながら、順次タスクが処理されていきます。

タイムアウト処理の実装

タスクの実行時間が長すぎる場合に、適切にタイムアウトする仕組みを追加します。

typescriptclass PriorityQueue<T> {
  // ... 前のコードに続く

  private async executeWithTimeout(
    item: QueueItem<T>
  ): Promise<T> {
    return Promise.race([
      item.task(),
      new Promise<T>((_, reject) => {
        setTimeout(() => {
          reject(
            new Error(
              `Task ${item.id} timed out after ${this.config.timeout}ms`
            )
          );
        }, this.config.timeout);
      }),
    ]);
  }

  // キューの状態を取得
  getStats() {
    return {
      queueSize: this.queue.length,
      running: this.running,
      capacity: this.config.maxSize,
      utilization: this.running / this.config.concurrency,
    };
  }
}

Promise.raceを使用することで、タスクの完了またはタイムアウトのいずれか早い方を処理できます。

指数バックオフの実装

エラー発生時に適切な間隔でリトライを行う指数バックオフを実装します。

バックオフ設定の型定義

リトライに関する設定を定義します。

typescript// 指数バックオフの設定
interface ExponentialBackoffConfig {
  initialDelayMs: number; // 初期待機時間(ミリ秒)
  maxDelayMs: number; // 最大待機時間(ミリ秒)
  multiplier: number; // 乗数(通常2.0)
  maxRetries: number; // 最大リトライ回数
  jitterFactor: number; // ジッター係数(0-1)
}

// リトライ結果の型
interface RetryResult<T> {
  success: boolean;
  data?: T;
  error?: Error;
  attempts: number;
  totalWaitTime: number;
}

ジッター係数により、待機時間にランダムな揺らぎを加えることができます。

バックオフ計算の実装

待機時間を計算するロジックを実装します。

typescriptclass ExponentialBackoff {
  private config: ExponentialBackoffConfig;

  constructor(config: ExponentialBackoffConfig) {
    this.config = config;
  }

  // 待機時間を計算(ジッター付き)
  calculateDelay(attemptNumber: number): number {
    // 基本の指数バックオフ計算
    const exponentialDelay = Math.min(
      this.config.initialDelayMs *
        Math.pow(this.config.multiplier, attemptNumber),
      this.config.maxDelayMs
    );

    // ジッターを追加(-jitterFactor ~ +jitterFactor の範囲)
    const jitter =
      exponentialDelay *
      this.config.jitterFactor *
      (Math.random() * 2 - 1);

    const finalDelay = Math.max(
      0,
      exponentialDelay + jitter
    );

    return Math.floor(finalDelay);
  }

  // 指定時間待機する
  private async sleep(ms: number): Promise<void> {
    return new Promise((resolve) =>
      setTimeout(resolve, ms)
    );
  }
}

ジッターにより、複数のクライアントが同時にリトライすることを防げます。

リトライ実行ロジックの実装

実際にリトライを実行する処理を実装します。

typescriptclass ExponentialBackoff {
  // ... 前のコードに続く

  // リトライ付きでタスクを実行
  async executeWithRetry<T>(
    task: () => Promise<T>,
    shouldRetry: (error: Error) => boolean = () => true
  ): Promise<RetryResult<T>> {
    let lastError: Error | undefined;
    let totalWaitTime = 0;

    for (
      let attempt = 0;
      attempt <= this.config.maxRetries;
      attempt++
    ) {
      try {
        // タスクを実行
        const data = await task();

        return {
          success: true,
          data,
          attempts: attempt + 1,
          totalWaitTime,
        };
      } catch (error) {
        lastError = error as Error;

        // 最後の試行の場合はリトライしない
        if (attempt === this.config.maxRetries) {
          break;
        }

        // リトライすべきエラーかチェック
        if (!shouldRetry(lastError)) {
          break;
        }

        // 待機時間を計算して待つ
        const delay = this.calculateDelay(attempt);
        totalWaitTime += delay;

        console.log(
          `Retry attempt ${attempt + 1}/${
            this.config.maxRetries
          } ` + `after ${delay}ms delay`
        );

        await this.sleep(delay);
      }
    }

    // すべてのリトライが失敗
    return {
      success: false,
      error: lastError,
      attempts: this.config.maxRetries + 1,
      totalWaitTime,
    };
  }
}

shouldRetryコールバックにより、リトライすべきエラーを柔軟に制御できます。

Claude Code API との統合実装

これまでに実装した 3 つのコンポーネントを統合し、実際の Claude Code API クライアントを構築します。

統合クライアントの型定義

API クライアントに必要な型を定義します。

typescript// Claude Code APIリクエストの設定
interface ClaudeApiRequest {
  model: string;
  messages: Array<{
    role: 'user' | 'assistant';
    content: string;
  }>;
  max_tokens: number;
  temperature?: number;
}

// 統合クライアントの設定
interface ResilientClientConfig {
  apiKey: string;
  rateLimiter: RateLimiterConfig;
  queue: QueueConfig;
  backoff: ExponentialBackoffConfig;
}

これらの型により、API リクエストと各コンポーネントの設定を統合的に管理します。

統合クライアントのクラス実装

3 つのコンポーネントを組み合わせたクライアントを実装します。

typescriptclass ResilientClaudeClient {
  private rateLimiter: TokenBucketRateLimiter;
  private queue: PriorityQueue<any>;
  private backoff: ExponentialBackoff;
  private apiKey: string;

  constructor(config: ResilientClientConfig) {
    this.apiKey = config.apiKey;
    this.rateLimiter = new TokenBucketRateLimiter(
      config.rateLimiter
    );
    this.queue = new PriorityQueue(config.queue);
    this.backoff = new ExponentialBackoff(config.backoff);
  }

  // メインのAPI呼び出しメソッド
  async sendMessage(
    request: ClaudeApiRequest,
    priority: number = 0
  ): Promise<any> {
    // キューにタスクを追加
    return this.queue.add(
      () => this.executeRequest(request),
      priority
    );
  }
}

このクラスが、すべてのコンポーネントを統合する中心的な役割を果たします。

レート制限を考慮したリクエスト実行

レート制限をチェックしながら API リクエストを実行します。

typescriptclass ResilientClaudeClient {
  // ... 前のコードに続く

  private async executeRequest(
    request: ClaudeApiRequest
  ): Promise<any> {
    // レート制限をチェック
    const canProceed = await this.rateLimiter.tryAcquire();

    if (!canProceed) {
      // トークンが不足している場合は待機
      const waitTime = this.rateLimiter.getWaitTime();
      console.log(
        `Rate limit reached. Waiting ${waitTime}ms`
      );
      await new Promise((resolve) =>
        setTimeout(resolve, waitTime)
      );

      // 再度試行
      return this.executeRequest(request);
    }

    // リトライ付きでAPI呼び出しを実行
    const result = await this.backoff.executeWithRetry(
      () => this.callClaudeApi(request),
      (error) => this.shouldRetryError(error)
    );

    if (!result.success) {
      throw result.error;
    }

    return result.data;
  }
}

レート制限に達した場合は、適切な時間待機してから再試行します。

実際の API 呼び出し処理

HTTP リクエストを使用して、実際に Claude Code API を呼び出します。

typescriptclass ResilientClaudeClient {
  // ... 前のコードに続く

  private async callClaudeApi(
    request: ClaudeApiRequest
  ): Promise<any> {
    const response = await fetch(
      'https://api.anthropic.com/v1/messages',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': this.apiKey,
          'anthropic-version': '2023-06-01',
        },
        body: JSON.stringify(request),
      }
    );

    // ステータスコードをチェック
    if (!response.ok) {
      const errorData = await response
        .json()
        .catch(() => ({}));
      throw new Error(
        `API Error ${response.status}: ${JSON.stringify(
          errorData
        )}`
      );
    }

    return response.json();
  }
}

エラー発生時には、詳細な情報を含む例外を投げて、適切な処理を可能にします。

エラー判定とリトライロジック

どのエラーでリトライすべきかを判定するロジックを実装します。

typescriptclass ResilientClaudeClient {
  // ... 前のコードに続く

  private shouldRetryError(error: Error): boolean {
    const errorMessage = error.message;

    // HTTP 429(レート制限)は常にリトライ
    if (errorMessage.includes('429')) {
      return true;
    }

    // HTTP 5xx(サーバーエラー)もリトライ
    if (/5\d{2}/.test(errorMessage)) {
      return true;
    }

    // ネットワークエラーもリトライ
    if (
      errorMessage.includes('ECONNRESET') ||
      errorMessage.includes('ETIMEDOUT') ||
      errorMessage.includes('ENOTFOUND')
    ) {
      return true;
    }

    // その他のエラー(4xx等)はリトライしない
    return false;
  }

  // 統計情報を取得
  getStats() {
    return {
      queue: this.queue.getStats(),
      timestamp: new Date().toISOString(),
    };
  }
}

一時的なエラーはリトライし、永続的なエラー(認証エラーなど)はリトライしないように区別します。

実装例の使用方法

最後に、実装したクライアントの使用例を示します。

クライアントの初期化

適切な設定値でクライアントを初期化します。

typescript// クライアントを設定して初期化
const client = new ResilientClaudeClient({
  apiKey: process.env.ANTHROPIC_API_KEY!,
  rateLimiter: {
    maxRequests: 50, // 1分あたり50リクエスト
    windowMs: 60000, // 60秒
    refillRate: 50 / 60, // 1秒あたり約0.83リクエスト
  },
  queue: {
    concurrency: 5, // 同時5リクエストまで
    maxSize: 100, // キューの最大サイズ
    timeout: 30000, // 30秒でタイムアウト
  },
  backoff: {
    initialDelayMs: 1000, // 初回は1秒待機
    maxDelayMs: 60000, // 最大60秒
    multiplier: 2.0, // 2倍ずつ増加
    maxRetries: 5, // 最大5回リトライ
    jitterFactor: 0.1, // ±10%のジッター
  },
});

環境変数から API キーを読み込み、各コンポーネントに適切な制限値を設定します。

基本的な使用例

シンプルなメッセージ送信の例です。

typescript// 基本的なメッセージ送信
async function sendSimpleMessage() {
  try {
    const response = await client.sendMessage({
      model: 'claude-3-5-sonnet-20241022',
      messages: [
        {
          role: 'user',
          content:
            'TypeScriptの型安全性について説明してください',
        },
      ],
      max_tokens: 1024,
    });

    console.log('Response:', response.content[0].text);
  } catch (error) {
    console.error('Error:', error);
  }
}

エラーハンドリングも含めた、実用的なコード例となっています。

複数リクエストの並行処理

複数のリクエストを効率的に処理する例です。

typescript// 複数のリクエストを並行処理
async function processBatchRequests() {
  const questions = [
    'JavaScriptのクロージャとは?',
    'Promiseの使い方を教えて',
    'async/awaitのベストプラクティスは?',
  ];

  // すべてのリクエストを並行して送信
  const promises = questions.map((question, index) =>
    client.sendMessage(
      {
        model: 'claude-3-5-sonnet-20241022',
        messages: [{ role: 'user', content: question }],
        max_tokens: 512,
      },
      index // 優先度として使用
    )
  );

  try {
    const results = await Promise.all(promises);
    results.forEach((result, index) => {
      console.log(`Q${index + 1}:`, questions[index]);
      console.log(`A${index + 1}:`, result.content[0].text);
      console.log('---');
    });
  } catch (error) {
    console.error('Batch processing error:', error);
  }
}

Promise.allを使用することで、すべてのリクエストが完了するまで待機できます。

モニタリングとログ出力

システムの状態を監視する例です。

typescript// 定期的に統計情報を出力
setInterval(() => {
  const stats = client.getStats();
  console.log('System Status:', {
    queueSize: stats.queue.queueSize,
    activeRequests: stats.queue.running,
    utilization: `${(stats.queue.utilization * 100).toFixed(
      1
    )}%`,
    timestamp: stats.timestamp,
  });
}, 5000); // 5秒ごと

// メイン処理を実行
async function main() {
  await sendSimpleMessage();
  await processBatchRequests();
}

main().catch(console.error);

定期的なモニタリングにより、システムの健全性を常に把握できます。

以下の図は、実装全体のフローを示しています。

mermaidsequenceDiagram
    participant App as アプリケーション
    participant Client as ResilientClient
    participant Rate as RateLimiter
    participant Queue as Queue
    participant Backoff as Backoff
    participant API as Claude API

    App->>Client: sendMessage()
    Client->>Queue: add task
    Queue->>Rate: tryAcquire()

    alt トークン利用可能
        Rate-->>Queue: true
        Queue->>Backoff: executeWithRetry()
        Backoff->>API: HTTP Request

        alt 成功
            API-->>Backoff: Response
            Backoff-->>Queue: Success
            Queue-->>Client: Result
            Client-->>App: Response
        else 429エラー
            API-->>Backoff: 429 Error
            Backoff->>Backoff: 待機時間計算
            Note over Backoff: 指数バックオフ<br/>+ ジッター
            Backoff->>API: リトライ
        end

    else トークン不足
        Rate-->>Queue: false
        Note over Queue: 待機後<br/>再試行
        Queue->>Rate: tryAcquire()
    end

図で理解できる要点:

  • リクエストは複数の層を経由して処理される
  • 各層で適切なチェックと制御が行われる
  • エラー時は自動的にリトライが実行される
  • すべての処理が非同期で効率的に動作する

まとめ

Claude Code API を使用した堅牢なシステムを構築するためには、レート制限・キューイング・指数バックオフの 3 つの技術を適切に組み合わせることが重要です。

レート制限により、API の利用制限を守りながら、システム全体の負荷を適切に制御できます。トークンバケットアルゴリズムを使用することで、瞬間的なバーストトラフィックにも柔軟に対応できるでしょう。

キューイングシステムは、リクエストの順序を管理し、同時実行数を制御することで、システムのスケーラビリティを向上させます。優先度付きキューを実装することで、重要なリクエストを優先的に処理できますね。

指数バックオフは、エラー発生時のリトライストームを防ぎ、システムに回復の時間を与えます。ジッターを加えることで、複数クライアント間の衝突も回避できます。

これらの技術を実装する際には、以下のポイントに注意してください。

まず、型安全性を確保することです。TypeScript の型システムを活用し、バグを早期に発見できるようにしましょう。

次に、テスタビリティを考慮します。各コンポーネントを独立したクラスとして実装し、単体テストが容易に行えるようにすることが大切です。

さらに、可観測性を組み込むことも重要です。ログやメトリクスを適切に記録し、問題発生時に迅速に原因を特定できるようにします。

そして、設定の柔軟性を保つことです。環境や要件に応じて、制限値やリトライ設定を簡単に変更できるようにしましょう。

本記事で紹介した実装パターンは、Claude Code API だけでなく、他の外部 API を利用する際にも応用できます。SRE の原則に基づいた設計により、信頼性の高いシステムを構築していきましょう。

実際の運用では、監視とチューニングを継続的に行い、システムの特性に合わせて最適な設定値を見つけていくことが成功の鍵となります。

関連リンク