Deep Engineering
Средний·Опубликовано·30 МИН

Очередь и хвост: почему рост нагрузки на 19 % даёт рост задержки вчетверо

Загрузка выросла с 0,80 до 0,95 — это плюс девятнадцать процентов. Время в системе выросло вчетверо. Урок разбирает, откуда берётся эта непропорциональность, и заодно опровергает ходячее «хвост растёт быстрее среднего»: в чистой очереди это не так, а причина расхождения хвоста и среднего — другая.

Полное техническое изложение

TL;DR

Задержка запроса — это время обслуживания плюс время ожидания, и с ростом нагрузки растёт ожидание. Пока сервер успевает, между запросами случаются паузы, и очередь в них рассасывается. Чем ближе поток запросов к пределу сервера, тем реже паузы — и тем дольше стоит каждая случайная порция запросов. Сама работа при этом не дорожает ни на миллисекунду.

Отсюда главное следствие: задержка растёт непропорционально нагрузке. В модели загрузка выросла в 1,19 раза (с 0,80 до 0,95), а среднее время в системе — в 4,1 раза, с 51,3 мс до 212,9, при неизменном обслуживании в 10 мс. Значит, «у нас есть ещё пятнадцать процентов запаса» — это не пятнадцать процентов задержки: работать на 0,95 вчетверо дороже по задержке, чем на 0,80, при том же железе и том же коде. Тот же механизм работает и в обратную сторону: при прежнем потоке запросов ускорение узкого места снижает не только обслуживание, но и загрузку, поэтому у предела даже 20 % дают кратный выигрыш — в модели среднее упало с 212,9 до 34,1 мс. И то же самое про графики: среднее и медиана растут вместе с хвостом, поэтому «средняя задержка в норме» ещё не доказывает, что в норме p99.

Дальше — числа, границы и одно опровержение. Модель здесь одна и названа прямо: M/M/1 — один обработчик, очередь без предела, показательные промежутки прихода и показательное время обслуживания со средним 10 мс. В этой модели при росте загрузки с 0,80 до 0,95 p99 вырос в 4,1 раза — ровно как и среднее, а отношение p99 к среднему осталось прежним: 4,43 против 4,38. Из этого следует не обратный закон, а отсутствие закона: «хвост всегда растёт быстрее среднего просто из-за очереди» — неверное обобщение, потому что в M/M/1 он растёт с той же скоростью. При тяжёлом хвосте времени обслуживания, нескольких обслуживающих устройствах, пакетировании и приоритетах картина другая, и в уроке она измерена: при той же загрузке 0,80 и том же среднем обслуживании 10 мс замена ровного обслуживания на «95 % по 5 мс и 5 % по 105 мс» подняла p99 в 3,1 раза, а медиану — в 2,6. Поэтому и обратная картина на графиках — «хвост взлетел, а среднее почти нет» — говорит о росте разброса, а не нагрузки. И ещё одна граница: загрузка ρ в модели — это не загрузка процессора на дашборде.

Порог входа
Перед уроком достаточно понимать
  • сервер обрабатывает запросы не мгновенно: на каждый уходит какое-то время;
  • запросы приходят когда придётся, а не по расписанию, поэтому иногда они приходят кучно;
  • если занятому серверу приходит новый запрос, тот ждёт своей очереди.
Заранее знать не нужно
  • перцентили и то, чем p99 отличается от среднего и медианы;
  • загрузка ρ, модель M/M/1, показательное распределение, «разброс времени обслуживания».

Что здесь на самом деле спрашивают

Лестница обычно такая:

  1. «Что такое p99?» — разминка.
  2. «Почему нельзя смотреть на среднее?» — здесь начинается суть.
  3. «Нагрузка выросла на пятую часть, а задержка вчетверо. Почему?» — главный вопрос темы.
  4. «До какой загрузки можно нагружать сервис?» — вопрос про запас.
  5. «Почему хвост длиннее середины?» — вопрос, на который обычно отвечают «потому что нагрузка», и это неверный ответ.
  6. «Что делать с хвостом?» — вопрос про то, на что реально можно влиять.
