# Затримка

## Що це таке

Затримка (англійською latency) — це час, за який система починає відповідати:
від миті, коли запит залишає клієнта, до повернення першого байта відповіді.
Коли мережевий шлях вимірюють туди й назад, кажуть про час обміну, round-trip
time (RTT), а у вебпродуктивності те саме називають time to first byte (TTFB).
Час відповіді — ширше поняття: він триває до останнього байта.

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

Кожна мілісекунда береться з одного з кількох джерел:

- **поширення**, шлях сигналу через відстань: світло в оптичному волокні
  долає приблизно 200 км за мілісекунду, тож маршрут завдовжки 1 000 км коштує
  щонайменше 10 мс туди й назад ще до того, як запрацює будь-яка програма;
- **передавання**, виведення байтів у канал, що відчутно для великих обсягів на
  повільних лініях;
- **черга**, очікування за іншою роботою в буфері маршрутизатора, пулі потоків,
  пулі з'єднань чи на блокуванні — єдина складова, що росте з навантаженням;
- **обробка**, сама робота: запит до бази, шаблон, шифрування;
- **обміни протоколу**, розмова до першого корисного байта. Нове
  HTTPS-з'єднання спершу потребує рукостискання TCP і рукостискання TLS, а
  TLS 1.3 скоротив повне рукостискання до одного обміну.

Робоча група IETF з метрик продуктивності IP точно визначила мережевий варіант у
RFC 2681: затримка обміну триває від першого надісланого біта до останнього біта
отриманої відповіді. Два рішення звідти варто перенести на будь-який сервіс.
Відповідь, яка так і не прийшла, вважається невизначеною, неформально —
нескінченною, а не тихо випадає з вибірки. А статистики, які описує документ, —
це перцентилі, медіана й мінімум. Він також зауважує, що мінімум відображає лише
поширення й передавання, а все понад мінімум свідчить про перевантаження. Цей
поділ так само працює для API, як і для шляху між маршрутизаторами.

Для окремого вебзапиту curl розкладе очікування на етапи:

```sh
curl -s -o /dev/null "$URL" -w '
dns        %{time_namelookup}
connect    %{time_connect}
tls        %{time_appconnect}
first byte %{time_starttransfer}
total      %{time_total}
'
```

Кожне число — секунди від початку, тож різниця між сусідніми і є ціною етапу:
пошук імені, встановлення з'єднання, рукостискання TLS і, нарешті, власний час
роздумів сервера до першого байта.

## Чому це важливо

Затримку відчуває користувач, пропускну здатність — рахунок за сервери. Коли
сторінка повільна, рішення полягає в тому, куди вкласти гроші, а чотири причини
вище мають чотири різні розв'язки, що не замінюють один одного. Відстань
долають, наближаючись до користувача: кеш, CDN, регіон поруч. Обміни
скорочують, роблячи їх менше: повторно використовують з'єднання, групують
запити, виконують виклики паралельно. Обробку прискорюють, роблячи менше
роботи: індекс, кращий план запиту. І тільки чергу лікують потужністю. Запит,
що триває дві секунди на вільному сервері, триватиме дві секунди й на сервері
вдесятеро більшому, тому нове залізо відповідає лише на одну причину з
чотирьох.

Середні значення применшують проблему, бо користувачі стикаються з хвостом
розподілу. Сторінка, що паралельно звертається до десяти бекенд-сервісів, чекає
на найповільніший. Якщо кожен виклик повільний в 1% випадків, то 1 − 0,99¹⁰,
тобто близько 9,6% завантажень сторінки, натрапляють щонайменше на один
повільний виклик. Рідкісна затримка в частинах стає звичною в цілому.

Тому серйозні цілі записують у перцентилях, наприклад: 99% запитів отримують
відповідь протягом 300 мс за 30 днів. І тому 99-й перцентиль зазвичай і є тим
числом, за яким варто стежити. Медіана показує, як сервіс почувається в добрий
день, а високі перцентилі — яким він здається клієнтам, що вже збираються піти.

## Як це виглядає на практиці

