Обработка ошибок 429 и 503 в Amazon Bedrock: стратегии ретраев | AiManual
AiManual Logo Ai / Manual.
11 Фев 2026 Гайд

Когда Bedrock говорит «нет»: как не сломать продакшн при ошибках 429 и 503

Практическое руководство по обработке ThrottlingException и ServiceUnavailableException в Amazon Bedrock на Python с boto3. Стратегии ретраев, экспоненциальные

Почему ваше приложение падает в 3 часа ночи

Вы развернули AI-сервис на Amazon Bedrock. Он работает прекрасно две недели. Потом в 3:17 ночи приложение падает. В логах - ThrottlingException. Клиенты недовольны. Вы пытаетесь понять, что случилось.

Оказывается, ваш бот начал обрабатывать ночные запросы от азиатских пользователей. Bedrock ограничил количество запросов. Приложение не умеет обрабатывать ошибки 429. Все сломалось.

Та же история с 503 ошибкой (ServiceUnavailableException). AWS проводит технические работы. Ваше приложение думает, что сервис умер навсегда. Паника.

Важно: Bedrock - управляемый сервис. Вы не контролируете его инфраструктуру. Но вы контролируете, как ваше приложение реагирует на его проблемы.

Две ошибки, которые разрушат ваш день

Давайте разберемся, что означают эти ошибки и почему они появляются.

Ошибка Код HTTP Исключение в boto3 Причина
Rate limiting 429 ThrottlingException Превышен лимит запросов
Service unavailable 503 ServiceUnavailableException Сервис временно недоступен

ThrottlingException - это не ошибка. Это механизм защиты. Bedrock говорит: "Слишком быстро, притормози". Если вы игнорируете это сообщение и продолжаете слать запросы, AWS может заблокировать ваш аккаунт.

ServiceUnavailableException - это временная проблема. Может длиться секунды. Может минуты. В глобальном выводе моделей такие ошибки случаются чаще, чем вы думаете.

Как НЕ надо обрабатывать ошибки

Посмотрите на этот код. Я видел его в десятках проектов:

import boto3
import json

client = boto3.client('bedrock-runtime')

def generate_text(prompt):
    try:
        response = client.invoke_model(
            modelId='anthropic.claude-3-5-sonnet-20241022',
            contentType='application/json',
            body=json.dumps({
                "prompt": prompt,
                "max_tokens": 1000
            })
        )
        return json.loads(response['body'].read())
    except Exception as e:
        print(f"Error: {e}")
        return None

Что здесь не так? Все.

  1. Одна попытка и сдался
  2. Нет задержки между ретраями
  3. Нет дифференциации ошибок
  4. Нет ограничения количества попыток

Этот код сломается при первой же 429 ошибке. В продакшене такое недопустимо.

Правильная стратегия ретраев

Хорошая обработка ошибок строится на трех принципах:

  • Экспоненциальная задержка (exponential backoff)
  • Джокер (jitter - случайная добавка)
  • Разные стратегии для разных ошибок

1 Базовый ретрай с экспоненциальной задержкой

Вот как это должно выглядеть:

import boto3
import json
import time
import random
from botocore.exceptions import ClientError

class BedrockClientWithRetry:
    def __init__(self, max_retries=5, base_delay=1):
        self.client = boto3.client('bedrock-runtime')
        self.max_retries = max_retries
        self.base_delay = base_delay
        
    def invoke_with_retry(self, model_id, body):
        last_exception = None
        
        for attempt in range(self.max_retries):
            try:
                response = self.client.invoke_model(
                    modelId=model_id,
                    contentType='application/json',
                    body=json.dumps(body)
                )
                return json.loads(response['body'].read())
                
            except ClientError as e:
                last_exception = e
                
                # Проверяем тип ошибки
                error_code = e.response['Error']['Code']
                
                if error_code == 'ThrottlingException':
                    # Для 429 ошибки используем экспоненциальную задержку
                    delay = self.base_delay * (2 ** attempt)
                    # Добавляем случайность (jitter)
                    jitter = random.uniform(0, 0.1 * delay)
                    total_delay = delay + jitter
                    
                    print(f"Throttling detected. Retry {attempt + 1}/{self.max_retries} in {total_delay:.2f}s")
                    time.sleep(total_delay)
                    
                elif error_code == 'ServiceUnavailableException':
                    # Для 503 ошибки - фиксированная задержка
                    delay = 5  # секунд
                    print(f"Service unavailable. Retry {attempt + 1}/{self.max_retries} in {delay}s")
                    time.sleep(delay)
                    
                else:
                    # Другие ошибки не ретраим
                    raise
                    
        # Если все попытки исчерпаны
        raise last_exception

