Управление надежностью сторонних зависимостей

или Когда чужая ошибка становится вашей проблемой

Наш сервис может быть идеально спроектирован, безупречно написан и тщательно протестирован. Но если платежный или SMS шлюз падают - наш бизнес тоже падает. Если купленный CDN перестает отвечать - наши пользователи видят ошибки. Если облачный провайдер объявляет об инциденте в регионе - наша доступность становится нулевой.

Сторонние зависимости - это дыры в тщательно спроектированной и дорогущей броне нашей системы, которые мы не контролируем.И чем больше у нас таких зависимостей (а в современной архитектуре их десятки и сотни), тем больше наша надёжность зависит от чужой компетентности.

Парадокс зависимостей: почему вы отвечаете за то, что не можете контролировать?

Представьте, что мы управляем рестораном. У нас лучшие повара, идеальная кухня, прекрасный сервис. Но: - Наш поставщик овощей привез тухлые помидоры - Электрическая компания по ошибке отключила электричество нам, а не соседу-должнику - Служба доставки потеряла наш заказ

Для наших гостей совершенно неважно, в чем причина и чья это вина. Для них плохо именно у нас. Так же и с IT-системами: пользователю всё равно, упал наш код или упал канал от нас до провайдера. Он видит ошибку в нашем продукте.

Категории зависимостей и их риски

1. Инфраструктурные зависимости (самые опасные)

  • Облачные провайдеры
  • CDN
  • DNS-провайдеры
  • Сетевые провайдеры

Риск: Полный или частичный отказ целого региона/сервиса. Примеры приводить не буду, их тьма.

2. Сервисные зависимости (самые распространенные)

  • Платежные шлюзы
  • Сервисы коммуникаций
  • Базы данных как сервис
  • Сервисы аутентификации
  • Внешние API

Риск: Отказ критичной бизнес-функции. Пример: Если платежный шлюз падает, мы не можем принимать платежи. Даже если всё остальное работает.

3. Библиотеки и фреймворки (самые коварные)

  • NPM, PyPI, Maven пакеты
  • Опенсорс-проекты
  • Коммерческие SDK

Риск: Баги, уязвимости, несовместимости версий, прекращение предоставление для пользователей из определенных стран. Пример: Log4Shell уязвимость в 2021 году затронула миллионы приложений через популярную библиотеку логирования.

Стратегия SRE: не избегать зависимостей, а управлять рисками

Мы не можем отказаться от зависимостей (это экономически и технически нецелесообразно). Но мы можем системно управлять рисками.

Принцип 1: Измеряй всё, даже то, что не контролируешь

Метрики для каждой зависимости:

// Мониторинг доступности ХХХ API
func monitorStripe() {
    response, err := http.Get("https://api.ххх.com/health")
    latency := time.Since(start)

    // Отправляем метрики в Prometheus
    stripe_availability.Set(1 if ok else 0)
    stripe_latency_seconds.Observe(latency.Seconds())
}

Что измерять: - Доступность (Health checks) - Задержка (Latency) — p50, p95, p99 - Частота ошибок (Error rate) - Лимиты и квоты (Rate limits usage)

Дашборд зависимостей должен быть, даже если у провайдера есть свой status page. Их status page может показывать "всё ок", но конкретно наш аккаунт или регион может иметь проблемы.

Принцип 2: Планируй отказ (Design for Failure)

Техника 1: Circuit Breaker для всех внешних вызовов

// Защита от падения ХХХ
stripeBreaker := gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name: "ХХХ-payments",
    Timeout: 30 * time.Second,
})

func processPayment(amount int) error {
    return stripeBreaker.Execute(func() (interface{}, error) {
        // Вызов ХХХ API
        return ххх.Charge.Create(...)
    })
}

Любой внешний вызов должен быть обёрнут в circuit breaker - это правило.

Техника 2: Graceful Degradation

Вместо "Платежи не работают" → бизнес теряет деньги делаем: "Оплата картой временно недоступна. Вы можете оформить заказ, мы свяжемся с вами для оплаты".

func checkout(cart Cart) (Order, error) {
    // Пытаемся провести оплату
    paymentResult, err := processPayment(cart.Total)

    if err != nil {
        // Если платежный шлюз недоступен
        if isPaymentGatewayDown(err) {
            // Создаем заказ со статусом "ожидает оплаты"
            order := createPendingOrder(cart)
            // Отправляем уведомление менеджеру
            notifyManager("Требуется ручная обработка заказа", order.ID)
            return order, nil // Не возвращаем ошибку пользователю!
        }
        return Order{}, err
    }

    return createConfirmedOrder(cart, paymentResult), nil
}

Техника 3: Кэширование и дублирование данных

  • Кэшируйте ответы внешних API, которые редко меняются (курсы валют, список стран, тарифы доставки, стоимость услуги и т.п.)
  • Храните локальную копию критичных данных (например, последние успешные платежные токены)
  • Используйте stale-while-revalidate подход: отдавайте устаревшие данные, пока обновляете кэш

Принцип 3: Диверсифицируй риски

Мульти-провайдер стратегия (для критичных зависимостей)

Пример для CDN:

func getAsset(userLocation string, assetPath string) ([]byte, error) {
    // Выбираем CDN на основе географии пользователя и здоровья провайдеров
    cdnProvider := selectCDNProvider(userLocation)

    data, err := cdnProvider.Get(assetPath)
    if err != nil && isRetriableError(err) {
        // Пробуем запасного провайдера
        backupProvider := getBackupCDNProvider()
        return backupProvider.Get(assetPath)
    }

    return data, err
}

