Eleven v4を発表これまでで最も感情豊かなモデル、Eleven v4をご紹介します。 10月12日まで、Creator+には3倍のクレジットが含まれます

コンテンツへ移動

音声AIのレート制限:同時実行数、キュー、429エラーについて

公開日
最終更新日

聴くこの記事を聴く

多くのチームは、音声AIのレート制限も他のAPIと同じように扱います。1分あたりのリクエスト数を制限し、サーバーに拒否されたら再試行して終わりです。しかしElevenLabsのワークロードでは、最初のトラフィック急増でこの考え方は通用しなくなります。実際に達する上限はリクエスト数ではなく、同時実行数だからです。

このガイドでは、なぜ同時実行数が真の制約となるのかを説明し、その範囲内に収めるためのクライアント側パターンを紹介します。上限付き同時実行プール、適切な429処理、マルチテナントの公平性、トークンバケットとリーキーバケットまで、実装可能な実践的システムを取り上げます。各パターンには、用途に合わせて調整できるTypeScriptの実装例を添えています。

音声エージェントを構築したり、ナレーションパイプラインなどの本番システムをモデル上で運用し、スケールさせたいなら、このガイドが役立ちます。

概要

  • 音声AIのレート制限は、1分あたりのリクエスト数の計測ではなく、同時実行数の制御です。
  • レート制限の上限に達しても、トラフィックがすぐ拒否されるわけではありません。代わりにリクエストは優先度キューに入り、約50msの遅延が加わります。
  • キューイング後も容量を超えている場合は、HTTP 429エラーになります。
  • WebSocketでは、アクティブな生成だけが上限にカウントされるため、実効容量が大幅に増えます。
  • マルチテナントシステムには、テナントごとのバケット、重み付き公平キューイング、予約ヘッドルーム、分離のためのキー間シャーディングという、追加の公平性レイヤーが必要です。
  • current-concurrent-requestsとmaximum-concurrent-requestsという2つのレスポンスヘッダーで、AIレート制限に対する現在の状況を確認できます。

上限が1分あたりのリクエスト数ではなく同時実行数である理由

同時実行数とは、同じ瞬間に処理中のリクエスト数です。1分あたりのリクエスト数は、一定期間のスループットです。この違いを理解することは重要です。上限内に収めるために調整すべき要素が変わるからです。

ElevenLabsのモデルを使用すると、サーバーの負荷は同時ユーザー数に応じて増加します。音声生成では、生成中ずっとスロットを占有します。この時間は入力の長さ、モデル、負荷によって変わります。

1分あたりのリクエスト数の上限では、今どれだけのスロットが占有されているかは分かりません。サーバーが計測しているのは、まさにこの点だけです。

プラン・モデルファミリーごとの上限

同時実行数の予算は単一の数値ではありません。同時実行数の上限は、プランとモデルファミリーごとに異なります。たとえば、スピーチtoテキストはテキスト読み上げよりも高い上限が設定されています。文字起こしリクエストは通常より短時間で完了し、システムが一度により多く処理できるためです。

Multilingual v2
Free
2
Starter
3
Creator
5
Pro
10
Scale
15
Business
15
Enterprise
Elevated
Flash
Free
4
Starter
6
Creator
10
Pro
20
Scale
30
Business
30
Enterprise
Elevated
STT
Free
8
Starter
12
Creator
20
Pro
40
Scale
60
Business
60
Enterprise
Elevated
Realtime STT
Free
6
Starter
9
Creator
15
Pro
30
Scale
45
Business
45
Enterprise
Elevated
Queue weight
Free
3
Starter
4
Creator
5
Pro
5
Scale
5
Business
5
Enterprise
6

上限はモデルファミリーごとに設定されます。Flashをエージェントに、Multilingual v2をナレーションに使う場合、2つの独立した予算を同時に利用することになります。プランごとの最新の数値と同時実行数に関する説明は、モデルページに掲載されています。

