Latency vs Throughput — разные оси оптимизации. Demo: streaming (low latency), batching (high throughput), tail latency at fan-out, hedged requests (Tail at Scale). Объясняет percentiles (p50/p95/p99), Little's law и почему average latency обманывает.
Latency и throughput путают постоянно. «У нас medium latency 50ms» и «у нас throughput 50K RPS» — это про две разные оси, и почти всегда между ними трейд-офф. Оптимизируешь одно — теряешь другое.
Без чёткой ментальной модели ты не сможешь правильно выбрать между batching и streaming, sync и async, CDN и origin pull, REST и Kafka. И не поймёшь, почему «среднее время ответа 100ms» — это ничего не значит для пользователя, который ловит p99 = 1.5s и закрывает вкладку.
Эта страница — точка сборки трёх независимых концепций: Little's law (как latency, throughput и concurrency связаны), percentiles vs average (почему среднее обманывает), tail latency at scale (Dean & Barroso 2013 — почему fan-out на 100 узлов почти всегда тормозит).
Latency — это время одного запроса (от send до receive). Throughput — это сколько запросов помещается в единицу времени.
Аналогия с дорогой: трак везёт 100 ящиков за раз (high throughput), но машина быстрее доезжает (low latency). Можно увеличить throughput, поставив больше траков — и потерять latency, потому что они стоят в пробке на разгрузке.
Формально связаны через Little's law:
L = λ × W
concurrent_requests = throughput × latency
Если у тебя 10K RPS и средняя latency 200ms — в системе одновременно «в полёте» 2000 запросов. Это число определяет, сколько коннекшнов, потоков, файловых дескрипторов, памяти ты должен заложить. Если хочешь снизить concurrency — снижай либо throughput, либо latency, но не оба сразу при том же L.
Главный сюрприз: average latency почти всегда врёт. У 99% запросов 50ms, у 1% — 5 секунд (GC pause, slow disk, network blip). Среднее = ~100ms — выглядит ОК. А пользователь, попавший в этот 1%, видит зависший UI. Поэтому SRE смотрит p95, p99, p99.9 — а не average.
Одна топология — три режима её использования:
Три edge-пути из gateway:
gateway → worker-N напрямую — streaming (low latency).gateway → batcher → worker-1 — batching (high throughput).gateway → worker-1/2/3 параллельно — fan-out scatter (tail latency).Сценарии переиспользуют одну и ту же топологию, чтобы было видно: разница не в железе, а в том, как ты гоняешь по нему трафик.
Streaming — low latency, low throughput. Каждый запрос идёт сразу к свободному worker, INSERT в БД, ответ клиенту. p50 ≈ 3ms, p99 ≈ 10ms — отлично для user-facing UI. Но throughput упирается в overhead per-request (network RTT, parse JSON, prepare statement) и редко превышает 1–5K RPS на инстанс.
Batching — high throughput, high latency. Gateway копит 100 событий в batcher (или ждёт 100ms, что наступит раньше), потом flush'ит как один bulk insert. Throughput взлетает в 10x — но первый запрос в batch ждал лишние 100ms впустую. Это классический трейд-офф ETL-пайплайнов, Kafka producer'ов (linger.ms), database write batching.
Tail latency — fan-out scatter problem. Запрос разлетается на 3 worker'а параллельно (типа поиск по шардам). Два вернулись за 50–80ms, третий поймал GC pause и тормозит 1000ms. Overall latency = MAX(всех) = 1000ms. Это «The Tail at Scale» (Dean & Barroso, 2013): если у тебя p99 = 1s на одном шарде и шардов 100 — почти каждый запрос ловит хотя бы один slow shard. Математика: P(никто не медленный) = 0.99^100 ≈ 37%.
Hedged requests — fix для tail latency. Google-трюк: критичный запрос дублируется на 2 worker'а, берём первый ответ, второй отменяем. p99 падает в 5–10 раз. Цена: +5% load на дубли — приемлемо для user-facing read'ов. Используется в Bigtable, Spanner, BigQuery, частично в Cassandra (speculative retry).
Главное решение: на какую ось ты оптимизируешь — и кто будет страдать.
| Решение | Что выигрываешь | Что теряешь |
|---|---|---|
| Streaming | low p99 latency | per-request overhead, низкий throughput |
| Batching | 10x throughput | +100ms latency для первого в batch |
| Async (queue) | low API latency | end-to-end latency растёт |
| Caching | low latency для hits | сложность инвалидации, stale reads |
| Hedged requests | low p99 при fan-out | +5–20% nagrузка, нужна dedup на писателе |
| Sync replication | сильная консистентность | latency = MAX по репликам |
| Async replication | low write latency | replication lag, потеря при failover |
Универсального правила нет. User-facing — почти всегда оптимизируй latency (p99 — не average). Batch / analytics / ETL — почти всегда throughput, latency терпит минуты.
linger.ms (сколько ждать batching) и batch.size — буквально диалы между latency и throughput. По умолчанию linger = 0 (streaming), у throughput-heavy пайплайнов ставят 5–100ms.linger.ms = 100 — throughput вырос, начальство довольно, пользователи в чате жалуются на лаги UI. Решение не из метрик, а из use case'а.pacelc-theorem.