модель с допущениямиЧисла этого урока — из симуляции bench/queueing/mm1.py, а не из замера живой системы. Допущения в докстринге: один обработчик, очередь без предела, показательные промежутки прихода и время обслуживания со средним 10 мс, сто тысяч запросов на точку, зерно фиксировано.

Числа этого урока получены моделью, а не прогоном на живом сервисе. Причина та же, что и в уроке про ретраи: наблюдать зависимость задержки от загрузки — значит менять нагрузку на живом сервисе. Модель даёт ту же зависимость при явно названных допущениях; взамен читателю нужно помнить, что у настоящего сервиса несколько обработчиков, ограниченная очередь и таймауты.

База: очередь появляется тогда, когда приходят чаще, чем успевают

Прежде чем говорить о загрузке, перцентилях и моделях, стоит назвать обычными словами, откуда вообще берётся очередь.

Пусть сервер успевает обрабатывать десять запросов в секунду. Больше он не может — это его предел.

  • Приходит восемь в секунду. Сервер справляется. Иногда запросы приходят кучно, и кому-то приходится подождать, но за этой кучностью наступает пауза, и очередь в ней рассасывается. Запас есть, и он в паузах.
  • Приходит десять в секунду. Сервер занят всё время. Пауз больше нет — а значит, рассасываться очереди нечем: всё, что накопилось в момент кучности, так и остаётся стоять и растёт дальше.

Вот и вся причина, по которой очередь ведёт себя так странно у самого предела: исчезают не силы, а паузы.

Вторая вещь, которую надо назвать до всего остального, — из чего складывается задержка, которую видит пользователь:

время обслуживания + время ожидания = задержка

Время обслуживания — сколько сервер тратит на сам запрос. Время ожидания — сколько запрос простоял в очереди, пока сервер занимался другими. Пользователь видит только сумму, а слагаемые меняются по-разному, и в этом весь урок.

И вот главный вопрос: какое из двух слагаемых растёт, когда «стало медленнее»? Работа ведь не изменилась: тот же код, то же железо, тот же запрос.

Ответ: растёт ожидание. Обслуживание остаётся тем же самым, а времени в очереди становится всё больше — потому что пауз, в которые она рассасывается, всё меньше.

Этого уже достаточно, чтобы ответить на базовый вопрос собеседования. Всё дальнейшее — про то, насколько непропорционально растёт ожидание, откуда берётся разница между серединой и хвостом и почему на неё влияет не то, о чём обычно думают.

Механизм 1: очередь дорожает быстрее, чем растёт нагрузка

TIME IN THE SYSTEM AGAINST UTILISATION
--------------------------------------
    load       mean        p50        p95        p99
    0.50      20.3       14.0       61.2       94.0 
    0.70      34.1       23.6      103.2      158.1 
    0.80      51.3       35.8      155.7      227.2 
    0.90     109.2       74.1      331.9      503.2 
    0.95     212.9      152.9      634.0      932.5 
модель с допущениямиbench/queueing/mm1.py, модель. Абсолютные миллисекунды заданы выбранным средним обслуживанием в 10 мс; содержательны отношения между строками.

Столбец «load» — это та самая доля из «Базы»: восемь запросов на десять посильных — загрузка 0,8, девять с половиной — 0,95. Формально загрузка ρ в модели — это доля времени, когда единственный обработчик занят.

И сразу оговорка, без которой это число сбивает с толку: ρ в модели — это не загрузка процессора на дашборде. Процессор показывает, сколько работает железо; ρ говорит, насколько плотно занято то место, где стоит очередь. Совпадать они не обязаны: узким местом может быть диск, соединение с базой, пул обработчиков или блокировка, и тогда очередь растёт по всем законам этой таблицы при спокойных процентах на графике процессора. Читая таблицу, держите в голове ρ того ресурса, который на самом деле ограничивает, а не самого заметного.

Прочитайте таблицу по столбцу «mean»: 20 → 34 → 51 → 109 → 213. Нагрузка растёт всё более мелкими шагами, время — ускоряясь.