同時実行数の上限に達するとどうなりますか?

同時実行数の上限に達しても、トラフィックがすぐ拒否されるわけではありません。システムは優先度キューによって段階的に対応し、レート制限の総容量をなお超えている場合にのみ完全な拒否へ移行します。

上限未満であれば、リクエストはすぐ実行されます。上限に達すると、後続のリクエストはプランの優先度に応じて並ぶキューに入ります。キューによる遅延は通常約50msなので、短時間の超過はほとんどユーザーに気付かれません。

キューイング後もシステムが容量超過の場合、HTTP 429を受け取ります。これは、すぐに再試行するのではなくペースを落とすべき合図です。表の優先度は、キューに入ったリクエストが他のトラフィックに対してどの順序で処理されるかを決めます。上位プランほど早くキューを抜けられます。

HTTPとWebSocket:上限へのカウント方法

選択するトランスポートは、レート制限と予算に直接影響します。同じ会話トラフィックでも、HTTPで処理するかWebSocketで処理するかによって、同時実行数の予算消費量が大きく異なることがあります。

HTTPでは、各リクエストがその全期間にわたり個別に同時実行数の上限へカウントされます。WebSocketでは、モデルが実際に音声を生成している時間だけがカウントされます。接続が開いていてもアイドル状態のWebSocketは、ほとんどカウントされません。

音声エージェントの会話には、誰も話しておらず、モデルも何も生成していない長い時間があります。HTTPでは、ターンごとにリクエストの全期間にわたってスロットを保持します。WebSocketでは、スロットを消費するのはアクティブな生成中の数ミリ秒だけなので、1つの同時実行スロットを多くの会話で時分割できます。

リアルタイムTTS WebSocketガイドでプロトコルの詳細を確認してください。インタラクティブなトラフィックでは、WebSocketをデフォルトにするのが適切です。

同時実行数約5で約100件の配信をサポートできる理由

同時実行数の計算は、再生時間を考慮するまで直感に反します。生成は再生よりはるかに速く、スロットが実際に占有されるのは音声生成中だけです。この差によって、小さな予算でも大きなオーディエンスに配信できます。

生成に数分の1秒しかかからないリクエストでも、数秒間の音声を生成します。リスナーがそれを再生している間にスロットは解放され、他のリスナーが利用できます。

目安として、同時実行数の上限が5あれば、約100件の同時音声配信をサポートできます。正確な数は、音声、話すテンポ、発話間の無音時間によって異なります。

現在の状況を示すヘッダー

上限に対する自分の位置を推測する必要はありません。すべてのレスポンスには、単なる推定ではなくヘッドルームを測定できる2つの数値が含まれています。

次の2つのヘッダーを確認してください。

  • current-concurrent-requests: 現在処理中のリクエスト数。
  • maximum-concurrent-requests: そのモデルファミリーにおける上限。

これらのヘッダーを合わせることで、現在の利用状況と利用可能な容量をリアルタイムで把握できます。AIレート制限に達する前に、推測に頼る必要はありません。

AIレート制限のクライアント側戦略

ほぼすべてのAIレート制限シナリオをカバーする、4つの基本要素があります。

  • トークンバケット: トークンがあればリクエストを進めます。容量は時間とともに補充されるため、レート制限に達せずに短時間の急増を処理できます。
  • リーキーバケット: 流入トラフィックを固定の出力レートへ平準化し、急なスパイクによって下流システムが過負荷になるのを防ぎます。
  • 上限付き同時実行プール: 同時にアクティブにできるリクエスト総数を制限するため、同時リクエスト数の上限を超えません。
  • フルジッター付き指数バックオフ: 失敗したリクエスト間の待機時間を徐々に延ばし、すべてのクライアントが同時に再試行するのを防ぎます。

以下では、同時実行数の上限に最も直接対応するものから始めて、これらを1つずつ構築する方法を示します。

以下のコードスニペットでは、1回だけ初期化した単一のクライアントを前提としています。

