Skip to main content

レート制限を処理するためのベストプラクティス

Model Studio API は、リクエスト量、トークン使用量、および増加率を制限します。これらの戦略を適用して、スループットを最大化し、可用性を維持します。

Model Studio API は、時間経過に伴うリクエスト、トークン使用量、および増加率を制限します。これをレート制限と呼びます。大規模言語モデルはレイテンシーが長く、2 つのディメンション (リクエスト数とトークン量) で制約を受けます。単純な「エラー時のリトライ」戦略はここでは機能しません。専用のトラフィック制御が必要です。 実装コストの低い順に、3 種類のソリューションがあります: 現在 429 エラーのトラブルシューティングを行っている場合は、「エラー診断と戦略の推奨」に進み、原因を特定してください。トラフィックバーストエラーの場合は、まずサーバーサイドキューイングを試してください。これはリクエストヘッダーを 1 つ追加するだけで済みます。

プラットフォームのレート制限メカニズム

プラットフォームは、ルートアカウントレベルで各モデルを個別にレート制限します。トリガー後、サービスは通常 1 分以内に再開します。レート制限の条件とモデルごとの現在の使用量については、「レート制限」および「モデルのモニタリング」をご参照ください。3 種類のレート制限ルールが適用されます:
  • 分単位のクォータ制限 (RPM / TPM):1 分あたりの最大リクエスト数 (RPM) と 1 分あたりの最大トークン使用量 (TPM)。
  • 瞬間的な頻度制限 (RPS / TPS):1 秒あたりの最大リクエスト数 (RPS) と 1 秒あたりの最大トークン使用量 (TPS)。1 秒以内に API 呼び出しやトークン消費が集中すると、レート制限がトリガーされる可能性があります。
  • 増加率制限 (トラフィックバースト):リクエスト量やトークン使用量が急増すると、レート制限がトリガーされます。しきい値は動的に調整されます。この制限をトリガーしないように、リクエストを徐々に増やしてください。
以下のセクションでは、プラットフォーム設定、クライアントサイドのトラフィック制御、アーキテクチャレベルのフォールバックという 3 つのレベルでのソリューションについて説明します。

エラー診断と戦略の推奨

同じエラーコードが、異なるレート制限ディメンションによってトリガーされることがあります。高い同時実行性もサーバーを飽和させ、タイムアウトを引き起こす可能性があります。適応型輻輳制御戦略は、この緩和に役立ちます。

エラーコード (DashScope / OpenAI)

トリガーディメンション

特徴診断

推奨戦略

Throttling.RateQuota / limit_requests

リクエストレート超過
(RPM 超過)

断続的なエラー。成功率は時間とともに低下します。

トークンバケット:単位時間あたりのリクエストクォータを制御します。

リクエストレート超過
(RPS 超過)

起動時や同時実行の急増時にエラーが集中します。

同時実行セマフォまたは平滑化レートリミッター:リクエスト間の間隔を広げます。

Throttling.AllocationQuota / insufficient_quota

トークン使用量超過
(TPM 超過)

長文テキスト処理時に断続的なエラーが発生します。

デュアルトークンバケット:RPM と TPM の両方のクォータを同時に制限します。

トークン使用量超過
(TPS 超過)

長文テキストの並行処理中に瞬間的なトークン消費量が高すぎます。

同時実行セマフォまたは平滑化レートリミッター。

Throttling.BurstRate / limit_burst_rate

トラフィック増加率超過
(トラフィックバースト)

起動後またはアイドル状態からの復帰後に、突然大量のリクエストが発生します。

まずサーバーサイドキューイングを試すことを推奨します。あるいは、initial_tokens=0 のような低い初期値を持つトークンバケットを使用してスロースタートを実装するか、平滑化レートリミッターを使用してピークシフトを行います。

プラットフォーム設定ソリューション

これらのソリューションはプラットフォームの機能に依存しており、クライアントサイドのコード変更は最小限または不要です。