Причина не в том, что сервис устал. Обслуживание всё это время занимает те же 10 мс; растёт только ожидание. Механизм такой: пока обработчик занят, новые запросы копятся; чем выше загрузка, тем реже случаются паузы, в которые очередь успевает рассосаться. При загрузке 0,5 пауз много, при 0,95 — почти нет, и каждая случайная порция запросов рассасывается всё дольше.

Отсюда правило, которое стоит назвать в ответе: последние проценты загрузки самые дорогие. В столбце «mean» шаг с 0,5 на 0,8 — это 20,3 → 51,3, а следующий шаг, с 0,8 на 0,95, — 51,3 → 212,9, вчетверо.

Механизм 2: считаем непропорциональность

WHAT GROWS FASTER THAN WHAT
---------------------------
  load 0.80 -> 0.95 is a rise of              1.19x
  mean time rises by                          4.15x
  p99 rises by                                4.10x
  p99 over mean at load 0.80                  4.43x
  p99 over mean at load 0.95                  4.38x
модель с допущениямиbench/queueing/mm1.py, модель. Строки 4 и 5 — то место, где модель опровергает ходячую формулировку, а не подтверждает её.

Первые две строки — ответ на главный вопрос темы: нагрузка выросла на 19 %, задержка — вчетверо. Это и есть непропорциональность, из-за которой планы вида «у нас есть ещё пятнадцать процентов запаса» оказываются неверными: пятнадцать процентов загрузки — это не пятнадцать процентов задержки.

А вот четвёртая и пятая строки — то, ради чего стоит читать модель внимательно. Отношение p99 к среднему не изменилось: 4,43 против 4,38. То есть в M/M/1 — той модели, из которой взята вся эта таблица, — p99 и среднее масштабируются одинаково: хвост растёт ровно с той же скоростью, что и середина, а не быстрее.

Почему так, видно из устройства самой модели, и это стоит назвать прямо: время в системе здесь распределено показательно, а у показательного распределения любой квантиль равен среднему, умноженному на постоянный множитель. Поэтому отношение p99 к среднему и не может измениться: растёт само среднее, а постоянный множитель остаётся прежним.

Теперь аккуратно про то, что отсюда следует. Из этого результата не следует, что хвост никогда не растёт быстрее среднего. Из него следует ровно одно: тезис «хвост всегда растёт быстрее среднего просто из-за очереди» не является общим законом — есть очередь, в которой это не так, и это самая изученная очередь на свете. Формулировать надо с оговоркой о границах: в M/M/1 хвост и среднее масштабируются одинаково; за её пределами — как получится.

А выходов за границу у настоящей системы много. Тяжёлый хвост времени обслуживания (есть класс запросов, который работает на порядок дольше остальных), несколько обслуживающих устройств вместо одного, пакетирование (запросы обрабатываются группами), приоритеты (кого-то пропускают вперёд) — в каждом из этих случаев картина другая, и хвост вполне может отрываться от середины. Первый из этих случаев в уроке измерен, и им мы займёмся дальше.

Механизм 3: хвост делает разброс, а не загрузка

Гипотеза: хвост отрывается от середины не из-за загрузки, а из-за разброса времени обслуживания. Проверяем так: загрузка та же, среднее то же, меняется только разброс: вместо «в среднем 10 мс» — «95 % запросов по 5 мс и 5 % по 105 мс». Среднее у такого обслуживания ровно то же самое: 0,95 × 5 + 0,05 × 105 = 10.

SAME MEAN SERVICE TIME, DIFFERENT SPREAD
----------------------------------------
  both runs: load 0.80, average service time 10 ms
  A: service time exponential, average 10 ms
  B: service time is 5 ms in 95% of cases and 105 ms in 5%

                           mean        p50        p99
      A, exponential      51.3       35.8      227.2
            B, mixed     139.1       92.2      713.8
  p99 of B over p99 of A                      3.14x
  p50 of B over p50 of A                      2.58x
модель с допущениямиbench/queueing/mm1.py, модель. Обе строки сняты при загрузке 0,80 и одинаковом среднем времени обслуживания; отличается только разброс.

Всё выросло — и медиана, и хвост, — но хвост сильнее: 3,1 против 2,6. Причём загрузка не менялась ни на процент, и средняя работа на запрос осталась прежней.

