Устойчивость и латентность
Сервис, который летает на пустом стенде, в проде живёт в другом мире: запросы разлетаются на десятки зависимостей, какая-то из них тормозит или ложится, нагрузка скачет, и вопрос не в том, случится ли частичный отказ, а в том, превратит ли его сервис в управляемую деградацию или в обвал. Эта тема — про приёмы, которые держат Go-сервис быстрым и стабильным именно в этих условиях: как мерить латентность, как ограничивать время вызовов, как повторять и когда переставать звонить в мёртвую зависимость, как изолировать ресурсы и как сбрасывать лишнее, не падая целиком.
Главная ловушка темы — оптимистичное мышление: «среднее в норме, таймаут поставим потом, на ошибке просто повторим». Среднее прячет хвост, в который под fan-out попадает значимая доля пользователей; отсутствие таймаута и распространения дедлайна копит зависшие горутины; наивный ретрай без backoff и jitter добивает восстанавливающуюся зависимость синхронным штормом. Дальше всё решает дисциплина отказа: circuit breaker, который перестаёт звонить в мёртвую зависимость и даёт ей подняться; bulkhead, изолирующий ресурсы, чтобы одна зависимость не утопила всё; backpressure и сброс нагрузки, которые тормозят источник и отдают 503/429 быстро вместо тихого OOM; и наконец hedged-запросы и слияние запросов, срезающие хвост и дедуплицирующие одинаковые вызовы. Тема разобрана по слоям — от измерения латентности до защиты горячего ключа.
Карта темы
- Латентность и перцентили — латентность против пропускной способности и конкурентности (закон Литтла), измерение перцентилей p50/p95/p99/p999 вместо среднего и доминирование хвоста под fan-out.
- Таймауты и дедлайны — каждый исходящий вызов ограничен таймаутом, а дедлайн в
context.Contextраспространяется вниз по цепочке, и каждый хоп вычитает своё время, не сбрасывая бюджет. - Ретраи, backoff и jitter — повторять только идемпотентное на временной ошибке, с экспоненциальным backoff и jitter против синхронного шторма, ограничивая бюджетом ретраев.
- Circuit breaker — автомат closed → open → half-open, который при росте доли ошибок размыкается и мгновенно отказывает локально, периодически пробуя восстановление зависимости.
- Bulkhead — изоляция ресурсов по каждой зависимости (отдельные пулы горутин и соединений, ограниченные очереди), чтобы одна засбоившая зависимость не выела все ресурсы сервиса.
- Backpressure — при перегрузе сигнал «притормози» вверх по течению вместо безграничного буфера: ограниченные каналы и очереди гасят всплески, а на заполнении блокируют или отбрасывают.
- Сброс нагрузки — под перегрузом осознанно отбрасывать лишнюю и менее важную работу ради ключевого пути, отдавая
503/429быстро, пока система ещё не рухнула. - Hedged-запросы — подождав около p95, отправить вторую копию запроса другой реплике и взять первый ответ, срезая хвост ценой ограниченной доли дополнительной нагрузки.
- Слияние запросов — схлопывать одновременные одинаковые in-flight запросы в один вызов (
singleflight), защищая горячий ключ от cache stampede и thundering herd.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Мерить латентность средним | Низкое среднее прячет хвост, в который попадает значимая доля пользователей |
| Игнорировать хвост под fan-out | Одна редкая медленная зависимость делает медленным p99 всему запросу |
| Не ставить таймаут на исходящий вызов | Зависший вызов держит горутину, коннект и память, пока сервис не утонет |
| Сбрасывать дедлайн на каждом хопе вместо распространения | Цепочка работает дольше бюджета клиента, который уже ушёл |
| Повторять неидемпотентную операцию | Ретрай платежа списывает деньги дважды |
| Ретраить без backoff и jitter | Синхронный шторм ретраев добивает восстанавливающуюся зависимость |
| Звонить в уже легшую зависимость | Каскадный отказ: вызовы копятся по таймауту и не дают ей подняться |
| Не изолировать ресурсы по зависимостям | Одна засбоившая зависимость выедает все горутины и коннекты — падает весь сервис |
| Копить входящие в неограниченный буфер | Память течёт до OOM вместо сигнала «притормози» вверх по течению |
| Тонуть под перегрузом, обслуживая всех | Медленная смерть всего сервиса вместо быстрого 503/429 лишним запросам |
| Слать N одинаковых запросов на горячий ключ | Thundering herd / cache stampede кладёт зависимость под one-hit нагрузкой |
Значение для собеседований
Устойчивость и латентность — стержень senior-части Go-интервью по system design: проверяют не знание названий паттернов, а понимание, как сервис ведёт себя под нагрузкой и при частичном отказе. Интервьюер смотрит, меряете ли вы перцентили, а не среднее, помните ли про доминирование хвоста под fan-out, ставите ли таймаут на каждый исходящий вызов и распространяете ли дедлайн, отличаете ли идемпотентный ретрай от опасного и знаете ли, когда перестать звонить в мёртвую зависимость, как изолировать ресурсы и как деградировать управляемо вместо обвала.
Что обычно проверяют:
- Чем латентность отличается от пропускной способности и конкурентности и почему меряют перцентили p50/p95/p99/p999, а не среднее.
- Зачем нужен таймаут на каждом исходящем вызове и как распространять дедлайн через
context.Contextвниз по цепочке. - Что можно ретраить, почему нужен экспоненциальный backoff с jitter и зачем бюджет ретраев против амплификации.
- Как работает circuit breaker (closed → open → half-open) и почему fail fast останавливает каскадный отказ.
- Чем bulkhead изолирует ресурсы и почему без него одна зависимость топит весь сервис.
- Чем backpressure отличается от сброса нагрузки и почему буфер нельзя растить без границ.
- Как hedged-запросы и слияние запросов срезают хвост и защищают горячий ключ и чем за это платят.
Типичный неверный ответ: «на ошибке просто повторим, а среднее у нас в норме». Это запускает разбор того, что среднее прячет хвост, а под fan-out именно хвост определяет p99; что ретрай без backoff и jitter добивает восстанавливающуюся зависимость, а неидемпотентный ретрай списывает деньги дважды; что без таймаута и circuit breaker вызовы в мёртвую зависимость копятся и дают каскадный отказ; и что под перегрузом нужно осознанно сбрасывать лишнее и отдавать 503/429 быстро, а не тонуть, обслуживая всех.