サーバーサイドキューイング (推奨)

トラフィックバーストのレート制限に対して、Model Studio はリクエストヘッダーで待機時間の上限を指定できます。サーバーはリクエストを受信すると、その時間枠内で随時リトライし、処理が開始されるか時間枠内のリトライが終了するまで続けます。この値は上限であり、設定値に達してからリトライするわけではありません。これにより、即座に 429 応答を返す場合と比較して、トラフィックバースト中の成功率が大幅に向上します。
この機能は、増加率/トラフィックバーストのレート制限 (Throttling.BurstRate) にのみ適用されます。絶対クォータのレート制限 (RPM/TPM) には適用されません。
設定 リクエストヘッダーに X-DashScope-Wait-Timeout フィールドを追加します:

ヘッダーフィールド

例

説明

X-DashScope-Wait-Timeout

90

バーストリクエストのキューイング待機時間の上限 (秒単位)。サーバーはこの時間枠内でリトライし、設定値に達してからリトライするわけではありません。

  • 値 0:キューイングなし。429 エラーが即座に返されます。

  • この値は上限のみを意味し、実際の待機時間は設定値より短くなる場合があります。たとえば 90 秒を設定した場合、呼び出しは 30 秒程度でリトライを開始することがあります。リトライが失敗した場合は、429 エラーが返されます。

  • 推奨範囲:3〜120 秒。トラフィックバースト時は、リトライ機会を増やして成功率を高めるため、大きめの値を設定することを推奨します。

タイムアウト設定 キューイングを有効にした後、追加された待機時間を考慮してクライアントのタイムアウトを調整します:
  • 非ストリーミングリクエスト (stream: false):タイムアウト = 元の基本タイムアウト + Wait-Timeout の値。
  • ストリーミングリクエスト (stream: true):タイムアウト > Wait-Timeout の値。ストリーミングリクエストは最初のチャンクの後にタイミングを開始するため、最初の応答タイムアウトはキューイング時間を超えるだけで十分です。
例:元の基本タイムアウトが 120 秒で、Wait-Timeout が 90 秒に設定されている場合、非ストリーミングリクエストのタイムアウトは 210 秒に設定する必要があります。
OpenAI Python SDK
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    timeout=210.0,  # 元のタイムアウト 120 秒 + キューイング待機の上限 90 秒
)

response = client.chat.completions.create(
    model="qwen-plus",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "X-DashScope-Wait-Timeout": "90"  # キューイング待機時間の上限:90 秒
    }
)
print(response.choices[0].message.content)

プロビジョンドスループットユニット (PTU)

PTU サービスは、専用の予約済みコンピューティング能力を提供します。パブリックリソースプールでの競合を回避し、リアルタイムで高スループットの要件に最適なソリューションです。 ビジネスに確定的なスループット要件 (SLA コミットメントなど) がある場合、またはクライアントサイドのトラフィック制御なしで安定した高スループットが必要な場合は、PTU を使用してください。 PTU は予約済みリソースであり、完全に使用されていない場合でも継続的に課金されます。無駄を避けるために、実際のピーク負荷に基づいて必要な仕様を評価してください。

非同期バッチ処理 (Batch API)

厳密なリアルタイム要件がないタスク (データクレンジング、バッチ分析など) には、Batch API を使用してバッチ処理に送信します。タスクはオフピーク時間に実行され、結果を非同期で返し、オンラインのレート制限の対象にはなりません。 数時間から数日の応答時間を許容できるオフラインタスクに適しています:データアノテーション、ログ分析、バッチ要約など。Batch API のコストは、通常、リアルタイム API 呼び出しよりも低くなります。 応答時間は保証されません。即時応答が必要なサービスには適していません。送信後、ポーリングまたはコールバックを介して結果を取得します。

クライアントサイドのトラフィック制御戦略