Это и есть тот выход за границу M/M/1, о котором шла речь выше: как только у времени обслуживания появился тяжёлый хвост, хвост задержки стал расти быстрее середины. Обратите внимание на формулировку: не «хвост растёт быстрее среднего, потому что очередь», а «хвост растёт быстрее среднего, когда меняется разброс; одной загрузки для этого мало».

Отсюда формулировка для ответа: хвост создаёт разброс, а не загрузка. Пять процентов медленных запросов — это не «пять процентов пользователей подождут», это очередь, которая за ними выстраивается, и в неё попадают все остальные.

И практический вывод: если хвост оторвался от середины, надо искать медленный класс запросов. Прибавка производительности узкого места снижает загрузку и сжимает всё распределение — хвост вместе с серединой; сокращение разброса двигает именно хвост. Какой из рычагов сильнее, зависит от того, насколько узкое место близко к насыщению, — это следующий раздел.

Механизм 4: ускорение узкого места при том же потоке запросов

Из первой таблицы легко сделать неверный вывод. При загрузке 0,95 запрос проводит в системе в среднем 212,9 мс, из них обслуживание — 10 мс, около пяти процентов; значит, ускорять обслуживание бессмысленно? Нет: в этом рассуждении ожидание принято постоянным, а оно само зависит от скорости обслуживания. Если поток запросов прежний, более быстрый обработчик реже занят — загрузка падает вместе с временем обслуживания, и очередь сжимается.

Проверка на той же модели и с тем же зерном: поток держим неизменным, 95 запросов в секунду, меняем только обработчик.

SAME ARRIVALS, MORE CAPACITY: 95 REQUESTS PER SECOND IN EVERY ROW
-----------------------------------------------------------------
  arrivals: 95 requests per second in every row, seed 20260905

                                       util     mean      p50      p99   theory
           baseline: 1 server, 10 ms  0.950    212.9    152.9    932.5    200.0
        service -20%: 1 server, 8 ms  0.760     34.1     23.8    153.1     33.3
    capacity x1.2: 1 server, 8.33 ms  0.792     41.0     28.6    181.6     40.0
            second server: 2 x 10 ms  0.475     13.0      9.5     55.4     12.9
      one server twice as fast: 5 ms  0.475      9.6      6.7     44.7      9.5

  baseline row equals block 1 at load 0.95   True

                against the baseline  mean falls  p99 falls
        service -20%: 1 server, 8 ms       6.24x      6.09x
    capacity x1.2: 1 server, 8.33 ms       5.19x      5.13x
            second server: 2 x 10 ms      16.34x     16.83x
      one server twice as fast: 5 ms      22.08x     20.85x
модель с допущениямиbench/queueing/mm1.py, четвёртый блок, модель. Входной поток во всех строках один и тот же — 95 запросов в секунду; первая строка совпадает с блоком 1 при загрузке 0,95. Столбец theory — среднее по формуле M/M/c для тех же параметров. Строка с двумя обработчиками нарушает допущение «обработчик один»: у них общая очередь.

Обслуживание на 20 % короче (10 → 8 мс) снижает загрузку с 0,95 до 0,76, и среднее падает с 212,9 до 34,1 мс — в 6,2 раза, p99 — в 6,1. Если «на 20 % быстрее» понимать как производительность ×1,2 (8,33 мс), выигрыш — в 5,2 раза. Формула M/M/1 для среднего, E[T] = 1/(μ − λ), даёт то же направление и тот же порядок: 200 → 33,3 мс; у самого предела конечная симуляция отходит от формулы на несколько процентов.

Второй обработчик — тоже прибавка производительности, только вдвое, и выигрыш у него соответственно больше: в 16 раз по среднему. Сравнивать его с ускорением на 20 % — значит сравнивать разные прибавки. При равной прибавке один вдвое более быстрый обработчик здесь даёт среднее даже ниже, 9,6 мс против 13,0: у двух обработчиков каждый запрос по-прежнему обслуживается 10 мс. И второй обработчик помогает, только если работа действительно делится: общая блокировка или общая база оставят узкое место там, где оно было.