У картографічному застосунку затримки під час рендерингу шарів і запитів до
даних про місцезнаходження шкодили і користувачам, і надійності бекенду. База
під ним, Microsoft SQL Server, не мала нативної геопросторової індексації, тож
повільною ланкою була обробка, а не відстань.
[Перехід на PostgreSQL із PostGIS скоротив середній час відповіді з 2,5 секунди до менш ніж 250 мілісекунд](/uk/portfolio/improved-geographical-map-application-performance-by-10x-through-6/),
тобто просторові запити стали вдесятеро швидшими. За цим стоять індекси GiST і
SP-GiST на просторових стовпцях, понад 50 мільйонів перенесених записів і
конфігурація, налаштована на паралельний доступ; навантаження на процесор
бекенду впало на 65%.

Затримка також обмежує пропускну здатність там, де за один рядок змагаються
багато запитів. Нотатка про те, як
[продати останній квиток рівно один раз](/uk/notes/selling-limited-stock-without-overselling/),
оцінює стелю для одного товару як кількість продажів, яку несе одне блокування,
поділену на час його утримання. Усе повільне всередині блокування цю стелю
знижує: виклик оплати під час утримання опускає її приблизно до двох продажів
на секунду. Навіть захисний механізм тут має ціну. Синхронний standby у
PostgreSQL додає затримку до кожного коміту, а отже, подовжує саме той час
утримання, на який ділить формула.

А щоб з'ясувати, куди йде час,
[власний моніторинг на одній платформі](/uk/portfolio/built-first-party-error-monitoring-and-tracing-98/)
записує затримку API у власну базу даних і трасує запити через OpenTelemetry.
Тож повільний запит можна простежити крізь фронтенд, API і базу даних, а не
вгадувати.

## Де виникають проблеми

**Середнє ховає хвіст.** Середні 100 мс цілком сумісні з тим, що кожен
п'ятдесятий запит триває три секунди. Записуйте затримку як гістограму й
читайте з неї перцентилі. І стежте за тим, як їх поєднують: 99-ті перцентилі
п'яти серверів не можна усереднити до 99-го перцентиля всього парку.
Документація Prometheus прямо про це каже: квантилі, які обчислює summary,
агрегувати не можна, а кошики гістограми можна спершу підсумувати й лише тоді
обчислити квантиль.

**Секундомір вмикають не там.** Сервер, що міряє від початку роботи
обробника, пропускає час, який запит провів у балансувальнику навантаження, у
черзі прийому з'єднань чи в очікуванні з'єднання з пулу. Генератори
навантаження, які чекають на кожну відповідь, перш ніж надіслати наступний
запит, надсилають менше запитів саме тоді, коли система застрягає, і застій
майже не видно в результатах. Вимірюйте й на боці клієнта, а сервер нехай
повідомляє свою частку в стандартному заголовку:

```http
Server-Timing: db;dur=53, app;dur=47.2
```

**Розмова надто балакуча.** Сторінка, що виконує по одному запиту на кожен
рядок списку, платить по одному обміну за рядок. Якщо база в іншому регіоні й
обмін коштує 30 мс, то сто рядків забирають три секунди ще до того, як
будь-який запит зробить справжню роботу. Один запит з join або один пакетний
виклик платить за відстань один раз.

**Повторні спроби множать навантаження.** Клієнт, що здається через секунду й
пробує ще тричі, перетворює один повільний сервер на вчетверо більший трафік, і
сервер від цього стає ще повільнішим. Дайте кожному запиту загальний дедлайн,
збільшуйте паузу між спробами експоненційно й з випадковим розкидом і обмежте
кількість повторів.

## Що почитати далі

- [RFC 2681: A Round-trip Delay Metric for IPPM](https://www.rfc-editor.org/rfc/rfc2681)
  — затримка обміну, визначена до біта, з явними правилами для втрачених
  відповідей і перцентилів.
- [RFC 7679: A One-Way Delay Metric for IP Performance Metrics](https://www.rfc-editor.org/rfc/rfc7679)
  — відповідник для затримки в один бік, ухвалений як Internet Standard.
- [Server Timing](https://www.w3.org/TR/server-timing/) — заголовок W3C, яким
  сервер повідомляє браузеру, на що пішов час.
- [Histograms and summaries](https://prometheus.io/docs/practices/histograms/)
  — чому квантилі із summary не поєднуються, а кошики гістограми поєднуються.
- [RFC 8446: TLS 1.3](https://www.rfc-editor.org/rfc/rfc8446) — будова
  рукостискання, зокрема обмін, який воно заощаджує.

<https://engineer.company/uk/glossary/latency/>