サーバーサイドキューイングなどのプラットフォームソリューションでは不十分な場合、クライアントサイドにトラフィック制御を追加します。基本原則は、バーストを避けるためにリクエストをタイムウィンドウ内で均等に分散させることです。システムの起動後や長時間のアイドル期間の後には、同時実行数を徐々に増やしてください。 複雑さが増す順に 4 つの戦略があります。各戦略は前の戦略を含み、さらに機能を追加します:
  • 基本的なリトライは、受動的な防御のみを提供します。
  • リクエストレート制限は、アクティブなキューイングを追加します。
  • トラフィックシェーピングは、さらにトークンレベルの制御とスムーズな送信を導入します。
  • 適応型輻輳制御は、リアルタイムのフィードバックに基づいて送信レートを動的に調整します。
ニーズに合った最も低コストの戦略を選択してください。

各戦略のスループット性能比較

image
異なる負荷下でのスループット比較:
  • 基本的なリトライ戦略:低負荷下では効果的です。高い同時実行性の下では輻輳崩壊を起こしやすく、スループットが急激に低下します。
  • リクエストレート制限戦略:輻輳崩壊に対する強力な保護を提供します。ただし、長文テキストを含む混合ワークロード下では、トークン制御がないため、スループットはのこぎり状の変動を示します。
  • トラフィックシェーピング戦略:高い安定性。ピークスループットを一部犠牲にすることで、スムーズな出力を実現します。
  • 適応型輻輳制御戦略:高負荷下で安定した高スループットポイントに動的に収束できますが、コールドスタート時のプロービングオーバーヘッドがあります。

基本的なリトライ戦略

個人テスト、ローカルスクリプト、および低頻度のバックグラウンドタスクに適しています。送信リクエストにレート制限はありません。429 または 5xx エラーが発生した場合にのみ、ランダムジッター付きの指数バックオフリトライをトリガーします。 積極的なトラフィック制御はありません。マルチスレッドの同時実行下では、これによりレート制限が容易にトリガーされ、リクエストのバックアップが発生します。
import openai
from openai import OpenAI
from tenacity import (
    retry,
    stop_after_attempt,
    wait_random_exponential,
    retry_if_exception_type
)

RETRYABLE_ERRORS = (
    openai.RateLimitError,
    openai.InternalServerError,
    openai.APIConnectionError,
)

@retry(
    wait=wait_random_exponential(min=1, max=60),
    stop=stop_after_attempt(6),
    retry=retry_if_exception_type(RETRYABLE_ERRORS)
)
def chat_with_retry(client, model, messages, max_tokens):
    return client.chat.completions.create(
        model=model,
        max_tokens=max_tokens,
        messages=messages
    )

client = OpenAI(
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="YOUR_DASHSCOPE_API_KEY"
)

try:
    response = chat_with_retry(
        client=client,
        model="qwen-plus",
        messages=[{"role": "user", "content": "What is exponential backoff retry?"}],
        max_tokens=1024
    )
    print(response.choices[0].message.content)
except Exception as e:
    print(f"Request failed: {e}")
このコードは、固定間隔のリトライではなく、指数バックオフを使用します。固定間隔のリトライは、失敗したすべてのリクエストを同時に再送信させ、再びレート制限をトリガーします。ランダムジッター付きの指数バックオフは、リトライを分散させます:
  • 待機時間が段階的に倍増:例えば、1s, 2s, 4s...。これにより、短期間での繰り返しリクエストを回避します。
  • ランダムジッターを追加:ランダムな値 (例:2s +/- 0.5s) を加えることで、リトライのトラフィックを分散させ、二次的なフラッディング (サンダーリングハード効果) を防ぎます。
システムは、「失敗 — 一斉にリトライ — 再び失敗」のループに陥るのではなく、徐々に回復します。

リクエストレート制限戦略