Обратная крайность — «ускорение кода выгоднее всего» — тоже неверна. Выигрыш от одного и того же ускорения зависит от того, насколько близко узкое место к насыщению:

THE SAME 20% SHORTER SERVICE AT DIFFERENT ARRIVAL RATES
-------------------------------------------------------
  1 server; service 10 ms -> 8 ms; arrivals fixed within each row

  arrivals/s         util  mean, 10 ms  mean, 8 ms    falls
          50   0.50->0.40         20.3        13.4    1.51x
          80   0.80->0.64         51.3        22.7    2.26x
          95   0.95->0.76        212.9        34.1    6.24x
модель с допущениямиbench/queueing/mm1.py, четвёртый блок, модель. В каждой строке поток запросов один и тот же до и после ускорения; меняется только среднее время обслуживания, 10 → 8 мс.

Вдали от предела (50 запросов в секунду) те же 20 % дают полтора раза, у предела — шесть. И ускорять имеет смысл именно узкое место: ускорение шага, у которого очереди нет, не снижает загрузку того ресурса, где очередь стоит.

Глубже: чего в модели нет

Модель этого урока — M/M/1, и называть её стоит всякий раз вместе с числами: почти все выводы верны в её границах, а не вообще. Есть четыре вещи, о которых стоит сказать самому.

Обработчик один. Настоящий сервис обслуживает несколько запросов параллельно, и это сильно меняет числа: очередь у нескольких обработчиков рассасывается быстрее, и та же загрузка стоит дешевле. Направление зависимости при этом сохраняется, а вот конкретные кратности — нет. Здесь же ломается и равенство «p99 и среднее растут одинаково»: оно доказано только для M/M/1.

Очередь без предела. В модели никто не отказывает и не уходит. В настоящей системе есть очередь accept конечной длины — та самая, что описана в tcp(7) и разобрана в уроке про очередь accept, — и таймауты клиентов (урок про таймауты): и то и другое обрезает хвост — ценой ошибок вместо ожидания.

Ретраев нет. А они есть в жизни, и они добавляют нагрузку именно тогда, когда система у предела (урок про ретраи). Реальная кривая круче модельной ровно на этот вклад.

И ещё раз про ρ. Загрузка в модели — доля времени, когда занято то самое единственное место, где стоит очередь. На дашборде такого числа обычно нет: там загрузка процессора, а очередь может стоять у пула соединений, у диска или у блокировки. Прикладывать таблицу к процентам процессора — самая частая ошибка при переносе этих выводов в живую систему.

Как отвечать на собеседовании

Короткий ответ: время в системе — это обслуживание плюс ожидание, и с ростом загрузки растёт ожидание, а не обслуживание. Поэтому зависимость непропорциональна: в модели рост загрузки с 0,80 до 0,95 — это плюс 19 %, а время выросло в 4,1 раза при неизменных 10 мс работы на запрос.

Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.

Если интервьюер копает глубже

Хороший ответ отличают три вещи. Первая — вы не выдаёте «хвост растёт быстрее среднего» за общий закон очередей: в модели M/M/1 отношение p99 к среднему постоянно (4,43 против 4,38), то есть от одной только загрузки растягивается всё распределение целиком. Вторая — вы называете настоящую причину длинного хвоста: разброс времени обслуживания; в той же модели при той же загрузке и том же среднем смена ровного обслуживания на «95 % по 5 мс и 5 % по 105 мс» подняла p99 втрое, а медиану — вдвое с небольшим. Третья — вы делаете из этого практический вывод при фиксированном потоке запросов: если хвост оторвался от середины, ищут медленный класс запросов; если растёт всё распределение, нужна производительность узкого места — и у предела даже её прибавка на 20 % сокращает время в разы (в модели при 95 запросах в секунду — с 212,9 до 34,1 мс), а вдали от предела — заметно меньше.

И две вещи, в которых легко перегнуть палку. Первая: «p99 и среднее растут одинаково» — свойство M/M/1, а не закон очередей. При тяжёлом хвосте времени обслуживания, нескольких обслуживающих устройствах, пакетировании и приоритетах хвост вполне отрывается от середины — измеренный в уроке разброс как раз и есть такой случай. Вторая: загрузка ρ в модели — не загрузка процессора на дашборде; прежде чем прикладывать таблицу к своей системе, надо понять, у какого ресурса стоит очередь.

