Таймауты. Без таймаута зависший ответ навсегда занимает поток, соединение и память — так медленная зависимость останавливает весь сервис. Нужны раздельно: на установку соединения (короткий) и на ответ. Значение — от измеренного распределения задержек зависимости (около p99.9 с запасом), а не круглое «30 секунд». И таймаут на всю операцию целиком, а не только на одну попытку.
Повторы спасают от кратковременных сбоев (перезапуск экземпляра, потеря пакета), но опасны:
- повторять можно только идемпотентные операции — или делать их идемпотентными ключом идемпотентности;
- что повторять: таймауты, обрыв соединения, 502/503/504, 429 (с учётом
Retry-After); не повторять 400, 401, 403, 404, 409; - экспоненциальная отсрочка — пауза 100 мс, 200, 400… с потолком;
- джиттер — случайная пауза в пределах отсрочки (full jitter:
random(0, base·2^n)). Без него тысячи клиентов после сбоя повторяют синхронно волнами и снова кладут сервис; - ограничение числа попыток — 2–3.
Шторм повторов. Если каждый из трёх уровней цепочки делает 3 попытки, нижний сервис при сбое получает до 27 запросов на один пользовательский. Повторы надо делать на одном уровне (обычно ближе к краю) или ограничивать бюджетом: повторов не больше 10% от успешных запросов за окно (gRPC retry throttling, Finagle). Когда зависимость лежит, бюджет кончается, и повторы прекращаются сами. Circuit breaker решает ту же задачу резче — перестаёт звать зависимость на время.
Дедлайны. Вместо независимых таймаутов на каждом уровне — абсолютный срок, передаваемый вниз (gRPC deadline, заголовок). Нижний сервис видит, что времени осталось 50 мс, и не начинает работу на 200 мс; верхний не ждёт дольше своего срока. Иначе нижние продолжают считать то, что верхний уже выбросил.
Hedging — разновидность повтора для хвостов: копия запроса после p95 без ожидания ошибки, только для чтения.