Где применять: CDN, платежи, SMS и т.п.

Экономика решения: Сравниваем стоимость мульти-провайдера с стоимостью простоя. Если 1 час простоя платежей стоит 500000 руб, а второй провайдер стоит 5000 руб/мес - ROI очевиден.

Мульти-регион (Multi-region)

Даже с одним облачным провайдером можно снизить риски: - Размещать сервисы в нескольких географических регионах - Настраивать автоматическое переключение трафика (DNS failover) - Реплицировать критичные данные между регионами

Принцип 4: Имей план Б (и В, и Г)

Runbook для инцидентов с зависимостями

Для каждой критичной зависимости должен быть документ:

Зависимость: ХХХ Payments
SLO: 99,95% доступности
Мониторинг: https://internal-monitor/xxx-payments
Статус-пейдж: https://status.xxx.com

Действия при нарушении SLO:
1. Проверить статус на status.xxx.com
2. Проверить наши метрики (error rate, latency)
3. Если подтвержден инцидент у XXX:
   - Активировать режим graceful degradation
   - Уведомить поддержку XXX (тикет #12345)
   - Оповестить бизнес о временном отключении онлайн-оплат
   - Включить альтернативный платежный шлюз (YYYPay)
4. Мониторить восстановление

Регулярные учения (Dependency Failure Drills)

Раз в квартал проводите "учения": 1. Имитируйте отказ зависимостей (отключите тестовый доступ к API) 2. Проверьте, как срабатывают circuit breakers 3. Убедитесь, что graceful degradation работает 4. Протестируйте ручные процедуры переключения 5. Обновите документацию по результатам

Принцип 5: Автоматизируй восстановление

Уровень 1: Автоматическое переключение на бекап

# Конфигурация Terraform с альтернативными провайдерами
resource "aws_route53_record" "primary" {
  # Основной CDN
}

resource "aws_route53_record" "failover" {
  # Резервный CDN
  failover_routing_policy {
    type = "SECONDARY"
  }
}

Уровень 2: Автоматическое снижение функциональности

// Автоматическое включение режима read-only при падении базы данных
func handleDBFailure() {
    if isDatabaseDown() {
        enableReadOnlyMode()
        notifyTeam("База данных недоступна, включен режим read-only")
        // Продолжаем обслуживать 80% запросов (чтение)
    }
}

Оценка и выбор зависимостей: due diligence

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

Чек-лист оценки провайдера:

  1. SLA и компенсации: Какие гарантии? Как рассчитываются? Пример: XXX обещает 99,99% для S3, при нарушении 10% кредита.
  2. Статус-пейдж и история: Как часто были инциденты? Насколько прозрачны отчеты?
  3. Архитектура высокодоступности: Multi-AZ? Multi-region? Geo-redundancy?
  4. Лимиты и квоты: Есть ли лимиты запросов? Как быстро можно увеличить?
  5. Поддержка: SLA на ответ поддержки? Есть ли выделенный менеджер?
  6. Контрактные обязательства: Можно ли вывезти данные? Какие penalties при прекращении?

Технический аудит:

  • Тестируйте в проде (аккуратно): Канареечные тесты с реальным трафиком
  • Нагрузочное тестирование: Проверяйте лимиты до достижения
  • Chaos-тесты: Имитируйте проблемы сети, задержки, частичные отказы

Юридические и бизнес-аспекты

Контрактное покрытие (Contractual Coverage):

  • Включайте в договоры с провайдерами пункты о взаимной ответственности
  • Требуйте финансовых компенсаций за нарушение SLA
  • Оговаривайте процедуры эскалации и временные рамки

Страхование бизнес-рисков:

Некоторые компании страхуют бизнес от простоя, вызванного сбоями провайдеров.

Метрики успеха

Как понять, что вы хорошо управляете зависимостями?

  1. MTTR для инцидентов с зависимостями должен снижаться
  2. Количество инцидентов, где зависимость являлась root cause должно снижаться
  3. Coverage мониторинга (% зависимостей с настроенным мониторингом)
  4. Coverage graceful degradation (% зависимостей с реализованными fallback-ами)
  5. Время простоя из-за зависимостей / Общее время простоя должно быть небольшим и снижаться

Культурный аспект: от обвинений к партнерству

Плохой подход: "Опять этот *** XXX упал! Надо их закидать тикетами и пожаловаться их CPO и СЕО!" Хороший подход: "XXX - наш партнер. У них случился инцидент. Как мы можем помочь им быстрее восстановиться? Как мы можем улучшить нашу устойчивость к таким инцидентам?"

Практики: - Совместные постмортемы с провайдерами - Регулярные архитектурные обзоры - Программы бета-тестирования новых функций устойчивости

Матрица ответственности

Наша задача как SRE - построить матрицу управления зависимостями:

Зависимость Критичность SLO провайдера Наш SLO Мониторинг Circuit Breaker Graceful Degradation Fallback Runbook
XXX Высокая 99.95% 99.9% YYY
SendGrid Средняя 99.9% 99% SMTP сервер ⚠️
Yandex Maps API Низкая 99.9% 95% Кэш

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

Управление зависимостями это не про паранойю. Это про здоровый инженерный прагматизм: признать, что мир неидеален, и построить систему, которая продолжает работать даже когда этот мир подводит.