Дальше спросят

Спросят дальше

До какой загрузки можно нагружать сервис?

Короткий ответ

Ответ следует из таблицы, а не из привычки: в столбце «mean» переход с 0,5 на 0,8 — это 20,3 → 51,3, а с 0,8 на 0,95 — 51,3 → 212,9, вчетверо. То есть «сколько можно» — это вопрос о том, какой задержкой вы согласны платить, и о том, насколько резкими бывают всплески.

Запас нужен не ради среднего, а ради всплеска: короткий пик загоняет систему, стоящую на 0,95, в очередь, которая уже не рассасывается; стоящая на 0,7 переживает тот же пик и возвращается.

Спросят дальше

Что лучше: второй обработчик или ускорение обработки на 20 %?

Короткий ответ

Сравнивать надо при том же потоке запросов, и тогда оба действия делают одно и то же — прибавляют производительность узкому месту, а с ней снижают загрузку и ожидание. Ускорение уменьшает не только «пять процентов» обслуживания: при прежнем потоке обработчик реже занят, и очередь сжимается вместе с ним. В модели при 95 запросах в секунду обслуживание 10 → 8 мс сократило среднее с 212,9 до 34,1 мс, а второй обработчик — до 13,0.

Второй обработчик выиграл потому, что это прибавка вдвое, а не на четверть. При равной прибавке один вдвое более быстрый обработчик дал в модели 9,6 мс. Поэтому выбирают по цене и осуществимости: второй обработчик помогает, только если работа делится и не упирается в общую блокировку или базу; ускорение — только если ускоряется именно узкое место.

Спросят дальше

Как уменьшить хвост, не трогая мощность?

Короткий ответ

Уменьшить разброс. В модели это единственное, что двигало хвост сильнее середины: та же загрузка, то же среднее обслуживание — и p99 втрое больше из-за пяти процентов медленных запросов.

На практике это означает: найти медленный класс (тяжёлые запросы, холодный кеш, промахи в базу) и либо ускорить его, либо увести в отдельный путь, чтобы он не задерживал остальных. Второе часто дешевле и всегда предсказуемее.

Спросят дальше

Правда ли, что p99 — это «худшие 1 % пользователей»?

Короткий ответ

Не совсем, и это важная поправка: p99 считается по запросам, а не по людям. Если страница делает сто запросов и каждый независимо от остальных превышает порог p99 с вероятностью 1 %, то хотя бы один превысит его с вероятностью 1 − 0,99¹⁰⁰ ≈ 63 %. Независимость здесь — допущение, а не факт: запросы одной страницы часто медленные вместе (один перегруженный бэкенд, одна пауза сборщика), и тогда доля меньше.

И «на странице был медленный запрос» ещё не значит «страница была медленной». Это решает критический путь: медленный запрос, который шёл параллельно другим и закончился раньше самого долгого из них, страницу не задержал; цепочка последовательных запросов складывает задержки. Поэтому смотрят не только на p99 запросов, но и на долю сессий с хотя бы одним медленным запросом, и отдельно — на задержку самой страницы.

Частые заблуждения

Утверждение

задержка растёт пропорционально нагрузке

На самом деле

Модель: рост загрузки в 1,19 раза (с 0,80 до 0,95) даёт рост времени в системе в 4,1 раза. Растёт при этом только ожидание — обслуживание всё время занимает те же 10 мс. Поэтому «у нас есть ещё пятнадцать процентов запаса» — это не пятнадцать процентов задержки.

Утверждение

при росте нагрузки хвост растёт быстрее среднего — это закон очередей

На самом деле

Законом это не является. В модели M/M/1 при росте загрузки с 0,80 до 0,95 и среднее, и p99 выросли в 4,1 раза, а отношение p99 к среднему осталось прежним (4,43 против 4,38): от одной загрузки растягивается всё распределение целиком. За границами этой модели — тяжёлый хвост обслуживания, несколько обслуживающих устройств, пакетирование, приоритеты — хвост действительно может расти быстрее, и в уроке это измерено на разбросе.