受動的なリトライだけでは、実際のトラフィックを処理できません。頻繁なリトライはレイテンシーを増加させます。この戦略はアクティブなトラフィック制御を導入します:リクエストを送信する前にチェックしてスロットルし、順序のないトラフィックを RPM 制限を尊重するキューに整理します。アクティブな平滑化は、予測可能な小さなキューイング遅延を追加しますが、これは「エラー — 待機 — リトライ」ループのコストよりもはるかに小さいです。小さな既知のコストは、大きな未知のコストに勝ります。 チャットボットや、最初のトークンまでの時間が重要な他の軽量なリクエスト・レスポンスサービスに適しています。 クライアントサイドのアクティブなキューイングは、2 段階の制御を使用します:
  • RPM トークンバケット:1 分あたりの総リクエスト数を制限します。バケットの容量は RPM クォータに等しく、トークンは一定のレートで補充されます。借用をサポート:トークンが不十分な場合、リクエストは将来のクォータから借用し、厳密に FIFO を守ります。
  • 同時実行セマフォ:同時リクエストを制限し、瞬間的な高い同時実行性が RPS 制限をトリガーするのを防ぎます。
これら 2 つのレベルを厳密な順序で実行します:最初に RPM トークンを取得し、次にセマフォを取得します。同時実行スロットは貴重なリソースです。実行準備ができたリクエストにのみ割り当ててください。順序を逆にすると、高負荷下でhead-of-line ブロッキングが発生します:リクエストはスロットを保持しますがトークンがなく、すべてのスロットが埋まり、何も送信されません。原則:待機中に貴重なリソースを保持しないこと。 以下のコードは、トークンバケットをフルの状態 (initial_tokens=rpm_limit) で初期化します。これは、起動時にすぐにリクエストを処理する必要があるオンラインサービスに適しています。フルのバケットがレート制限をトリガーする場合は、初期値を下げて (例えば、「空のバケットで開始」するために initial_tokens=0)、より緩やかに立ち上げてください。 この戦略はトークン使用量を追跡しません。長文テキストのタスクは、依然として TPM クォータを使い果たす可能性があります。
import time

class TokenBucket:
    """
    1 分あたりのリクエスト数 (RPM) を制御するためのトークンバケット実装。
    高い同時実行性の下で先入れ先出し (FIFO) 順序を保証するための負債メカニズムをサポートします。
    """
    def __init__(self, quota_per_minute: float, initial_tokens: float = 0.0):
        self.capacity = quota_per_minute
        self.tokens = initial_tokens
        self.refill_rate = quota_per_minute / 60.0
        self.last_refill = time.monotonic()

    def reserve(self, cost: float = 1.0) -> float:
        """
        トークンを取得します。
        トークンが不十分な場合、待機する秒数を返します (負債をサポート)。
        """
        self._refill()

        # 1. トークンが十分な場合:直接差し引く
        if self.tokens >= cost:
            self.tokens -= cost
            return 0.0

        # 2. トークンが不十分な場合:待機時間を計算し、負債を発生させる
        # FIFO 順序を保証するために、現在のリクエストのために将来のトークンを「予約」する
        deficit = cost - self.tokens
        wait_seconds = deficit / self.refill_rate
        self.tokens -= cost
        return wait_seconds

    def _refill(self):
        """経過時間に基づいてトークンを補充します。"""
        now = time.monotonic()
        elapsed = now - self.last_refill
        if elapsed > 0:
            self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
            self.last_refill = now

トラフィックシェーピング戦略

高く安定したスループットを必要とするバッチシナリオ (リアルタイム RAG インジェスト、バルク長文ドキュメント分析など) では、リクエストレート制限には TPM の死角があります。トラフィックシェーピングは、デュアルリソース認識 (RPM & TPM) と、バースト的なトラフィックをスムーズなフローに変換するシェーピングメカニズムを追加します。 リクエストレート制限に対する機能強化:
  • デュアルリソース制御 (RPM & TPM):RPM と TPM の両方のトークンバケットを維持します。すべてのリクエストは、送信される前に両方のディメンションのクォータチェックを通過する必要があります。
  • 入力時の事前控除、出力後の精算:出力長はリクエスト前に不明です。TPM バケットは、送信時に入力トークンを事前控除し、完了後に実際の出力トークンを精算します。トークンがマイナスになっても、後続のリクエストはカウントがプラスになるのを待つため、自然にフローが平滑化されます。
  • 継続的なウォームアップ:コールドスタート中、トークン発行レートは時間とともに線形に増加し、初期バーストのリスクを排除します。
  • 平滑化レートリミッター (ペーシング):リクエスト間の最小間隔を強制する (ペーシング) ことで送信レートを平滑化し、レート制限がトリガーされるリスクを低減します。