import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";

const elevenlabs = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY });

上限付き同時実行:上限に対応する基本要素

サーバーは同時実行数を計測するため、最も直接的なクライアント側の制御方法は、同時に処理中にできるリクエスト数を制限する上限付きワーカープールです。優先度キューとジッターの余地を残すため、上限はプランの制限より少し低く設定してください。

async function pool<T, R>(
  items: T[],
  maxInFlight: number,
  worker: (item: T) => Promise<R>,
): Promise<R[]> {
  const results: R[] = new Array(items.length);
  let next = 0;

  async function run(): Promise<void> {
    while (next < items.length) {
      const i = next++;
      results[i] = await worker(items[i]); // never more than maxInFlight of these run at once
    }
  }

  await Promise.all(
    Array.from({ length: Math.min(maxInFlight, items.length) }, run),
  );
  return results;
}

async function synthesize(text: string): Promise<Buffer> {
  const stream = await elevenlabs.textToSpeech.stream("JBFqnCBsd6RMkjVDRZzb", {
    text,
    modelId: "eleven_flash_v2_5",
    outputFormat: "mp3_44100_128",
  });
  const chunks: Buffer[] = [];
  for await (const chunk of stream) chunks.push(Buffer.from(chunk));
  return Buffer.concat(chunks);
}

// Plan Flash limit is, say, 10. Stay under it.
const texts = Array.from({ length: 50 }, (_, i) => `Sentence number ${i}.`);
const audio = await pool(texts, 8, synthesize); // never more than 8 in flight

トークンバケット:バーストを許可し、平均を制限する

トークンバケットは最大capacity個のトークンを保持し、1秒あたりrefillRate個のトークンを補充します。各リクエストはトークンを1つ消費するため、長期的なレートを抑えながら、バケット容量までの短時間のバーストを許可します。

作業キューが突然到着した瞬間を平準化するには最適な方法です。一度にすべてを実行して、同時実行数を急増させるのを防げます。

class TokenBucket {
  private tokens: number;
  private updated = performance.now();

  constructor(private capacity: number, private refillPerSec: number) {
    this.tokens = capacity;
  }

  private refill(): void {
    const now = performance.now();
    const elapsed = (now - this.updated) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillPerSec);
    this.updated = now;
  }

  tryAcquire(cost = 1): boolean {
    this.refill();
    if (this.tokens >= cost) {
      this.tokens -= cost;
      return true;
    }
    return false;
  }

  timeUntil(cost = 1): number {
    this.refill();
    return this.tokens >= cost ? 0 : ((cost - this.tokens) / this.refillPerSec) * 1000;
  }
}

リーキーバケット:一定の流出を維持する

まったくバーストを許容したくない場合もあります。リーキーバケットは、入力のバースト性にかかわらず、固定の一定レートで処理を受け入れます。下流システムが時折のスパイクよりも滑らかで予測可能な負荷を望む場合に適しています。

たとえば、他のサービスと共有している小さな同時実行数の予算に、意図的に十分な余裕を残している場合です。

class LeakyBucket {
  private next = performance.now();
  constructor(private intervalMs: number) {} // admit at most one item per intervalMs

  async acquire(): Promise<void> {
    const now = performance.now();
    const wait = Math.max(0, this.next - now);
    this.next = Math.max(now, this.next) + this.intervalMs;
    if (wait > 0) await new Promise((r) => setTimeout(r, wait));
  }
}

フルジッター付き指数バックオフ

再試行可能なステータスでリクエストが失敗した場合、すぐに再試行すると状況は悪化します。バックオフで再試行の間隔を空け、フルジッターで各遅延を区間全体にランダム化することで、多数のクライアントが足並みをそろえて再試行し、失敗を招いた同じスパイクを再現するのを防ぎます。

以下のスニペットでは、失敗したステータスとRetry-After値を保持する小さなクラス、RetryableErrorを参照しています。これは以下の「適切な429処理」セクションで定義されています。

