Azure Azure OpenAI rate limiting FinOps LLM

Azure OpenAI Service - błąd 429 Rate Limit po przekroczeniu limitu tokenów lub zapytań

Rozwiązanie błędu 429 w Azure OpenAI: identyfikacja wąskiego gardła (TPM vs RPM), wdrożenie retry logic, zwiększenie quota lub przejście na PTU.

·
Azure OpenAI API zwraca HTTP 429 Too Many Requests, gdy aplikacja przekracza przydzieloną quota tokenów (TPM) lub liczbę zapytań na minutę (RPM). Skutek: użytkownicy doświadczają błędów w chatbotach, pipeline RAG i systemach automatycznego przetwarzania dokumentów. W godzinach szczytu może to oznaczać utratę kilkudziesięciu procent ruchu produkcyjnego do modeli GPT-4o lub GPT-4.

Ten runbook opisuje diagnozę i rozwiązanie problemu rate limitingu w Azure OpenAI Service. Szczegółową analizę kosztów i architektury modeli AI w chmurze znajdziesz w artykule Azure OpenAI vs modele self-hosted - koszty, latency i rezydencja danych w UE. Jeśli potrzebujesz pomocy z optymalizacją kosztów i wydajności AI workloadów - umów konsultacje.

Objaw

Aplikacja otrzymuje odpowiedź HTTP 429 z Azure OpenAI API. W logach widoczny jest komunikat RateLimitError lub 429 Too Many Requests:

# Sprawdź aktualne zużycie quota dla deploymentu
az cognitiveservices usage list \
  --name my-openai-resource \
  --resource-group rg-ai \
  --query "[?name.value=='OpenAI.Standard.gpt-4o'].{model:name.value, current:currentValue, limit:limit}" -o table

# Sprawdź metryki rate limitingu z ostatnich 30 minut
az monitor metrics list \
  --resource /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
  --metric "AzureOpenAIRequests" \
  --dimension StatusCode \
  --interval PT1M \
  --start-time $(date -u -d '-30 minutes' +%Y-%m-%dT%H:%M:%SZ) \
  --query "value[0].timeseries[?metricValues[?statusCode=='429']]" -o json

# Lista deploymentów z ich limitami
az cognitiveservices account deployment list \
  --name my-openai-resource \
  --resource-group rg-ai \
  --query "[].{model:properties.model.name, version:properties.model.version, capacity:sku.capacity, name:name}" -o table

W odpowiedzi HTTP z Azure OpenAI sprawdź nagłówki rate limit:

# Pojedyncze zapytanie testowe z wyświetleniem nagłówków
curl -s -D - https://my-openai-resource.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21 \
  -H "Content-Type: application/json" \
  -H "api-key: $AZURE_OPENAI_KEY" \
  -d '{"messages":[{"role":"user","content":"test"}],"max_tokens":5}' \
  2>&1 | grep -i "x-ratelimit\|retry-after"

# Spodziewane nagłówki:
# x-ratelimit-remaining-tokens: 0        <-- brak dostępnych tokenów
# x-ratelimit-remaining-requests: 0      <-- brak dostępnych zapytań
# retry-after: 12                         <-- czas oczekiwania w sekundach

Jeśli x-ratelimit-remaining-tokens lub x-ratelimit-remaining-requests wynosi 0 - deployment wyczerpał swoją quota na bieżącą minutę.

Przyczyna

Azure OpenAI stosuje dwa niezależne limity, z których każdy może wyzwolić błąd 429:

  • TPM (Tokens Per Minute) - łączna liczba tokenów (prompt + completion) przetworzonych w ciągu minuty. Dla deploymentu GPT-4o domyślna quota w regionie Sweden Central to np. 80K TPM. Długie prompty (RAG z kontekstem dokumentów) szybko wyczerpują ten limit.

  • RPM (Requests Per Minute) - liczba wywołań API w ciągu minuty. Wartość RPM jest obliczana automatycznie jako 6× wartość TPM ÷ 1000. Dla 80K TPM = 480 RPM. Wiele krótkich zapytań (np. klasyfikacja zdań) może uderzyć w RPM zanim wyczerpie TPM.

Dodatkowe czynniki wpływające na problem:

  • Global Standard vs Data Zone deployments - typy deploymentu mają różne bazowe quota. Global Standard oferuje wyższe limity domyślne, ale Data Zone (z gwarancją regionu danych) ma niższe pule, szczególnie dla GPT-4o w regionach europejskich. Quota jest współdzielona między wszystkimi deploymentami tego samego modelu w ramach subskrypcji i regionu.

  • Skokowy wzrost ruchu (burst) - nawet jeśli średnie zużycie mieści się w limicie, nagły skok ruchu (np. cron job przetwarzający paczkę dokumentów) może wyczerpać quota w pierwszych sekundach minuty. Azure nie buforuje nadwyżki - od razu zwraca 429.

  • Token estimation mismatch - aplikacja nie uwzględnia tokenów z system promptu, function calling schema czy historii konwersacji. Rzeczywiste zużycie TPM jest 2-3× wyższe niż estymowane na podstawie samych wiadomości użytkownika.