代替案:起動時にわずかなキューイング遅延が許容できる場合は、標準のトークンバケットを再利用し (initial_tokens=0 に設定)、より低い複雑さで安全なスタートを実現します。ここでの Python トークンバケットはデモンストレーション用です。本番環境では、Java の Guava の SmoothRateLimiter のような成熟したレート制限ライブラリを使用してください。 コード例では、平滑化の待機は同時実行ロックの内側に配置されています。待機が終了した後、複数のリクエストが同時にセマフォを競合し、出口でトラフィックが再び束になる可能性があります。ロック内での平滑化は、同時実行効率をわずかに低下させますが、正確な送信間隔を保証します。 完全なトラフィックシェーピングパイプラインは次のとおりです:入力トークンの推定 → デュアルアドミッション (RPM & TPM) → 同時実行ロック → トラフィックシェーピング → 送信 → 出力トークンの精算。
image
保守的な平滑化は、ピーク時の同時実行性を一部犠牲にします。極めて低いレイテンシーを必要とするサービスには適していません。
import time

class TokenBucket:
    """継続的なウォームアップメカニズムをサポートする高度なトークンバケット。"""
    def __init__(self, quota_per_minute: float, warmup_seconds: float = 0.0):
        self.capacity = quota_per_minute
        self.tokens = 0.0
        self.target_refill_rate = quota_per_minute / 60.0
        self.warmup_seconds = warmup_seconds
        self.start_time = time.monotonic()
        self.last_update_time = self.start_time
        self.cumulative_generated = 0.0

    def _get_cumulative_tokens(self, t: float) -> float:
        if t <= 0:
            return 0.0
        R = self.target_refill_rate
        T = self.warmup_seconds
        if T <= 0:
            return R * t
        if t <= T:
            return (R / (2 * T)) * (t ** 2)
        else:
            warmup_total = (R * T) / 2.0
            return warmup_total + R * (t - T)

    def _get_time_for_cumulative_tokens(self, target_cumulative: float) -> float:
        if target_cumulative <= 0:
            return 0.0
        R = self.target_refill_rate
        T = self.warmup_seconds
        if T <= 0:
            return target_cumulative / R
        warmup_total = (R * T) / 2.0
        if target_cumulative <= warmup_total:
            return ((2 * T * target_cumulative) / R) ** 0.5
        else:
            return (target_cumulative - warmup_total) / R + T

    def reserve(self, cost: float = 1.0) -> float:
        now = time.monotonic()
        relative_now = now - self.start_time
        current_cumulative = self._get_cumulative_tokens(relative_now)
        new_tokens = current_cumulative - self.cumulative_generated
        self.tokens = min(self.capacity, self.tokens + new_tokens)
        self.cumulative_generated = current_cumulative
        self.last_update_time = now
        if self.tokens >= cost:
            self.tokens -= cost
            return 0.0
        deficit = cost - self.tokens
        self.tokens -= cost
        target_cumulative = self.cumulative_generated + deficit
        target_time = self._get_time_for_cumulative_tokens(target_cumulative)
        wait_seconds = target_time - relative_now
        return max(0.0, wait_seconds)

    def adjust(self, amount: float):
        self.tokens = min(self.capacity, self.tokens + amount)

適応型輻輳制御戦略