Почему экспоненциальная задержка? Потому что она:

  • Дает сервису время восстановиться
  • Предотвращает лавину запросов при восстановлении
  • Снижает нагрузку на ваше приложение
💡
Jitter (случайная добавка) критически важен в распределенных системах. Без него все клиенты пытаются переподключиться одновременно, создавая эффект "толпы".

2 Умные ретраи с circuit breaker

Экспоненциальные задержки - это хорошо, но недостаточно. Нужен circuit breaker (предохранитель).

Circuit breaker отслеживает количество ошибок. Если их слишком много, он "разрывает цепь" и временно блокирует запросы. Это защищает и ваш сервис, и Bedrock.

class CircuitBreaker:
    def __init__(self, failure_threshold=5, reset_timeout=60):
        self.failure_threshold = failure_threshold
        self.reset_timeout = reset_timeout
        self.failure_count = 0
        self.last_failure_time = None
        self.state = "CLOSED"  # CLOSED, OPEN, HALF_OPEN
        
    def can_execute(self):
        if self.state == "OPEN":
            # Проверяем, не истек ли таймаут
            if time.time() - self.last_failure_time > self.reset_timeout:
                self.state = "HALF_OPEN"
                return True
            return False
        return True
        
    def on_success(self):
        if self.state == "HALF_OPEN":
            self.state = "CLOSED"
        self.failure_count = 0
        
    def on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.failure_count >= self.failure_threshold:
            self.state = "OPEN"
            print(f"Circuit breaker OPENED. Waiting {self.reset_timeout}s")

class ResilientBedrockClient:
    def __init__(self):
        self.client = BedrockClientWithRetry()
        self.circuit_breaker = CircuitBreaker()
        
    def generate(self, prompt):
        if not self.circuit_breaker.can_execute():
            raise Exception("Circuit breaker is OPEN")
            
        try:
            result = self.client.invoke_with_retry(
                model_id='anthropic.claude-3-5-sonnet-20241022',
                body={"prompt": prompt, "max_tokens": 1000}
            )
            self.circuit_breaker.on_success()
            return result
            
        except Exception as e:
            self.circuit_breaker.on_failure()
            raise

Что делать, когда все падает

Иногда ретраи не помогают. Bedrock недоступен. Что тогда?

У вас должен быть план Б. Несколько вариантов:

  1. Фолбэк на другую модель. Claude недоступен? Попробуйте Titan или Jurassic.
  2. Фолбэк на другой регион. Используйте глобальную архитектуру с репликацией в нескольких регионах.
  3. Кеширование ответов. Для частых запросов сохраняйте ответы в Redis.
  4. Деградация функциональности. Вместо сложного анализа покажите простой ответ.
class FallbackBedrockClient:
    def __init__(self):
        self.primary_model = 'anthropic.claude-3-5-sonnet-20241022'
        self.fallback_models = [
            'amazon.titan-text-premier-v1:0',
            'ai21.j2-ultra-v1'
        ]
        self.current_model_index = 0
        
    def generate_with_fallback(self, prompt):
        models_to_try = [self.primary_model] + self.fallback_models
        
        for model_id in models_to_try:
            try:
                client = BedrockClientWithRetry(max_retries=3)
                return client.invoke_with_retry(model_id, {"prompt": prompt})
                
            except Exception as e:
                print(f"Model {model_id} failed: {e}")
                continue
                
        # Все модели упали
        return {"completion": "Извините, сервис временно недоступен. Попробуйте позже."}