Утверждение

длинный хвост — признак перегрузки

На самом деле

Не обязательно: хвост делает разброс. Модель при неизменной загрузке 0,80 и неизменном среднем обслуживании 10 мс: замена ровного обслуживания на «95 % по 5 мс и 5 % по 105 мс» подняла p99 в 3,1 раза. Загрузка не менялась ни на процент.

Утверждение

если среднее в норме, то и с хвостом всё в порядке

На самом деле

В M/M/1 среднее и хвост двигаются вместе, поэтому «среднее в норме» ничего не доказывает — оно просто ещё не выросло. А вот случай «хвост вырос, среднее почти нет» встречается и означает конкретную вещь: вырос разброс, то есть появился медленный класс запросов.

Утверждение

при высокой загрузке запрос почти всё время ждёт, поэтому ускорять обработку бесполезно

На самом деле

Ожидание само зависит от скорости обслуживания: при прежнем потоке запросов более быстрый обработчик реже занят. Модель, 95 запросов в секунду: обслуживание 10 → 8 мс снизило загрузку с 0,95 до 0,76, а среднее время в системе — с 212,9 до 34,1 мс, в 6,2 раза; p99 — в 6,1. Обратного закона тоже нет: при 50 запросах в секунду то же ускорение дало полтора раза, ускорять имеет смысл именно узкое место, а если хвост оторвался от середины, рычаг — разброс.

Утверждение

p99 — это худший процент пользователей

На самом деле

Это худший процент запросов. Если страница делает сто запросов и каждый независимо превышает порог с вероятностью 1 %, хотя бы один превысит его с вероятностью 1 − 0,99¹⁰⁰ ≈ 63 % — так что доля сессий с медленным запросом может быть в десятки раз выше доли медленных запросов. Но медленный запрос ещё не делает страницу медленной: это решает критический путь. Считать надо и запросы, и страницы.

Утверждение

модель одноканальной очереди не имеет отношения к реальному сервису

На самом деле

Числа — не имеет: в реальном сервисе несколько обработчиков, ограниченная очередь, таймауты и ретраи, и все кратности будут другими. Направление — имеет: ожидание растёт быстрее загрузки, а хвост тянет разброс. Поэтому выводы M/M/1 читают как отношения, а не как миллисекунды для вашей системы, — и отдельно помнят, что загрузка ρ в модели не равна загрузке процессора на дашборде.

Практика

Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона модели, а не придуман.

Практика · что напечатает

Одноканальная очередь, обслуживание в среднем 10 мс. Считаются три кратности: во сколько раз вырастет среднее время в системе при росте загрузки с 0,80 до 0,95; во сколько раз при этом вырастет p99; и во сколько раз вырастет p99 при неизменной загрузке 0,80, если время обслуживания станет «95 % по 5 мс и 5 % по 105 мс» при том же среднем. Что напечатает этот код?
low = simulate(0.80)
high = simulate(0.95)
mixed = simulate(0.80, service_mixed)
print(f"{high['mean'] / low['mean']:.1f}")
print(f"{high['p99'] / low['p99']:.1f}")
print(f"{mixed['p99'] / low['p99']:.1f}")

Практика · оцените

Загрузка одноканальной очереди выросла с 0,80 до 0,95 — на девятнадцать процентов. Во сколько раз вырастет среднее время в системе?
раза

Проверка знаний

Вопрос 1 из 6

Загрузка выросла с 0,80 до 0,95. Что произойдёт со средним временем в системе?

Источники и что читать дальше

1 ИСТОЧНИК

  1. tcp(7), Linux man-pages 6.7Официальная документация. Единственное место, где урок опирается не на модель, а на документ: очередь готовых соединений, в которой запрос ждёт обработки, — та самая, что описана в listen(2)/tcp(7) и разобрана в уроке про очередь accept. Всё остальное в этом уроке — симуляция с объявленными допущениями (bench/queueing/mm1.py), а не замер живой системы.https://man7.org/linux/man-pages/man7/tcp.7.html