大規模で動的なワークロードに適しています:API ゲートウェイ、複雑なプロキシ、マルチテナントシステムなど。
選択のヒント:この戦略は万能の解決策ではありませんこの戦略は、非常に不確実で変動の激しい環境を扱います。これは万能の選択肢ではありません:
  • パフォーマンスのパラドックス:負荷が予測可能である場合 (バッチ処理など)、最適な静的パラメーターは、「試行と収束」を必要とする動的プロービングよりも優れたパフォーマンスを発揮します。
  • プロービングのオーバーヘッド:動的アルゴリズムには、必然的にコールドスタートのランプアップと探索的な変動が伴います。これは、既知のシナリオでは不要なオーバーヘッドです。
  • メンテナンスコスト:閉ループフィードバックは、システムの複雑さとトラブルシューティングの難易度を増大させます。
ビジネスが非常に大規模で、複雑な負荷、および著しい変動性を持たない限り、よりシンプルな最初の 3 つの戦略のいずれかを選択してください。
リクエストレート制限とトラフィックシェーピングは静的クォータ戦略であり、安定的で予測可能な負荷の下でうまく機能します。しかし、ゲートウェイレベルでは、下流の負荷は絶えず変化し (短いリクエストと深い推論が混在)、プラットフォームのしきい値は動的に変動します。静的戦略は、効率と安定性のバランスを取るのに苦労します。 BBR に着想を得て、この戦略は EBP (Elastic Bandwidth Probing) に基づく閉ループ制御システムを構築します。RPM/TPM クォータをガイド的な上限として使用し、リアルタイムのフィードバック (レイテンシーの変化、レート制限シグナル) から最適な送信レートを動的に計算します。
  • EBP:過去の最高の成功ウォーターマークを保存します。現在の同時実行数とウォーターマークとの距離に基づいてバネの張力をシミュレートすることで、プロービングゲインを計算します (遠いほど速く、近いほど遅く)。高い飽和状態でも探索を続けるために、小さな線形推力を追加します。
  • TPT 輻輳認識:LLM の生成時間は出力長に比例します。長文テキストでの高レイテンシーは輻輳を意味しません。TPT (トークンあたりの時間) は、コンテンツ長のノイズを除去します。輻輳は、TPT が著しく悪化した場合にのみ宣言されます。
  • バースト防止レートガバナー:EBP のターゲットに関係なく、ガバナーは同時実行数の増加加速度を制限し、スムーズなランプアップを保証し、増加率制限をトリガーするステップ変更を回避します。
image
大規模モデル向けのネイティブ BBR からの主な変更点:
  • ガイド付きプロービング:既知の RPM/TPM クォータを「ガイド的な上限」として導入し、盲目的なプロービングによる頻繁な衝突を回避します。
  • シグナルソース (RTT → TPT):ネイティブ BBR は RTT を使用します。LLM シナリオでは、コンテンツ長のレイテンシーがネットワークジッターをはるかに上回ります。TPT はその干渉を排除します。
  • 応答メカニズムの強化 (ProbeRTT → Hold):レイテンシーの変動に直面した場合、積極的にバックオフしてスループットを低下させるのではなく、現在の同時実行数レベルを維持することを選択します。
  • ハードレート制限応答 (Packet Loss → 429 Drain):429 エラーがトリガーされると、即座にアグレッシブな Drain 状態に入り、クールダウン期間後に高速回復を実行します。
制限事項:
  • TPT ノイズ:TPT は「合計レイテンシー / 合計トークン」として推定されます。合計レイテンシーには、ネットワークのラウンドトリップ、キューイング、最初のトークンまでの時間が含まれ、ジッターや長い入力の影響を受けやすく、誤って Hold 状態をトリガーする可能性があります。
  • 大規模リクエストのスターベーション:スケジューリングパフォーマンスのために、非厳密な FIFO ウェイクアップを使用します。クォータが不足している場合、短いトークンのリクエストがリソースを先取りし、長いトークンのリクエストが長時間待たされる可能性があります。
  • コールドスタート:統計モデルを構築するためにウォームアップ期間が必要です。低負荷または短命のタスクでは、スループットが最初の 3 つの戦略よりも低くなる可能性があります。