async function withBackoff<T>(
  call: () => Promise<T>,
  opts: { maxAttempts?: number; baseMs?: number; capMs?: number } = {},
): Promise<T> {
  const { maxAttempts = 5, baseMs = 500, capMs = 20_000 } = opts;
  let attempt = 0;
  for (;;) {
    try {
      return await call();
    } catch (e) {
      if (!(e instanceof RetryableError) || ++attempt >= maxAttempts) throw e;
      // honor Retry-After if present; otherwise capped exponential growth with full jitter
      const delay =
        e.retryAfterMs ?? Math.random() * Math.min(capMs, baseMs * 2 ** attempt);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

適切な429処理:上限に達したときの対処法

429は、優先度キューを経ても容量を超えていたことを意味します。そのため、正しい対応は再試行を強めることではなく、速度を落とすことです。適切に処理するには、次の4つの戦略があります。

  • 検知
  • Retry-Afterの尊重
  • バックプレッシャーの通知
  • サーキットブレーカーによる再試行ストームの回避

それぞれを詳しく見ていきましょう。

1つ目は検知です。HTTP 429、および一時的な500、502、503、504は再試行可能として扱い、400、401、403、422は再試行不可として扱います。不正なリクエストや認可されていないリクエストを再試行しても成功せず、スロットを無駄にするだけです。

2つ目はRetry-Afterの尊重です。レスポンスにこのヘッダーがあれば、自分で遅延を計算するのではなく、正確に従ってください。サーバーはいつ容量を確保できる見込みかを通知しており、指数式よりも正確です。ヘッダーがない場合にのみ、ジッター付きバックオフへフォールバックしてください。

class RetryableError extends Error {
  constructor(public status: number, public retryAfterMs?: number) {
    super(`retryable ${status}`);
  }
}

function classify(resp: Response): void {
  if ([429, 500, 502, 503, 504].includes(resp.status)) {
    const ra = resp.headers.get("retry-after");
    throw new RetryableError(resp.status, ra ? Number(ra) * 1000 : undefined);
  }
  if (!resp.ok) throw new Error(`non-retryable ${resp.status}`);
}

3つ目はバックプレッシャーの通知です。再試行を見えない形で積み上げないでください。キュー深度や測定したヘッドルームから新しいリクエストにすぐ対応できないと分かる場合は、処理できない仕事を受け入れるのではなく、呼び出し元に明確なシグナルを返してエッジで拒否します。

4つ目は、サーキットブレーカーによる再試行ストームの回避です。失敗がしきい値を超えたら、失敗すると予想されるリクエストを送る代わりに、回路を開いてクールダウン期間中はすぐに失敗させます。期間後にいくつかのプローブリクエストを送り、成功すれば回路を閉じます。

class CircuitBreaker {
  private failures = 0;
  private openedAt: number | null = null;
  constructor(private threshold = 5, private cooldownMs = 10_000) {}

  allow(): boolean {
    if (this.openedAt === null) return true;
    if (performance.now() - this.openedAt >= this.cooldownMs) {
      this.openedAt = null; // half-open: allow a probe
      this.failures = 0;
      return true;
    }
    return false;
  }

  record(ok: boolean): void {
    if (ok) {
      this.failures = 0;
      this.openedAt = null;
    } else if (++this.failures >= this.threshold) {
      this.openedAt = performance.now();
    }
  }
}

AIレート制限のマルチテナントクォータパターン

ここまでの説明は、単一の予算に対する単一アプリケーションを前提としていました。ElevenLabs上でSaaSを構築する場合、問題の形が変わります。同時実行数の予算はすべての顧客で共有されるため、あるテナントのバッチジョブが他のテナントのライブトラフィックを圧迫してはなりません。テナントと単一のアップストリーム上限の間に、公平性レイヤーが必要です。

基盤となるのは、テナントごとのトークンバケットです。各テナントに権利に応じたサイズのバケットを割り当て、テナントバケットとグローバルリミッターの両方が許可する場合にのみリクエストを受け入れます。

class MultiTenantAdmission {
  private tenantBuckets = new Map<string, TokenBucket>();
  constructor(private globalMaxInFlight: number) {}

  private bucket(tenant: string): TokenBucket {
    let b = this.tenantBuckets.get(tenant);
    if (!b) {
      // Each tenant: burst of 5, sustained 2 starts/sec. Tune per tier.
      b = new TokenBucket(5, 2);
      this.tenantBuckets.set(tenant, b);
    }
    return b;
  }

  async run<R>(tenant: string, work: () => Promise<R>): Promise<R> {
    const b = this.bucket(tenant);
    if (!b.tryAcquire()) {
      throw new RetryableError(429, b.timeUntil());
    }
    // ... then admit through the global limiter (e.g. the bounded pool above)
    return work();
  }
}

バケットは単一テナントによる過剰利用を防ぎますが、テナント間でグローバルリミッターを取り合う場合に誰を優先するかは決めません。そのためには、重み付き公平キューイングを使用します。

先着順で処理してはいけません。あるテナントのバーストがスロットを独占できてしまうからです。テナントごとのキューを維持し、各テナントの重みに比例してディスパッチしてください。これにより、有料テナントは無料テナントよりも競合中の容量を多く利用できます。

公平性に加えて、ヘッドルームを予約します。通常トラフィックが同時実行数の上限を100%消費しないようにしてください。レイテンシーに敏感なインタラクティブリクエストと優先度キューのため、たとえば15〜20%をバッファーとして確保します。

単一予算内の公平性だけでは足りなくなったら、ワークスペースまたはキー間でシャーディングします。どれほど公平に分配しても、単一の同時実行数予算は最終的にボトルネックになります。

その時点で、ワークロードを独自の予算を持つ別々のワークスペースまたはAPIキーへ分離します。たとえば、リアルタイムのエージェントトラフィック用に1つのキー、バックグラウンドナレーション用に別のキーを用意すれば、ナレーションのバックログがエージェントの容量に影響しません。

ワークスペースでは、スコープ制限、クレジットクォータ、キーごとの制御も適用できます。詳細は認証ドキュメントをご覧ください。

同時実行数の利用率をモニタリングする

測定なしにこれらを調整することはできません。測定していないヘッドルームは管理できないからです。すべてのレスポンスでcurrent-concurrent-requestsとmaximum-concurrent-requestsをモデルファミリー別に記録し、利用率をゲージとして出力してください。

function recordHeadroom(resp: Response, metrics: Metrics): void {
  const cur = Number(resp.headers.get("current-concurrent-requests"));
  const max = Number(resp.headers.get("maximum-concurrent-requests"));
  if (Number.isFinite(cur) && Number.isFinite(max)) {
    metrics.gauge("el.concurrency.current", cur);
    metrics.gauge("el.concurrency.max", max);
    if (max > 0) metrics.gauge("el.concurrency.utilization", cur / max);
  }
}

追跡すべき4つのシグナル:

  • 利用率(current / maximum)。
  • 総リクエスト数に対する429の割合。
  • 再試行深度、つまり論理リクエストあたりの試行回数。
  • モデル推論の数値ではなく、アプリケーションから測定する最初の音声までの時間。TTFAに含まれる内容については、レイテンシーの解説を参照してください。

健全なシステムでは、利用率を飽和状態より十分低く保ち、429は時折のバースト時にのみ発生します。これらのシグナルをモニタリングすれば、障害になるずっと前にレート制限の圧力を可視化できます。

クライアント側レート制限を超えてスケールすべきとき

クライアント側パターンで多くの負荷を処理できますが、定常的な需要はいずれその能力を超えます。そのときは、コストと手間の両方に役立つ変更を行うべきです。

以下の各ステップで追加の容量を得られます。

まず、インタラクティブなトラフィックをHTTPからWebSocketに切り替えます。エージェントまたはライブユースケースがHTTPで動作しているなら、WebSocketへの移行により、アクティブな生成だけがカウントされるようになります。会話型ワークロードでは、アイドル中の会話時間がスロットを消費しなくなるため、プランを変えずに実効容量が何倍にもなることがあります。

バーストは大きいものの平均負荷が予算内に収まるなら、トークンバケットまたはリーキーバケットと上限付きプールを組み合わせることで、ピークを平均化できます。

次に、適切なモデルを選びます。高速な生成では各スロットの保持時間が短くなるため、固定された同時実行数の上限で維持できる配信数が増えます。Eleven Flash v2.5はリアルタイム用途における最も低レイテンシーな選択肢です。これをインスタントボイスクローンまたはデフォルト音声と組み合わせると、プロフェッショナルボイスクローンで発生する生成ごとのオーバーヘッドを避けられます。

プランのアップグレードはその後にしてください。クライアントの動作を適切にした後も定常需要が本当に予算を超える場合、上位プランではモデルごとの同時実行数上限とキュー優先度の両方が上がります。API料金ページで各プランを比較してください。

公開されている上限を超える必要がある場合、エンタープライズプランでは引き上げられたカスタム同時実行数上限と、最高のキュー優先度を提供します。対象ユースケースでは、IP許可リスト(Enterpriseプレビュー)やゼロ保持モードなどの追加制御も利用できます。上限引き上げについては、アカウントマネージャーにお問い合わせください。

AIレート制限で押さえるべきポイント

根本的な誤りは、音声AIのレート制限をリクエスト数の計測として扱うことです。ここで扱ったすべては、同時実行数の制御に関するものです。成功を左右するのは、同じ瞬間に音声を生成しているリクエスト数と、それぞれがスロットを保持する時間です。

この事実を前提にクライアントを構築してください。

上限付きプールで処理中のリクエスト数を制限し、トークンバケットまたはリーキーバケットで受け入れを調整し、上限付き指数バックオフとフルジッターで再試行し、Retry-Afterを尊重して、再試行ストームが発生する前に回路を遮断します。

マルチテナントシステムでは、テナントごとのバケット、重み付き公平性、予約ヘッドルーム、分離のためのシャーディングを重ねます。current-concurrent-requestsとmaximum-concurrent-requestsのヘッダーを監視し、失敗ではなく利用率の傾向に対してアラートを設定してください。

本当にさらに容量が必要になったら、順番に対応します。まずWebSocketとより適切なクライアント動作、次に適切なモデル、その後にプランのアップグレード、最後にエンタープライズの上限です。

ElevenAPIで音声アプリケーションを構築

本番環境向けのAIレート制限は、適切なトランスポート、適切なモデル、そして現在の状況を正確に示すヘッダーから始まります。

ElevenAPIは、Eleven Flash v2.5のような低レイテンシーモデル、リアルタイムWebSocketストリーミング、スピーチtoテキスト、テキスト読み上げAPI、そして上限内でスケールする音声エージェントを構築できる、レスポンスごとの同時実行数ヘッダーを提供します。

この記事のAIレート制限戦略と組み合わせることで、予測可能なパフォーマンスを維持しながら、負荷時でも応答性の高い音声体験を提供できます。

ElevenAPIでモデルラインアップの動作を確認するか、アカウントを作成して、今すぐElevenLabsでの開発を始めましょう。

AIレート制限に関するよくある質問

執筆者

Tadas Petraは、ElevenLabsのプロダクトスイートを使った開発をサポートするデベロッパーリレーションズエンジニアです。また、デベロッパー向け教育プラットフォーム「Hungrimind」の創設者でもあります。以前はAgoraでリードデベロッパーアドボケイト、Zero To Masteryでインストラクターを務めていました。

関連記事

最高品質のAIオーディオで創造する