Rozwiązanie

A) Retry z exponential backoff:

Najprostsze rozwiązanie na sporadyczne 429. Implementacja z biblioteką tenacity:

import openai
from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_exception_type
from openai import AzureOpenAI, RateLimitError

client = AzureOpenAI(
    azure_endpoint="https://my-openai-resource.openai.azure.com",
    api_version="2024-10-21",
    api_key=os.environ["AZURE_OPENAI_KEY"],
)

@retry(
    retry=retry_if_exception_type(RateLimitError),
    wait=wait_exponential(multiplier=1, min=2, max=60),
    stop=stop_after_attempt(6),
    before_sleep=lambda retry_state: print(
        f"429 received, retry {retry_state.attempt_number}/6, "
        f"waiting {retry_state.next_action.sleep}s..."
    ),
)
def call_openai(messages: list, model: str = "gpt-4o") -> str:
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        max_tokens=1024,
    )
    return response.choices[0].message.content


# Alternatywnie - wykorzystaj nagłówek retry-after z odpowiedzi
@retry(
    retry=retry_if_exception_type(RateLimitError),
    wait=lambda retry_state: float(
        getattr(retry_state.outcome.exception(), 'response', None)
        and retry_state.outcome.exception().response.headers.get('retry-after', 5)
        or 5
    ),
    stop=stop_after_attempt(5),
)
def call_openai_with_retry_after(messages: list) -> str:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
    )
    return response.choices[0].message.content

B) Zwiększenie quota przez Azure Portal / CLI:

# Sprawdź aktualne limity quota w regionie
az cognitiveservices model list \
  --location swedencentral \
  --query "[?model.name=='gpt-4o'].{model:model.name, version:model.version, maxCapacity:model.skus[0].capacity.maximum}" -o table

# Sprawdź aktualną capacity deploymentu
az cognitiveservices account deployment show \
  --name my-openai-resource \
  --resource-group rg-ai \
  --deployment-name gpt-4o \
  --query "{capacity:sku.capacity, model:properties.model.name}" -o json

# Zwiększ capacity deploymentu (np. z 80K do 150K TPM)
az cognitiveservices account deployment create \
  --name my-openai-resource \
  --resource-group rg-ai \
  --deployment-name gpt-4o \
  --model-name gpt-4o \
  --model-version "2024-08-06" \
  --model-format OpenAI \
  --sku-capacity 150 \
  --sku-name "GlobalStandard"

# Jeśli region nie ma wystarczającej capacity - sprawdź dostępność w innych regionach
az cognitiveservices model list \
  --location eastus2 \
  --query "[?model.name=='gpt-4o'].{version:model.version, available:model.skus[0].capacity.maximum}" -o table

Uwaga: Wartość sku-capacity podawana jest w tysiącach TPM. Wartość 150 oznacza 150 000 TPM.

C) Load balancing przez wiele deploymentów (LiteLLM):

Rozłóż ruch na kilka zasobów OpenAI w różnych regionach:

# litellm_config.yaml
model_list:
  - model_name: gpt-4o
    litellm_params:
      model: azure/gpt-4o
      api_base: https://openai-swedencentral.openai.azure.com/
      api_key: os.environ/AZURE_OPENAI_KEY_SWEDEN
      api_version: "2024-10-21"
    model_info:
      tpm: 80000
      rpm: 480

  - model_name: gpt-4o
    litellm_params:
      model: azure/gpt-4o
      api_base: https://openai-eastus2.openai.azure.com/
      api_key: os.environ/AZURE_OPENAI_KEY_EASTUS2
      api_version: "2024-10-21"
    model_info:
      tpm: 80000
      rpm: 480

  - model_name: gpt-4o
    litellm_params:
      model: azure/gpt-4o
      api_base: https://openai-francecentral.openai.azure.com/
      api_key: os.environ/AZURE_OPENAI_KEY_FRANCE
      api_version: "2024-10-21"
    model_info:
      tpm: 60000
      rpm: 360

router_settings:
  routing_strategy: "usage-based-routing-v2"
  enable_pre_call_checks: true    # sprawdza TPM/RPM przed wysłaniem
  retry_after: 5
  num_retries: 3
  allowed_fails: 2
  cooldown_time: 30               # sekundy cooldown po 429
# Uruchom LiteLLM proxy
litellm --config litellm_config.yaml --port 4000

