Прямой ответ
Грациозная деградация — это способность системы отдавать полезный ответ при отказе части зависимостей. Экран товара без блока рекомендаций полезнее, чем страница с ошибкой 500, потому что пользователь можетвыполнить основное действие.
Основа подхода — разделение зависимостей на две группы:
| Группа | Пример | Поведение при отказе |
|---|---|---|
| Обязательные | База заказов, аутентификация | Ошибка, запрос не выполняется |
| Необязательные | Рекомендации, персонализация, счётчики | Блок пуст или показан запасной вариант |
Классификация выполняется по вопросу: «может ли пользователь выполнить основную задачу без этих данных». Если да — зависимость необязательна.
Реализация на уровне композиции:
@router.get("/products/{product_id}")
async def product_page(product_id: str):
product = await catalog.get(product_id) # обязательная: ошибка пробрасывается
results = await asyncio.gather(
pricing.get(product_id),
recommendations.similar(product_id),
reviews.summary(product_id),
return_exceptions=True, # необязательные: сбой не прерывает
)
price, similar, rating = results
return {
"product": product,
"price": None if isinstance(price, Exception) else price,
"similar": [] if isinstance(similar, Exception) else similar,
"rating": None if isinstance(rating, Exception) else rating,
"degraded": [n for n, r in
zip(("price", "similar", "rating"), results)
if isinstance(r, Exception)],
}
Поле degraded сообщает клиенту, какие блоки не заполнены. Клиент показывает заглушку или скрывает блок, а не отображает пустоту как ошибку данных.
Три уровня запасного поведения:
- Пустой блок. Самый простой вариант; подходит для рекомендаций и похожих товаров.
- Запасные данные из кэша. Устаревшая цена лучше отсутствия цены, если срок устаревания известен.
- Статический запасной вариант. Список популярных товаров вместо персональных рекомендаций.
Практика: как проектировать деградацию
1. Определяйте критичность до инцидента. Список зависимостей с пометкой «обязательная/необязательная» должен лежать в репозитории. Во время аварии классификацию придумывать поздно.
2. Изолируйте отказы. Необязательная зависимость не должна исчерпывать общие ресурсы. Отдельный пул соединений или ограничение числа одновременных запросов предотвращает каскадный сбой; о паттерне — в статье про bulkhead.
3. Сочетайте с circuit breaker. Постоянные попытки обращения к упавшему сервису тратят время и ресурсы. Автоматическое размыкание цепи возвращает запасной ответ сразу; о настройке — в статье про circuit breaker.
4. Задавайте короткий таймаут для необязательных вызовов. Если основной ответ обязан прийти за секунду, рекомендациям стоит дать 200 миллисекунд: лучше пустой блок, чем медленный ответ.
5. Помечайте устаревшие данные. Запасное значение из кэша должно нести возраст, иначе пользователь принимает устаревшую цену за актуальную.
6. Измеряйте долю деградированных ответов. Метрика показывает, как часто пользователи видят неполные данные; рост — ранний сигнал о проблеме.
7. Не деградируйте безопасность. Аутентификация и проверка прав остаются обязательными всегда. Деградация, открывающая доступ без проверки, хуже отказа.
8. Фиксируйте запасной ответ в коде, а не в конфигурации. Запасной вариант, который нужно собрать вручную во время инцидента, не будет применён. Он должен срабатывать автоматически по факту отказа.
9. Проверяйте деградацию в тестах. Сценарий «зависимость недоступна» должен быть покрыт тестом так же, как успешный путь. Без теста поведение при отказе определяется случайно.
Рекомендации:
- Начинайте с явного перечня зависимостей. Диаграмма «сервис → зависимости» с пометкой критичности выявляет неочевидные обязательные связи.
- Тестируйте отказ каждой зависимости. Проверка выполняется отключением, а не обзором кода; о нагрузочных сценариях — в статье про Locust.
- Согласуйте контракт частичного ответа. Поля, которые могут отсутствовать, должны быть опциональными в схеме с первого дня.
- Логируйте причину деградации. Пользователь видит пустой блок; разработчику нужна причина в логе с идентификатором запроса.
- Не превращайте деградацию в норму. Постоянно деградированный ответ означает, что зависимость либо нужно починить, либо убрать.
Практический сценарий: сбой сервиса рекомендаций уронил карточку товара
Контекст: при недоступности сервиса рекомендаций карточка товара возвращала ошибку 500. Основной сценарий — добавление в корзину — стал недоступен.
- Диагностика. В обработчике вызовы шли последовательно без перехвата исключений. Отказ рекомендаций прерывал обработку до формирования ответа.
- Проверка критичности. Рекомендации не влияют на возможность купить товар, то есть зависимость необязательная. Классификация нигде не была зафиксирована.
- Действия. (а) Перевели необязательные вызовы на параллельное выполнение с
return_exceptions=True; (б) задали таймаут 200 мс для рекомендаций и отзывов; (в) добавили запасной вариант — список самых продаваемых товаров из кэша с возрастом до часа; (г) ввели полеdegradedв ответе и метрику доли деградированных страниц; (д) зафиксировали перечень зависимостей с классификацией в репозитории; (е) добавили тест, имитирующий отказ рекомендаций и ожидающий успешный ответ. - Результат. При повторном сбое рекомендаций карточка товара показывала запасной блок; конверсия не изменилась, а инцидент перестал затрагивать основной сценарий.
Вывод: отказ необязательной зависимости не должен влиять на основную задачу. Классификация зависимостей и явная обработка частичного отказа стоят меньше одного инцидента с потерей выручки.
FAQ
Чем грациозная деградация отличается от отказоустойчивости? Отказоустойчивость стремится сохранить полный результат, например через резервирование. Деградация принимает частичный результат как приемлемый, когда сохранить полный невозможно.
Что делать, если зависимость обязательна, но часто падает? Искать способ сделать её необязательной: кэшировать, хранить локальную копию, переносить проверку на более ранний этап. Если это невозможно, нужна резервная реализация.
Как показать пользователю, что данные неполные? Явной заглушкой и при необходимости текстом. Молчаливое отсутствие блока воспринимается как ошибка данных, а не как временное ограничение.
Стоит ли деградировать запись данных? Обычно нет. Запись должна либо выполниться, либо вернуть явную ошибку; молчаливая потеря записи хуже отказа.
Источники: Azure Architecture Center (circuit breaker, bulkhead), Google SRE Book — ссылки в шапке.