Задержка против пропускной способности
Задержка — время на запрос; пропускная способность — запросов в секунду. Не противоположности: закон Литтла связывает их через конкурентность, а теория очередей объясняет, почему задержка взрывается ещё до предела пропускной способности. Перепутаешь — оптимизируешь не то число.
Команда видела, что среднее время ответа API — здоровые 40 мс, поэтому удвоение трафика застало их врасплох: среднее осталось 40 мс, а поток тикетов в поддержку взорвался. p99 вырос со 180 мс до 2,3 секунды. Они смотрели на среднее — единственное число, которое прячет ровно тех клиентов, кому плохо. Починка была не «сделать быстрее», а понять, что задержка и пропускная способность — разные оси, и что система сползала вверх по крутому участку кривой, о существовании которой они не знали.
Две разные оси
Прежде чем чинить то, что сломалось у команды, нужно знать, какое именно число ты оптимизируешь — и почему их путаница уводит в неверную сторону.
Задержка (latency) — сколько длится одна операция: отправил запрос, получил ответ, измерил промежуток. Единицы — время: миллисекунды, микросекунды. Пропускная способность (throughput) — сколько операций завершается за единицу времени: запросов в секунду (RPS), запросов к БД в секунду (QPS), байт в секунду.
Они кажутся одним и тем же («быстро»), но они ортогональны. Грузовик с 10 000 жёстких дисков, едущий через страну, обладает огромной пропускной способностью (перевезены терабайты) и ужасной задержкой (двое суток до первого байта). Оптоволокно имеет крошечную задержку и, для одного маленького запроса, скромную пропускную способность. AWS формулирует так же: throughput — это объём перемещённых данных, latency — задержка до начала их прибытия, и крутишь ты их разными рычагами.
Когда видишь сервис с хорошей пропускной способностью, но всё равно получаешь жалобы — проверь, не смотришь ли на среднюю задержку вместо перцентилей: разрыв обычно там. Junior-модель — «выше пропускная способность значит ниже задержка» — ошибочна достаточно часто, чтобы быть опасной. Добавление шага батчинга повышает пропускную способность и повышает задержку. Добавление реплик повышает пропускную способность и может вообще не тронуть задержку одного запроса. Чтобы рассуждать о системе, нужно держать оба числа — раздельно.
Закон Литтла: мост
Две оси не независимы — их связывает конкурентность «в полёте». Закон Литтла для любой стабильной системы гласит:
L = λ × W
L = среднее число запросов в системе (конкурентность)
λ = пропускная способность (темп прибытия = темп завершения в стабильном режиме)
W = средняя задержка (время, проведённое запросом в системе)Он почти неприлично общий — никаких допущений о распределении, планировании или паттерне прибытия. После перестановки λ = L / W: пропускная способность равна конкурентности, делённой на задержку. Это одно уравнение убивает кучу плохой интуиции. Если каждый запрос занимает W = 200 мс, а ты можешь держать L = 100 запросов в полёте, твой потолок — λ = 100 / 0,2 = 500 RPS, и никакая «оптимизация» его не побьёт, не изменив L или W. Хочешь больше пропускной способности? Либо ускорь каждый запрос (ниже W), либо запускай больше параллельно (выше L — через потоки, соединения или машины).
▸Почему это работает
Почему закон Литтла держится без допущений? Потому что это бухгалтерское тождество, а не физический закон. На длинном окне всё, что вошло, должно выйти (стабильность). Площадь под кривой «запросов в системе во времени» можно просуммировать двумя способами — по запросам (число × время пребывания каждого) или по времени (интеграл конкурентности) — и две суммы обязаны быть равны. Это равенство и есть L = λW. Поэтому он одинаково применим к CPU, пулу потоков, пулу соединений к БД, партиции Kafka или очереди на кассе — и поэтому пул соединений размера L с задержкой вызова W имеет жёсткий потолок пропускной способности, который считается на салфетке.
Почему задержка взрывается раньше, чем упрётся пропускная способность
Вот часть, что удивляет людей: когда давишь пропускную способность к пределу ресурса, задержка растёт не линейно — она уходит в вертикаль. Теория очередей (модель M/M/1) даёт форму:
W = 1 / (μ − λ)
μ = темп обслуживания (макс. пропускная способность ресурса)
λ = темп прибытия (предложенная нагрузка)При утилизации 50% (λ = μ/2) время ожидания мало́. При 90% знаменатель — 0,1μ, и задержка в 10× больше времени обслуживания без нагрузки. При 99% — в 100×. Колено этой кривой — самая важная форма в планировании ёмкости: сервер, который выглядит «наполовину простаивающим» при 50% CPU, имеет совсем другое поведение хвоста, чем при 85%, и зазор между ними — там, где живут сбои. Поэтому зрелые системы целятся в 60–70% утилизации с запасом, а не выжимают 95% — последний кусок пропускной способности стоит неограниченной задержки.
Среднее лжёт; правду говорят перцентили
Команда из вступления смотрела на среднее. Среднее — худшая сводная статистика для задержки, потому что распределения задержек сильно скошены вправо: большинство запросов быстрые, несколько — катастрофически медленные, и эти несколько — ровно те, которые пользователь ретраит (добавляя нагрузку) или бросает (теряя деньги). Вместо этого отчитывайся перцентилями: p50 (медиана, типичный опыт), p99 (1 из 100 — твои активные пользователи ловят это по несколько раз за сессию), p999 (1 из 1000).
Хвостовая задержка — не диковинка; она накапливается. Если одна страница делает 100 бэкенд-вызовов и ждёт их все, страница так же медленна, как самый медленный из 100 вызовов — поэтому медиану страницы задаёт p99 каждого бэкенда. Это усиление хвоста на масштабе (tail-at-scale), и поэтому p99 твоих зависимостей — это твой p50.
▸Частая ошибка
Ловушка измерения, что тихо лжёт: coordinated omission (скоординированное упущение). Генератор нагрузки, который шлёт запрос, ждёт ответа, а потом шлёт следующий, не может записать задержку запросов, которые он не отправил во время затыка. Когда сервер замирает на 1 секунду, в open-loop мире были бы тысячи запросов, каждый со страданием до 1 с; closed-loop генератор увидел один медленный запрос. Твой p99 выглядит прекрасно, пока пользователи кричат. Инструменты вроде wrk2 и честное open-loop нагрузочное тестирование существуют именно ради этого — если твой бенчмарк держит пропускную способность постоянной независимо от скорости сервера, не верь его хвостовым числам.
Сервис держит максимум 200 одновременных запросов (лимит пула соединений). Измеренная средняя задержка — 50 мс на запрос. По закону Литтла, какова его максимальная устойчивая пропускная способность?
Твой p50 ровный при росте нагрузки, но p99 утроился, а CPU держится на 88%. Какова наиболее вероятная причина?
Закон Литтла гласит L = λ × W, где L — конкурентность, W — задержка, а λ — _______; так что при фиксированном лимите конкурентности единственный способ поднять эту величину — снизить задержку каждого запроса.
- 01Сформулируй закон Литтла и что значит каждый член.
- 02Почему задержка растёт нелинейно при приближении к пределу пропускной способности?
- 03Почему отчитываться перцентилями, а не средней задержкой?
Задержка и пропускная способность — разные оси: задержка это время на запрос (в мс/мкс), пропускная способность это завершённых запросов за единицу времени (RPS/QPS). Их связывает закон Литтла, L = λW — бухгалтерское тождество, означающее λ = L / W: при фиксированном лимите конкурентности (размер пула) единственный путь к большей пропускной способности — ниже задержка запроса или больше ёмкости. Теория очередей объясняет опасность: W = 1/(μ−λ), поэтому задержка спокойна, пока не приблизишься к пределу ресурса, а затем уходит в вертикаль — вот почему зрелые системы работают на 60–70% утилизации, а не 95%. Наконец, смотри перцентили, а не среднее: задержка скошена вправо, медленный хвост — это то, что чувствует пользователь, и при fan-out p99 каждой зависимости становится твоим p50. Теперь, когда встретишь здоровое среднее на фоне растущих тикетов поддержки, знаешь что делать: открывай p99 и смотри на утилизацию — прежде чем объявлять что-то исправленным.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.