class ElasticCongestionController:
    async def acquire(self):
        """[アドミッションフェーズ] リクエストを開始する前にチェック"""
        # 1. SSR スロースタート再起動:アイドル時間が長すぎる場合、制限を積極的に減衰させる
        #    古いウォーターマークによるバーストトラフィックを防ぐため。
        if self.is_idle_too_long():
            self.perform_slow_start_restart()

        # 2. サーキットブレーカーチェック:DRAIN (クールダウン) 状態の場合、強制的に待機。
        if self.state == CongestionState.DRAIN:
            await self.wait_for_cooldown()

        # 3. デュアルバジェットチェック:同時実行スロットとトークンバジェットの両方をチェック。
        await self.wait_for_budget(request_tokens)

    async def release(self, latency, actual_tokens, error):
        """[フィードバックフェーズ] リクエスト終了後の決定"""
        if error:
            # [障害応答] レート制限エラー (429/503) 時:即座にドレイン + 乗法バックオフ
            self.state = CongestionState.DRAIN
            self.concurrency_limit *= self.backoff_factor  # 例:0.7
            return

        # [通常応答] TPT (トークンあたりの時間) を計算
        current_tpt = latency / actual_tokens

        # [輻輳認識] TPT スパイク (生成が遅くなる):HOLD に入り観察
        # 同時実行数レベルを維持し、バックオフも増加もさせない
        if current_tpt > self.metrics.ema_tpt * 2.0:
            self.state = CongestionState.HOLD
        else:
            # [定常状態プロービング] ネットワークは正常:EBP 弾性プロービングを実行
            self.state = CongestionState.PROBING
            self.update_limit_via_ebp()

アーキテクチャレベルのフォールバックソリューション

プラットフォーム設定とクライアントサイドのトラフィック制御が、可用性やピークスループットの要件をまだ満たせない場合、アーキテクチャレベルでフォールバックメカニズムを追加します。

モデルフォールバック

プライマリモデルがレート制限やサービスの問題で応答できない場合、より寛大なクォータを持つ代替モデルに自動的にフォールバックします。 フォールバックパスの設計原則
  • 異なるシリーズのモデルを選択:レート制限はモデルごとです。フォールバックには異なるモデルを使用します。例えば、qwen3.6-plus から qwen3.6-flash にフォールバックします。
  • レート制限エラーでのみフォールバックをトリガー:すべての例外ではなく、429 エラーでフォールバックします。モデルを切り替えても、ネットワークタイムアウトやパラメーターエラーは解決しません。
  • フォールバックモデルを事前に検証:フォールバック後に機能的な問題が発生しないように、必要な機能 (Function Calling、構造化出力など) をサポートしていることを確認します。
以下の例は、429 エラーコードに基づくモデルフォールバックロジックを示しています。プライマリモデルへのリクエストがレート制限をトリガーすると、自動的にフォールバックモデルに切り替えてリトライします。
import os
import asyncio
from openai import AsyncOpenAI, APIStatusError

# プライマリモデルとフォールバックモデル (異なるシリーズ、独立したクォータ)
PRIMARY_MODEL = "qwen3.6-plus"
FALLBACK_MODEL = "qwen3.6-flash"

client = AsyncOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

async def chat_with_fallback(messages: list) -> str:
    """フォールバック付きリクエスト:プライマリモデルがレート制限された場合、自動的にフォールバックモデルに切り替えます。"""
    for model in [PRIMARY_MODEL, FALLBACK_MODEL]:
        try:
            response = await client.chat.completions.create(
                model=model,
                messages=messages
            )
            return response.choices[0].message.content
        except APIStatusError as e:
            if e.status_code == 429 and model == PRIMARY_MODEL:
                print(f"[Rate Limit Triggered] {model}, falling back to {FALLBACK_MODEL}")
                continue
            raise
    raise RuntimeError("All models are unavailable")