Мониторинг и алерты

Обработка ошибок - это не только код. Это еще и мониторинг.

Что нужно мониторить:

  • Количество 429 ошибок в минуту
  • Количество 503 ошибок
  • Среднее время ответа
  • Процент успешных запросов (SLA)
  • Состояние circuit breaker

Внимание: Если у вас больше 1% 429 ошибок, вы делаете что-то не так. Лишком много запросов. Нужно оптимизировать.

Настройте CloudWatch алерты:

{
  "AlarmName": "Bedrock-Throttling-High",
  "MetricName": "ThrottledRequests",
  "Namespace": "AWS/Bedrock",
  "Statistic": "Sum",
  "Period": 300,
  "EvaluationPeriods": 2,
  "Threshold": 10,
  "ComparisonOperator": "GreaterThanThreshold"
}

Частые ошибки и как их избежать

Ошибка 1: Бесконечные ретраи

Без ограничения попыток приложение может пытаться бесконечно. Установите разумный лимит (3-5 попыток).

Ошибка 2: Отсутствие jitter

Все инстансы вашего приложения падают одновременно и восстанавливаются одновременно. Добавьте случайность.

Ошибка 3: Игнорирование заголовков retry-after

Bedrock иногда возвращает заголовок Retry-After. Используйте его!

def get_retry_delay_from_response(response):
    if 'Retry-After' in response.headers:
        try:
            return int(response.headers['Retry-After'])
        except:
            pass
    return None

Ошибка 4: Одинаковая стратегия для всех ошибок

429 и 503 требуют разных подходов. 429 - экспоненциальная задержка. 503 - фиксированная пауза.

Когда это особенно важно

Есть сценарии, где ошибки Bedrock особенно критичны:

  • Мультиагентные системы. Каждый агент делает запросы. Ошибка одного ломает всю цепочку. Читайте про продвинутые техники для мультиагентных систем.
  • RAG-системы. Поиск + генерация. Если Bedrock падает, вся система бесполезна. Узнайте о проблемах RAG-систем.
  • Корпоративные чат-боты. Тысячи пользователей. SLA 99.9%. Без правильной обработки ошибок не достичь.

Что изменилось в 2025-2026

Bedrock постоянно развивается. На 11 февраля 2026 года:

  • Новые модели имеют более высокие лимиты запросов
  • Появилась встроенная поддержка ретраев в некоторых SDK
  • Улучшен мониторинг и алертинг
  • Но основные принципы обработки ошибок не изменились

Последние версии моделей (актуально на 11.02.2026):

  • anthropic.claude-3-5-sonnet-20250220 (новая версия)
  • amazon.titan-text-premier-v2:0
  • meta.llama-3-3-70b-instruct-v2:0
  • cohere.command-r-plus-20250206
💡
Новые модели часто имеют разные лимиты rate limiting. Проверяйте документацию для каждой модели. Claude 3.5 Sonnet может иметь другие ограничения, чем Titan.

Финальный совет

Тестируйте обработку ошибок. Специально вызывайте 429 и 503 в тестовой среде. Используйте инструменты вроде AWS Fault Injection Simulator.

Помните: Bedrock - это чужой сервис. Вы не контролируете его доступность. Но вы контролируете, как ваше приложение ведет себя, когда Bedrock недоступен.

Хорошая обработка ошибок превращает "катастрофу" в "временную проблему". Плохая обработка ошибок превращает временную проблему в катастрофу.

И последнее: не пытайтесь обойти rate limiting. Если Bedrock говорит "стоп", он это серьезно. Лучше оптимизируйте ваши запросы. Используйте эффективное управление контекстом, кеширование, батчинг. Это дешевле, чем разбираться с заблокированным аккаунтом.