Архитектура низкой задержки
Зачем нужна архитектура низкой задержки
Низкая задержка — это не только «быстрое среднее», а стабильные хвосты (p95/p99) при реальной нагрузке. Путь к этому — бюджет задержки, дисциплина очередей/ретраев, близость данных и кэшей, правильные протоколы/коннекты и строгая эксплуатация (лимиты, наблюдаемость, деградация).
Цели и бюджет задержки
1. Определите SLO: «p95 ≤ 120 мс, p99 ≤ 250 мс, ошибка ≤ 0.3%».
2. Соберите бюджет: клиент → edge → регион → сервисы → сторы → ответ.
- Клиент–edge: 15 мс
- Edge–регион: 15 мс
- Gateway/L7: 10 мс
- Бизнес-сервис: 40 мс
- Хранилище/кэш: 25 мс
- Запас/джиттер: 15 мс
Метрики и хвосты
Мерите p50/p90/p95/p99, сквозную и на каждом хопе.
Разбивайте по лейблам: регион, метод, версия клиента, тип сети (мобайл/бродбенд), размер payload.
Различайте время очереди и время исполнения (см. Little’s Law: L = λ·W).
Tail-чувствительные техники: hedged requests (редко и с защитой), запрет каскадных ретраев.
Сеть и протоколы
QUIC/HTTP/3: меньше потерь на мобильных/роуминге, мультиплексирование без head-of-line.
TLS 1.3 и 0-RTT (только для безопасных идемпотентных запросов).
DNS: короткий TTL для динамических маршрутов, Anycast для POP.
TCP: `TCP_NODELAY` (осмотрительно), отключение лишних `Nagle`/`Delayed ACK` там, где оправдано; keep-alive и быстрое восстановление соединений.
gRPC/HTTP/2: мультиплекс, flow-control и настройки окон; избегайте чрезмерной компрессии на маленьких payload.
Соединения и пулы
Разделяйте пулы по доменам/предназначению (чтобы «медленные соседи» не отнимали слоты).
Warm-up/Keep-alive: поддерживайте устойчивое число теплых коннектов.
Connection coalescing (HTTP/2/3) и reuse.
Тайм-ауты: `connect`, `TLS handshake`, `request`, `idle`. Разные значения на разных хопах.
Локальность данных и вычислений
Edge/регион: выносите чтения и легкие вычисления ближе к пользователю (см. «Edge-узлы и региональная логика»).
Read-local / Write-global: реплики для чтения, глобальная истина для записи.
Кэш-иерархия: CDN/edge-кэш → региональный KV/Redis → сервисный кэш → локальный in-proc.
Прогрев (warming): загрузка горячих ключей при релизе/масштабировании.
Stale-while-revalidate для невысокорисковых данных.
Хранилища и индексы
Выбирайте схемы доступа O(1)/O(logN); держите узкие индексы под частые запросы.
Hot-keys: шардируйте по `hash(id)` или добавляйте «соль» для равномерности.
Batching на выходе в БД/кеш (до разумных размеров) вместо десятков одиночных вызовов.
Для OLTP — максимально короткие транзакции; read-committed/snapshot вместо серийных блокировок.
Конкурентность и безблокировочные приемы
Сначала устраните ожидания на очередях, потом оптимизируйте CPU.
Async I/O и неблокирующие драйверы; структуры lock-free там, где уместно.
Избегайте глобальных мьютексов; granular-локи, CAS/версионирование.
Пулы потоков: фиксируйте размеры, чтобы не упереться в контекст-свитчи.
NUMA-осознанность: привязка потоков к сокетам, локальные аллокаторы.
JVM/GC и рантайм-тюнинг (если применимо)
Генерация кода и аллокации: меньше боковых эффектов → меньше GC пауз.
Современные коллекторы (G1/ZGC/Shenandoah) с целевыми паузами; escapes и аренды буферов.
Class/Data sharing, JIT warming, AOT/native-image для стартам-зависимых функций.
Гистограммы пауз GC включайте в общий бюджет задержки.
Очереди, backpressure, защита от перегрузки
Размер очередей = маленький: длинные очереди дают «красивое p50» и убивают p99.
Явный backpressure: отвечайте «медленнее», нежели копите.
Adaptive concurrency: снижайте параллельность при росте ошибок/латентности (VEGAS/gradient алгоритмы, AIMD).
Circuit breaker: быстрые отказы при деградации апстрима, bulkhead (кают-компании) на пулы и ресурсы.
Rate limit: скользящее окно/токены, приоритизация (user tier/critical-path).
Ретраи, хеджинг и идемпотентность
Ретраи только на транзиентные ошибки, с джиттером и максимумом попыток.
Идемпотентные операции и `Idempotency-Key` — обязательны для повторов.
Hedged requests: отправляйте дубли после порога (например, p95 + 10 мс) и всегда отменяйте лишний.
Никогда не ретрайте внутри каждого слоя без координации — получите шторма.
Кэширование и прогрев
Горячий путь должен обходиться без сети при типовой нагрузке (in-proc/LRU).
Negative cache на 10–60 с, чтобы не долбить отсутствующие ключи.
Массовый прогрев при релизе/скейлинге: списки горячих ключей, read-ahead, background refresh.
Деградация и фоллбеки
Graceful Degradation: урезайте второстепенные фичи при росте латентности (менее детализированный ответ, отключение обогащений).
Soft timeouts: возвращайте базовый ответ/кеш вместо 5xx.
Fail-open/Fail-closed — явно задокументируйте для каждого вызова.
Наблюдаемость и профилирование
Дистрибутивный трейсинг: спаны на каждом хопе, сэмплинг хвостов (tail-based).
RED/USE метрики: Rate, Errors, Duration / Utilization, Saturation, Errors.
Top-N «медленных» маршрутов ежедневно.
Профилировщики (alloc/cpu/lock) в проде с низким оверхедом (eBPF/async-profiler/Flight Recorder).
Синтетика с разных ASN/сетей и мобильных каналов.
Тестирование производительности
Latency-SLO тесты (p95/p99) с реальными payload и вариативностью.
Chaos-сценарии: деградация DNS, рост потерь пакетов, задержки TLS, «медленный» стор.
Cold-start/scale-up: замерьте первые минуты после релиза, когда кэши пустые.
Нагрузочные пулы разделяйте по сценариям (не мешайте read/write тесты).
Мини-шаблоны
Политика таймаутов/ретраев (псевдо)
yaml timeouts:
connect: 100ms tls_handshake: 150ms request_p95_budget: 80ms retries:
max_attempts: 2 backoff: exp_jitter(10ms..60ms)
retry_on: [CONNECT_ERROR, TIMEOUT, 502, 503, 504]
hedging:
enabled: true threshold: p95 + 10ms cancel_extra_on_first_success: true circuit_breaker:
error_rate_threshold: 5%
p95_threshold_increase: 30%
half_open_after: 10s
Пулы и bulkhead’ы
yaml pools:
checkout:
max_conns: 256 per_host: 64 queue: 8 # small analytics queue:
max_conns: 64 queue: 4
Ответ с деградацией
json
{
"status": "ok",
"profile": { "id": "u123", "name": "…"},
"recommendations": "degraded, "//disabled the heavy part
"served_from": "edge-cache",
"trace_id": "…"
}
Кейсы применения
iGaming/финансы: авторизация платежа < 200 мс p95, лимиты/баланс — чтение из региональных проекций, записи — идемпотентные с версией.
Маркетинг/рекомендации: ответы < 100 мс p95, кэш фич-флагов на edge, модели — предварительный скоринг + быстрые правила на горячем пути.
Мобильные клиенты: HTTP/3, агрессивный reuse коннектов, уменьшенные payload (Protobuf), защитные таймауты и offline-кэш.
Анти-паттерны
Длинные очереди перед воркерами: «красивое среднее» и убитый p99.
Каскадные ретраи на каждом слое без координации.
Глобальный «мега-кэш» без инвалидации и прогрева.
Нечеткие таймауты (везде «по умолчанию») — неконтролируемые хвосты.
Один общий пул коннектов для всего трафика — head-of-line блокировки.
Тяжелая логика на edge со stateful-эффектами.
Отключенная телеметрия хвостов — вы «не видите» p99.
Чек-лист продакшена
- Есть бюджет задержки по хопам и таймауты под него.
- Включены HTTP/2/3, TLS 1.3, пулы коннектов и warm-up.
- Иерархия кэшей, список горячих ключей и стратегии прогрева.
- Read-local/Write-global и шардирование горячих ключей.
- Явный backpressure, маленькие очереди, circuit-breakers и bulkhead’ы.
- Ретраи с джиттером, идемпотентность, ограниченный хеджинг.
- Трейсинг с метками региона/версии/клиента; мониторинг p95/p99.
- Perf-тесты с синтетикой по ASN/мобайлу, сценарии cold-start и chaos.
- Процедуры деградации и фоллбеков задокументированы.
- p95/p99 соответствуют SLO на реальной нагрузке.
FAQ
Почему p99 важнее среднего?
Потому что пользователи сталкиваются с хвостами, а не со средним. p99 показывает, «сколько реально болит».
Стоит ли включать хеджинг везде?
Нет. Он полезен для редких хвостов в критичных путях и только при строгих лимитах/идемпотентности.
Как уменьшить холодный старт?
Прогрев кэшей/соединений, предварительная компиляция/JIT-прогрев, минимизация lazy-инициализаций, warm-пулы.
Можно ли «победить сеть»?
Частично: HTTP/3, edge-POP, Anycast, компактные payload, connection reuse и разумные таймауты.
Итог
Архитектура низкой задержки — это система договоренностей и дисциплины: бюджет задержки, близость данных, маленькие очереди, предсказуемые ретраи, кэш-иерархии, правильные протоколы и безжалостная наблюдаемость хвостов. Следуя этим принципам, вы держите p95/p99 в узде без жертв стабильности и кошелька.