async def main():
    result = await chat_with_fallback(
        messages=[{"role": "user", "content": "Hello"}]
    )
    print(result)

if __name__ == "__main__":
    asyncio.run(main())
モデルフォールバックは、クライアントサイドのトラフィック制御戦略と組み合わせることができます。例えば、リクエストレート制限戦略のリトライメカニズムにフォールバックロジックを統合することができます。リトライが尽きてもレート制限がトリガーされる場合は、フォールバックモデルに切り替えます。

メッセージキュー (MQ) を使用したピークシフト

即時応答を必要としないバックエンドサービスには、ピークシフトのためにメッセージミドルウェア (RabbitMQ, Kafka) を導入します。バーストトラフィックはまず MQ に入り、コンシューマーはレート制限クォータに合わせて一定のレートでプルして処理します。これにより、フロントエンドのピークとバックエンドの呼び出しが分離されます。 ユーザーが非同期の結果を受け入れるビジネスに適しています:チケット処理、コンテンツモデレーション、バッチデータアノテーションなど。 主要な設計ポイント:
  • コンシューマーのレート制御:コンシューマー側は、メッセージを無制限にプルするのではなく、RPM/TPM クォータに基づいて一定のレートでメッセージを消費するために、リクエストレート制限またはトラフィックシェーピング戦略を使用する必要があります。
  • デッドレター処理:複数回のリトライ後に失敗したメッセージをデッドレターキューに移動し、アラートをトリガーします。無限リトライが消費をブロックするのを防ぎます。
  • バックプレッシャーの伝播:MQ のバックログがしきい値を超えた場合、上流にプレッシャーを伝播させ (例えば、キューイングステータスを返す)、キューの無限の増加を防ぎます。

本番環境での考慮事項

コード例では、コアアルゴリズムを実証するために Python の asyncio シングルスレッドループを使用しています。大規模な本番環境で使用する前に、以下を考慮してください。
  • 非テキストモデルへの適応 上記の戦略はテキストモデルを例として使用していますが、コア原則はマルチモーダルサービス (画像生成、音声合成) にも適用されます。単位は異なりますが、本質は同じです:送信レートと処理能力の制限。
    • 音声認識などのモデルは、通常、単位時間あたりのリクエスト数 (RPM など) と使用量 (音声の長さなど) の両方で制約されます。戦略は基本的にテキストモデルと同じです。
    • 画像やビデオのモデルは、通常、タスクの送信レートと同時タスク数によって制約されます。リクエストレート制限戦略と同じアプローチを使用できます:タスクの送信レートを制限し、セマフォを使用して同時実行数を制御します。
    メトリクスがどのように変化しても、クライアントサイドのスロットリングの原則は変わりません。カウンターまたはプロービングメトリックを、ご利用のモダリティのものに置き換えてください。特定のルールについては、「レート制限」をご参照ください。
  • 並行モデルにおける原子性 例:asyncio はシングルスレッドの協調スケジューリングを使用するため、状態の変更は単一プロセス内で本質的にアトミックです。 本番環境:マルチスレッドまたはマルチプロセス環境では、トークンバケットと統計ウィンドウの同時実行安全性を確保してください。競合状態はトラフィック制御を破壊します。
  • 分散レート制限 例:すべてのトラフィック制御コンポーネントはインメモリです。 本番環境:マルチインスタンスのデプロイメントでは、各インスタンスが独立してスロットルします。合計使用量が制限を超える可能性があります。すべてのノードで一元的に使用量を管理するために、中央集権的なカウンター (Redis など) を使用してください。
  • 優先度付きキューとスターベーション防止 例:優先度の区別はありません。適応型輻輳制御戦略は、スケジューリングパフォーマンスのために非厳密な FIFO ウェイクアップを使用します。 本番環境:高/低優先度のリクエストには、重み付き優先度付きキューを実装して、高優先度トラフィックの帯域幅を保証します。低優先度キューには最小クォータを予約して、スターベーションを防ぎます。