# Test - zapytania automatycznie routowane do dostępnego deploymentu
curl http://localhost:4000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"test"}]}'

D) Przejście na Provisioned Throughput Units (PTU):

Dla stabilnych, przewidywalnych workloadów produkcyjnych - PTU gwarantuje stałą przepustowość bez throttlingu:

# Sprawdź dostępność PTU w regionie
az cognitiveservices model list \
  --location swedencentral \
  --query "[?model.name=='gpt-4o'].model.skus[?name=='ProvisionedManaged'].{name:name, minCapacity:capacity.minimum, maxCapacity:capacity.maximum}" -o json

# Utwórz deployment typu Provisioned (minimalnie 50 PTU)
az cognitiveservices account deployment create \
  --name my-openai-resource \
  --resource-group rg-ai \
  --deployment-name gpt-4o-ptu \
  --model-name gpt-4o \
  --model-version "2024-08-06" \
  --model-format OpenAI \
  --sku-capacity 50 \
  --sku-name "ProvisionedManaged"

# Monitoruj utilization PTU (powinno być <80% dla rezerwy)
az monitor metrics list \
  --resource /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
  --metric "ProvisionedManagedUtilizationV2" \
  --dimension ModelDeploymentName \
  --interval PT1M \
  --start-time $(date -u -d '-60 minutes' +%Y-%m-%dT%H:%M:%SZ) \
  --query "value[0].timeseries[].data[-5:]" -o table

Uwaga kosztowa: PTU to zobowiązanie rezerwacyjne (billing hourly/monthly). 50 PTU dla GPT-4o to ~$2/godz. Opłaca się, gdy zużycie pay-as-you-go przekracza ~$1.5/godz. na danym deploymencie. Szczegóły w kalkulatorze: Azure OpenAI Pricing.

Walidacja

# 1. Wyślij serię zapytań testowych i sprawdź brak 429
for i in $(seq 1 20); do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    https://my-openai-resource.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21 \
    -H "Content-Type: application/json" \
    -H "api-key: $AZURE_OPENAI_KEY" \
    -d '{"messages":[{"role":"user","content":"Say OK"}],"max_tokens":5}')
  echo "Request $i: HTTP $STATUS"
  sleep 0.5
done
# Expected: wszystkie odpowiedzi HTTP 200

# 2. Sprawdź metryki - brak nowych 429 w ostatnich 5 minutach
az monitor metrics list \
  --resource /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
  --metric "AzureOpenAIRequests" \
  --dimension StatusCode \
  --interval PT1M \
  --start-time $(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ) \
  --query "value[0].timeseries[].data[].{time:timeStamp, total:total}" -o table
# Expected: brak wierszy z StatusCode=429

# 3. Skonfiguruj alert na przyszłość
az monitor metrics alert create \
  --name "alert-openai-429-rate-limit" \
  --resource-group rg-ai \
  --scopes /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
  --condition "total AzureOpenAIRequests where StatusCode includes 429 > 10" \
  --window-size 5m \
  --evaluation-frequency 1m \
  --severity 2 \
  --action-group /subscriptions/<sub-id>/resourceGroups/rg-monitoring/providers/Microsoft.Insights/actionGroups/ag-platform-team \
  --description "Azure OpenAI rate limiting - >10 requests z 429 w 5 minut"

Po wdrożeniu rozwiązania zweryfikuj w Application Insights, że success rate wywołań OpenAI wrócił do >99%. Dashboard:

// Application Insights - KQL query do monitorowania 429
dependencies
| where timestamp > ago(1h)
| where target contains "openai.azure.com"
| summarize
    total=count(),
    failed_429=countif(resultCode == "429"),
    success_rate=round(100.0 * countif(resultCode == "200") / count(), 2)
  by bin(timestamp, 5m)
| order by timestamp desc
Nierozwiązany problem 429 w Azure OpenAI bezpośrednio wpływa na doświadczenie użytkowników: chatboty nie odpowiadają, pipeline RAG przerywają przetwarzanie, a automatyzacje generowania treści się zatrzymują. W architekturach bez retry logic pojedynczy nagły skok ruchu może spowodować kaskadowe awarie w mikroserwisach zależnych od LLM. Dla aplikacji produkcyjnych z SLA >99.9% rekomendujemy kombinację rozwiązań B+C (zwiększona quota + load balancing) lub D (PTU) - sam retry (rozwiązanie A) jedynie maskuje problem, nie eliminując go.

 

Jerzy Kopaczewski

Azure OpenAI throttluje Twoje zapytania?

Umów bezpłatną 30-minutową rozmowę. Przeanalizujemy Twoje wzorce ruchu, dobierzemy optymalną strategię quota i wdrożymy architekturę odporną na rate limiting.