Все повільно, графіки червоні, і хтось уже запропонував звичне рішення: «Просто додайте CPU». Інший каже «Це сховище». Третій наполягає «Це мережа». Саме так команди витрачають дні, гроші й репутацію: вирішують найголоснішу теорію замість реального обмеження.
Ліки нудні й надійні: вимірюйте насичення, затримку, пропускну здатність і черги — одночасно — на критичному шляху. Потім приймайте рішення, що відповідає фізиці. Не відчуттям.
Що таке вузьке місце (і чим воно не є)
Вузьке місце — це ресурс, який обмежує корисну роботу вашої системи для заданого навантаження. Це не «те, що має найбільший графік». Це не «компонент, який оновлювали минулого кварталу». Це не «те, що на 90%».
Вузькі місця специфічні для навантаження. «Сховище — це вузьке місце» — це не діагноз, це категорія. Діагноз виглядає так: «Випадкові 4k читання з QD=1 обмежені затримкою NVMe; ми досягаємо p99 = 3.2ms і черги утворюються в пулі потоків додатку». Це дієво. Це ніж, а не сирена.
Три визначення, які варто викарбувати в операційному посібнику
- Пропускна здатність (Capacity): скільки роботи ресурс може виконати за одиницю часу (наприклад, запитів/сек, МБ/сек, IOPS).
- Насичення (Saturation): наскільки ви близькі до цієї пропускної здатності під навантаженням (і чи утворюються черги).
- Хвостова затримка (Tail latency): що бачать найбільш постраждалі користувачі. Саме в p95/p99 живе ваш пейджер.
Також: вузькі місця рухаються. Виправили CPU — відкрили проблему з блокуваннями. Виправили блокування — виявили сховище. Виправили сховище — тепер мережа. Вітаю: ваша система тепер достатньо швидка, щоб знайти новий спосіб бути повільною.
Одне застереження, щоб тримати себе в тонусі. Парафраз ідеї Gene Kim: Покращення потоків означає знаходження обмежень і підняття їх; оптимізація не-обмежень лише створює локальну швидкість і глобальний біль.
Жарт №1 (короткий, по темі): «Швидке поліпшення продуктивності» схоже на «тимчасове правило брандмауера». Воно буде з вами на вашій пенсійній вечірці.
Чотири сигнали, які мають значення: насичення, затримка, пропускна здатність, помилки
Якщо хочете менше суперечок і швидші інцидентні дзвінки, уніфікуйте словник вимірювань. Я використовую чотири сигнали, бо вони змушують бути конкретним:
Насичення: «Чи ми на межі?»
Насичення про черги і конкуренцію. Черги виконання CPU. Черги запитів до диска. Черги передавання NIC. Пули підключень бази даних. Планування подів Kubernetes. Якщо черги зростають — ви насичені або неправильно налаштовані.
Затримка: «Скільки часу займає одна одиниця роботи?»
Вимірюйте end-to-end затримку і затримку компонентів. Середня затримка — ввічлива вигадка; використовуйте перцентили. Хвостова затримка виявляє вузькі місця першою, бо черги підсилюють варіативність.
Пропускна здатність: «Скільки корисної роботи ми завершуємо?»
Пропускна здатність — це не «байти переміщені», якщо бізнесу важливі «завершені замовлення». Ідеально вимірювати обидва: на рівні користувача й на рівні системи. Система може рухати багато байтів і водночас виконувати менше транзакцій. Саме так повтори і ефект thundering herd оплачують нову яхту вашого хмарного провайдера.
Помилки: «Ми відмовляємося швидко чи повільно?»
Коли система насичена, вона часто виходить з ладу повільно, перш ніж зробить це гучно. Таймаути, повтори, помилки контрольних сум, TCP ретрансміти, помилки вводу/виводу, context deadline exceeded — це дим вузького місця. Ставте помилки в ранг сигналів про продуктивність.
Що означає «реальний ліміт»
Реальний ліміт — це точка, де збільшення навантаження не збільшує завершену роботу, а затримка і черги вибухають. Це вимірюваний «згин». Ваша мета — виявити цей згин для важливого навантаження, а не для синтетичного бенчмарка, який вихваляє вашу покупку.
Черги: куди продуктивність тихо вмирає
Більшість продакшн-вузьких місць — це проблеми черг, замасковані під «повільно». Машина не обов’язково повільна; вона чекає своєї черги за іншою роботою. Ось чому «використання ресурсу» може виглядати нормально, поки система страждає.
Закон Літтла, але в операційному ключі
Закон Літтла: L = λW (середня кількість у системі = інтенсивність надходження × час у системі). Вам не потрібна математична ступінь, щоб ним користуватися. Якщо затримка (W) зростає, а інтенсивність (λ) приблизно та сама, кількість речей, що чекають (L), має рости. Це черги. Знайдіть чергу.
Ключові типи черг, з якими ви зіткнетеся о 2-й ночі
- Черга виконання CPU: потоки готові до виконання, але не заплановані (спостерігайте load і довжину run queue).
- Черга блочного I/O: запити, що чекають на диски/NVMe (спостерігайте avgqu-sz, await, utilization пристрою).
- Черги мютексів/локів: потоки, що чекають на лок (perf, eBPF або метрики додатку).
- Пули підключень: пул підключень БД/HTTP, що обмежують паралелізм (спостерігайте час очікування в пулі).
- Кернельні мережеві черги: відкиди в qdisc, переповнення кільцевих буферів (спостерігайте drops, retransmits).
- Затримки GC та аллокатора: черги всередині рантаймів (JVM, Go, Python).
Черги не завжди погані. Вони стають поганими, коли неконтрольовані, приховані або пов’язані з повторними запитами. Повтори множать черги. Невелике зростання затримки перетворюється на самостійно створений DDoS.
Плейбук швидкої діагностики (перший/другий/третій)
Це послідовність «увійти в палаючу кімнату, не ставши самим вогнем». Вона розроблена для реальності on-call: у вас часткова видимість, розгнівані стейкхолдери й один шанс не погіршити ситуацію.
Перший: підтвердьте симптом у термінах користувача
- Що повільно? Завантаження сторінки? API-ендпоінт? Батч-джоба? DB-запит?
- Це затримка, пропускна здатність чи і те, й інше?
- Це глобально чи ізольовано (одна зона, один пул вузлів, один орендар)?
- Що змінилося недавно (деплойти, конфіг, форма трафіку, зростання даних)?
Другий: знайдіть чергу, найближчу до болю
Почніть з сервісу, що бачить користувач, і спускайтеся по стеку:
- Метрики додатку: перцентили затримки, конкуренція, таймаути, рівень повторів.
- Пули потоків/робітників: довжина черги, насичення, відхилена робота.
- БД: час очікування в пулі підключень, повільні запити, очікування локів.
- ОС: run queue, iowait, тиск пам’яті, переключення контекстів.
- Сховище: затримка пристрою/глибина черги, файлові блокування, ZFS txg sync, writeback.
- Мережа: ретрансміти, відкиди, насичення інтерфейсу, затримка DNS.
Третій: виміряйте насичення й запас потужності, і припиніть здогадки
Виберіть 2–3 кандидати на обмеження і зберіть тверді докази за 10 хвилин:
- CPU: run queue, steal time, throttling, гарячі ядра.
- Пам’ять: великі збои сторінок, swap, reclaim, ризик OOM.
- Диск/NVMe: await, svctm (обережно), avgqu-sz, util, writeback stalls.
- Мережа: ретрансміти, відкиди, qdisc, помилки NIC, RTT.
Якщо ви не можете пояснити уповільнення однією з цих категорій, шукайте координаційні вузькі місця: локи, вибори лідера, централізовані сервіси, квоти, ліміти API, або один гарячий шар.
Практичні завдання: команди, виходи, рішення (12+)
Ці завдання призначені для виконання на Linux-хості в «гарячому шляху». Кожне включає: команду, що означає вихід, і яке рішення прийняти. Виконуйте їх під час інциденту, але також у спокійний час, щоб знати, що таке «норма».
Завдання 1: Визначте, чи CPU справді є обмеженням (run queue + iowait)
cr0x@server:~$ uptime
14:02:11 up 37 days, 3:19, 2 users, load average: 18.24, 17.90, 16.88
Значення: Load average рахує runnable завдання й ті, що в uninterruptible sleep (часто I/O wait). «18» на машині з 32 ядрами може бути нормою або ж трагедією, залежно від того, що роблять ці завдання.
Рішення: Не ставте діагноз «CPU» тільки за load. Далі виконайте vmstat і подивіться на per-core.
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
12 7 0 82124 51200 842120 0 0 120 9820 820 2400 22 8 41 29 0
15 8 0 81560 51200 841980 0 0 140 10120 900 2600 21 9 39 31 0
Значення: r — runnable tasks; b — blocked (часто на I/O). Високий wa разом із високим b вказує на I/O-стали, а не на дефіцит CPU.
Рішення: Якщо b і wa високі, переключіться на аналіз сховища/мережевого I/O перед масштабуванням CPU.
Завдання 2: Перевірте насичення по ядрах і steal/throttling (проблеми віртуалізації)
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.1.0 (server) 01/10/2026 _x86_64_ (32 CPU)
13:58:01 CPU %usr %sys %iowait %irq %soft %steal %idle
13:58:02 all 24.10 10.20 18.50 0.00 0.90 0.00 46.30
13:58:02 7 89.00 9.00 0.00 0.00 0.00 0.00 2.00
13:58:02 12 10.00 5.00 70.00 0.00 1.00 0.00 14.00
Значення: Одне ядро в максимумі може вказувати на однопотоковий вузький профіль або дисбаланс обробки переривань; високий %steal означає конкуренцію у гіпервізора; високий %iowait на конкретних ядрах може корелювати з прив’язкою IRQ та чергами пристроїв.
Рішення: Якщо кілька ядер заштовхнуті, дослідіть однопотокові «гарячі» місця, розподіл переривань або налаштування affinity перед додаванням нових ядер.
Завдання 3: Визначте головних очікувачів: CPU vs I/O vs лока (високо рівнева триаж)
cr0x@server:~$ top -b -n 1 | head -n 20
top - 13:59:41 up 37 days, 3:17, 2 users, load average: 18.24, 17.90, 16.88
Tasks: 412 total, 11 running, 401 sleeping, 0 stopped, 0 zombie
%Cpu(s): 22.4 us, 8.9 sy, 0.0 ni, 46.1 id, 22.1 wa, 0.0 hi, 0.5 si, 0.0 st
MiB Mem : 64000.0 total, 1100.2 free, 4200.1 used, 58700.0 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 59600.0 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
23120 app 20 0 4120.5m 520.2m 152.0m D 35.0 0.8 12:20.11 java
Значення: Процес у стані D — uninterruptible sleep, зазвичай чекає на I/O. Високий системний wa підсилює кандидатуру I/O як вузького місця.
Рішення: Слідуйте за процесами в стані «D», щоб дізнатися, на що вони чекають (блокові пристрої, NFS тощо).
Завдання 4: Зіставте завислий процес із його відкритими файлами та базовою файловою системою
cr0x@server:~$ lsof -p 23120 | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 23120 app cwd DIR 253,0 4096 2 /srv/app
java 23120 app 12u REG 253,0 1048576 98342 /srv/app/data/index.bin
Значення: Показує, які шляхи процес активно використовує. Ви намагаєтеся зв’язати «додаток повільний» з «ця файлова система/пристрій повільний».
Рішення: Визначте монтування та пристрій, що лежить в гарячому шляху; спрямовуйте інструменти I/O туди.
cr0x@server:~$ findmnt -T /srv/app/data/index.bin
TARGET SOURCE FSTYPE OPTIONS
/srv /dev/nvme0n1p2 ext4 rw,relatime
Рішення: Тепер ви знаєте, який пристрій перевіряти (nvme0n1 у прикладі).
Завдання 5: Затримка і черги на рівні пристрою (сировина правди)
cr0x@server:~$ iostat -x 1 3
Linux 6.1.0 (server) 01/10/2026 _x86_64_ (32 CPU)
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz %util
nvme0n1 820.0 2100.0 3280.0 26800.0 0.0 0.0 0.00 0.00 1.2 18.9 42.10 98.00
Значення: Високий %util з великою aqu-sz означає, що пристрій насичений і запити ставлять у чергу. Write await високий — записи чекають довго.
Рішення: Припиніть вважати, що CPU це виправить. Зменшіть тиск записів, змініть шаблон I/O, додайте пристрої або перемістіть навантаження. Також перевірте, чи затримка походить від пристрою чи від поведінки файлової системи при скиданні (flush).
Завдання 6: NVMe-специфічні логи здоров’я й лічильники помилок
cr0x@server:~$ sudo nvme smart-log /dev/nvme0
Smart Log for NVME device:nvme0 namespace-id:ffffffff
critical_warning : 0
temperature : 41 C
available_spare : 100%
percentage_used : 7%
media_errors : 0
num_err_log_entries : 0
Значення: Якщо бачите media errors або високе percentage_used, продуктивність може деградувати, і ваше «вузьке місце» перетворюється на «несправний диск».
Рішення: Якщо з’являються помилки, переведіть систему з режиму продуктивності в режим надійності: плануйте заміну і зменшіть навантаження.
Завдання 7: Тиск запису файлової системи й kernel writeback (dirty pages можуть бути вашою чергою)
cr0x@server:~$ cat /proc/meminfo | egrep 'Dirty|Writeback|MemFree|MemAvailable'
MemFree: 80124 kB
MemAvailable: 61045232 kB
Dirty: 1248204 kB
Writeback: 92160 kB
WritebackTmp: 0 kB
Значення: Великі і стійкі Dirty можуть означати, що система буферизує записи й пізніше скидає їх болючими вибухами, створюючи стрибки затримки.
Рішення: Якщо dirty росте без спаду, перевірте обмеження writeback, журналювання файлової системи й патерни флашування додатку.
Завдання 8: Виявлення тиску пам’яті (непомітне вузьке місце, що маскується під CPU)
cr0x@server:~$ vmstat 1 5 | tail -n +3
6 0 0 10240 45000 1200000 0 0 0 2000 1100 3800 30 12 48 10 0
5 0 0 8120 45000 1180000 0 0 0 2200 1200 4200 29 11 44 16 0
Значення: Слідкуйте за si/so (swap in/out) і постійно низькою вільною пам’яттю з високою кількістю cs (context switches). Навіть без свапу, хвилі reclaim можуть раптово піднімати затримку.
Рішення: Якщо reclaim інтенсивний (перевірте sar -B або PSI), зменшіть пам’ятний слід або додайте RAM; не женіться за фантомними проблемами CPU.
Завдання 9: Pressure Stall Information (PSI): доведіть час, втрачений через контенцію
cr0x@server:~$ cat /proc/pressure/io
some avg10=12.34 avg60=10.21 avg300=8.90 total=98324212
full avg10=4.12 avg60=3.20 avg300=2.75 total=31244211
Значення: PSI кількісно показує, скільки часу задачі простоюють через I/O-тиск. full означає, що інколи жодна задача не могла просунутись через I/O — це реальний біль, а не теорія.
Рішення: Якщо PSI високий, пріоритезуйте зменшення конкуренції I/O (блечування, кешування, обмеження черг) замість оптимізації CPU на мікрорівні.
Завдання 10: Мережа: знайдіть ретрансміти і відкиди (затримка, що виглядає як «повільний додаток»)
cr0x@server:~$ ss -s
Total: 1252 (kernel 0)
TCP: 1023 (estab 812, closed 141, orphaned 0, timewait 141)
Transport Total IP IPv6
RAW 0 0 0
UDP 12 9 3
TCP 882 740 142
INET 894 749 145
FRAG 0 0 0
Значення: Велика частота створення/закриття з’єднань може вказувати на повтори/таймаути. Цей вихід — лише апетайзер.
cr0x@server:~$ netstat -s | egrep 'retransmit|segments retransmited|packet receive errors|dropped'
18432 segments retransmited
92 packet receive errors
1184 packets received, dropped
Значення: Ретрансміти й відкиди вбивають пропускну здатність і створюють хвостову затримку.
Рішення: Якщо ретрансмітів більше під час уповільнення — розглядайте мережевий шлях як кандидата: перевірте статистику NIC, черги й upstream-пристрої.
Завдання 11: Лічильники на рівні NIC (доведіть, що це не «додаток»)
cr0x@server:~$ ip -s link show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 0a:1b:2c:3d:4e:5f brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped missed mcast
9823412231 9123421 0 812 0 0
TX: bytes packets errors dropped carrier collsns
11233412211 10233421 0 221 0 0
Значення: Відкиди на хості — однозначний сигнал. Вони можуть бути через переповнення кільцевих буферів, проблеми qdisc або просто надто великий трафік.
Рішення: Якщо відкиди ростуть, зменшіть стрибкоподібність (pacing), обережно збільшіть буфери або виправте upstream-затор; не просто «додайте потоки».
Завдання 12: Перевірте сплески затримок файлової системи від синхронних навантажень (приклад ext4)
cr0x@server:~$ sudo dmesg -T | egrep -i 'blocked for more than|I/O error|nvme|EXT4-fs' | tail -n 10
[Fri Jan 10 13:55:12 2026] INFO: task java:23120 blocked for more than 120 seconds.
[Fri Jan 10 13:55:12 2026] EXT4-fs (nvme0n1p2): Delayed block allocation failed for inode 98342 at logical offset 152343 with max blocks 4
Значення: Kernel каже, що процеси заблоковані і іноді чому. Це може звинувачувати тиск на алокацію, writeback або зависання пристрою.
Рішення: Ставтеся до повідомлень «blocked for more than» як до серйозних I/O-сталів. Припиніть нескінченні рестарти; знайдіть причину I/O.
Завдання 13: ZFS: помітити тиск синхронізації transaction group (поширена причина «чому записи стрибкоподібні»)
cr0x@server:~$ sudo zpool iostat -v 1 3
capacity operations bandwidth
pool alloc free read write read write
tank 1.20T 2.40T 820 2400 3.2M 26.8M
mirror 1.20T 2.40T 820 2400 3.2M 26.8M
nvme0n1 - - 410 1200 1.6M 13.4M
nvme1n1 - - 410 1200 1.6M 13.4M
Значення: Добре для пропускної здатності й операцій, але не обов’язково для затримки. Якщо записи виглядають нормальними, а затримка додатку жахлива, можливо, ви застрягли на sync-записах, contention для SLOG або на txg sync-сплесках.
Рішення: Якщо підозрюєте поведінку sync, перевірте робоче навантаження та властивості dataset.
cr0x@server:~$ sudo zfs get sync,logbias,compression,recordsize tank/app
NAME PROPERTY VALUE SOURCE
tank/app sync standard local
tank/app logbias latency local
tank/app compression lz4 local
tank/app recordsize 128K local
Рішення: Якщо додаток робить багато дрібних sync-записів (БД, журнали), перевірте дизайн SLOG і чи дійсно потрібні ці sync-семантики. Не вмикайте sync=disabled в продакшені, якщо вам не подобається пояснювати втрату даних юристам.
Завдання 14: Безпечно бенчмаркуйте з fio (не обманюйте себе)
cr0x@server:~$ sudo fio --name=randread --filename=/srv/app/.fiotest --size=2G --rw=randread --bs=4k --iodepth=1 --numjobs=1 --direct=1 --time_based --runtime=30 --group_reporting
randread: (groupid=0, jobs=1): err= 0: pid=31022: Fri Jan 10 14:01:02 2026
read: IOPS=18.2k, BW=71.0MiB/s (74.4MB/s)(2131MiB/30001msec)
lat (usec): min=55, max=3280, avg=68.11, stdev=22.50
clat percentiles (usec):
| 1.00th=[ 58], 50.00th=[ 66], 95.00th=[ 82], 99.00th=[ 120]
Значення: Це вимірює 4k випадкове читання при QD=1, що часто відповідає реальним паттернам додатку краще, ніж «велике послідовне читання». Перцентили показують хвостову поведінку.
Рішення: Якщо ваш додаток чутливий до затримки й fio показує ріст p99 під навантаженням, ви виявили стелю затримки сховища. Плануйте пом’якшення (кеш, шардінг, швидше медіа, кращу батчацію) замість налаштування не пов’язаних шарів.
Завдання 15: Визначте глибину черги блочного шару та планувальник
cr0x@server:~$ cat /sys/block/nvme0n1/queue/nr_requests
1024
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
Значення: nr_requests впливає на те, наскільки глибокими можуть бути черги. Вибір планувальника впливає на хвостову затримку, справедливість і пропускну здатність.
Рішення: Якщо хвостова затримка — болюча тема, розгляньте планувальник, що краще поводиться під конкуренцією, і зменшіть черги там, де це доцільно. Не копіюйте рішення механічно; тестуйте на своєму навантаженні.
Завдання 16: Виявлення троттлінгу (контейнери і cgroups: невидиме вузьке місце)
cr0x@server:~$ cat /sys/fs/cgroup/cpu.stat
usage_usec 92833423211
user_usec 81223311200
system_usec 11610112011
nr_periods 124112
nr_throttled 32110
throttled_usec 8823122210
Значення: Якщо nr_throttled і throttled_usec ростуть, ваше навантаження CPU-тротлиться cgroups. Це виглядає як «нам потрібно більше CPU», але насправді це «ми встановили ліміт».
Рішення: Налаштуйте ліміти/requests CPU, зменшіть стрибкоподібність або перемістіть навантаження. Не купуйте залізо, щоб компенсувати власні квоти.
Жарт №2 (короткий, по темі): Якщо ваше вузьке місце — «база даних», вітаю — ви відкрили гравітацію.
Цікаві факти та трохи історії (щоб ви перестали повторювати старі помилки)
- Закон Амдала (1967) формалізував, чому пришвидшення однієї частини системи має спадну віддачу, якщо решта залишається серіальною. Це математика за «ми подвоїли CPU і отримали +12% швидкості».
- Закон Літтла (опублікований 1961) став одним із найпрактичніших інструментів для роздумів про затримку та конкуренцію в системах ще до появи сучасної обсервабіліті.
- Крива «utilization vs latency» вивчали в теорії черг десятиліттями; сучасні інциденти «p99 пішов вгору» — це ця крива, а не загадка.
- Плутанина щодо IOwait існує вже давно: Linux враховує завдання в uninterruptible sleep у load average. Люди досі повідомляють одне одному про «high load», яке насправді означає очікування диска.
- Ethernet уже давно швидший, ніж багато стеків сховища; практично програмний оверхед (kernel paths, TLS, серіалізація) часто виявляється вузьким місцем раніше за лінійку мережі.
- NVMe значно покращив паралелізм, пропонуючи кілька черг і малий оверхед порівняно з SATA/AHCI, але це також зробило легшим приховування латентності за глибокими чергами — поки хвостова затримка не вдарить.
- «Free Lunch Is Over» (середина 2000-х) стосувалося не тільки CPU; воно змусило софт confront паралельні вузькі місця: локи, contention кешу й витрати на координацію.
- Кеші записів і бар’єри мають довгу історію торгівлі надійністю за швидкість. Багато катастрофічних «перемог у продуктивності» були просто невиявленими налаштуваннями, що призводили до втрати даних.
- Хвостова затримка стала мейнстрімом у великомасштабних веб-сервісах, бо середні значення не пояснювали больові точки користувачів. Перехід індустрії на p95/p99 — одне з небагатьох культурних поліпшень, яке можна виміряти.
Три корпоративні міні-історії з практики
Міні-історія 1: Інцидент через хибне припущення
Симптом був класичним: затримка API збільшилася вдвічі, потім утричі, і почав різко падати error budget. On-call команда побачила високі load average на вузлах додатку і вирішила «CPU завантажений». Вони масштабували деплоймент, і нічого не покращилося. Більше підів — та сама проблема.
Хибне припущення було тонким і поширеним: вони трактували load average як «використання CPU». Насправді у вузлах було достатньо простою CPU. Load був роздутий потоками, що застрягли в uninterruptible sleep. Вони чекали на диск.
Коренева причина жила в іншому шарі: зміни в стратегії логування додатку. «Безпечне» оновлення перемкнуло логування з буферизованого на синхронні записи для «аудитної точності». Файлові системи були на мережевих томах із пристойною пропускною здатністю, але посередньою синхронною затримкою. За пікового трафіку кожен запит робив хоча б один sync-запис. Затримка пішла вертикально, бо вузьким місцем була не пропускна здатність, а fsync-затримка та черги.
Виправлення не було «додати CPU». Логи перенесли в асинхронний конвеєр з надійним буферуванням і ізолювали операції запису від потоків обробки запитів. Також додали суворий бюджет на sync-операції в критичному шляху. Постмортем включив нове правило: якщо ви торкаєтесь fsync, мусите додати p99-метрики з продакшн-подібним сховищем.
Міні-історія 2: Оптимізація, що відкотилася
Платформена команда хотіла швидше обробляти батчі. Вони помітили, що job інжесту в об’єктне сховище «недовантажує мережу» й вирішили підкрутити конкаренцію: більше воркерів, більші буфери, глибші черги. Пропускна здатність зросла в staging. Люди потиснули руки. Зміни випустили в п’ятницю, бо чому б і ні.
У продакшені job досяг вищої пропускної здатності — коротко. Потім інша частина флоту почала страждати. Хвостова затримка API підскочила в інших сервісах. Підключення до БД накопичилися. Повтори вибухнули. Канал інцидентів перетворився на фестиваль скріншотів.
Що сталося: оптимізація не-обмеження доклала тиск до спільних ресурсів: буфери top-of-rack, NIC-черги хоста і, найважливіше, бюджет запитів бекенду сховища. Затримко-чутливі сервіси тепер конкурували з фонною задачею без backpressure і правил справедливості.
Фінальне рішення було не «обмежити батч до одного потоку». Воно полягало в явному плануванні й ізоляції: cgroup-формування мережі, окремі пули вузлів і лімітування запитів проти бекенду. Також ввели SLO-aware конкаренцію: задача знижує інтенсивність, коли p99 клієнтського трафіку погіршується. Це не модно. Це доросло.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Компанія з міксованим навантаженням — БД, черги та пошуковий кластер — мала звичку, яка виглядала не дуже на архітектурних оглядах: щоквартально вони запускали контрольовані навантажувальні тести в продакшені в неробочий час і записували базові «точки згину» для ключових сервісів. Ті самі скрипти. Ті самі вікна вимірювань. Ті самі дашборди. Ніяких геройств.
Одного вівторка клієнтська затримка піднялася. Не повний аут, а поступове погіршення: p99 пішов з «нормально» до «скаржаться люди». Перший респондер відкрив базовий звіт з двотижневої давнини і помітив: система досягала точки згину при на 20% меншому трафіку, ніж раніше. Це означало, що ефективна ємність скоротилась.
Оскільки були базові показники, вони не витрачали час на суперечки. Порівняли iostat і PSI з базою і побачили новий патерн: вищі затримки записів і глибші черги на підмножині вузлів. Ці вузли мали однакову прошивку NVMe — нещодавно оновлену в рамках «рутинного обслуговування». Оновлення змінило поведінку управління живленням і внесло латентні сплески під стійкими записами.
Вони відкотили прошивку на ураженому флоті, і точка згину повернулась. Ніякої нової архітектури. Ніяких ігор у звинувачення. Просто спокійний rollback, підкріплений доказами. Дія в постмортемі була приємно нудною: зафіксувати версії прошивок і додати перевірку регресії латентності в процедури обслуговування.
Поширені помилки: симптом → коренева причина → виправлення
1) Високий load average → «Нам потрібен більше CPU» → ви масштабували і нічого не змінилося
Симптом: Високий load average, користувачі скаржаться, CPU здається «завантаженим» на швидкий погляд.
Коренева причина: Завдання застрягли в I/O wait (uninterruptible sleep). Load включає їх.
Виправлення: Підтвердіть через vmstat (b, wa), iostat -x, PSI I/O. Розв’яжіть затримку сховища або зменшіть синхронні I/O в шляху запиту.
2) %util диска на 100% → «Диск вичерпано» → ви купуєте швидші диски і все ще бачите сплески затримки
Симптом: %util забито, await високий, глибина черги велика.
Коренева причина: Диск може бути нормальним; шаблон навантаження патологічний (дрібні sync-записи, fsync-шторм, журналювання), або черга занадто глибока й підсилює хвостову затримку.
Виправлення: Охарактеризуйте розмір I/O, частоту sync і конкаренцію. Додайте батчування, виведіть fsync з критичного шляху, налаштуйте планувальник/глибину черги і валідуйте за допомогою fio, що імітує I/O додатку.
3) «Мережа в порядку, у нас є пропускна здатність» → хвостова затримка все одно жахлива
Симптом: Інтерфейс Mbps нижче лінійки, але таймаути і великий p99.
Коренева причина: Відкиди/ретрансміти, bufferbloat або мікрострибки. Графіки пропускної здатності брешуть, коли пакети пересилаються повторно.
Виправлення: Перевірте netstat -s, ip -s link, статистику qdisc. Застосуйте pacing, справедливе чергування або зменшіть паралелізм сплесків.
4) «Ми оптимізували hit rate кешу» → тиск пам’яті тихо вбиває продуктивність
Симптом: Зростання використання кешу, але стрибки затримки й системний час CPU зростає.
Коренева причина: Зростання кешу викликає reclaim, page faults або паузи compact; система витрачає час на управління пам’яттю, не на обслуговування запитів.
Виправлення: Вимірюйте PSI memory, major faults і reclaim. Обмежте кеші, розмірно їх налаштуйте й залиште резерв для ядра й робочого набору.
5) «Більше потоків = більше пропускної здатності» → пропускна здатність застопорюється, а затримка вибухає
Симптом: Зросла конкуренція, p99 погіршився, CPU не повністю завантажений.
Коренева причина: Ви наситили спільний даунстрім (DB, диск, лок або пул). Більша конкуренція лише заглублює черги.
Виправлення: Знайдіть ресурс-обмежувач, додайте backpressure і зменшіть конкаренцію до точки згину. Використовуйте відкидання навантаження замість нескінченного очікування.
6) «Увімкнули компресію» → CPU в нормі, але пропускна здатність впала
Симптом: Менше записів на диск, але пропускна здатність запитів падає і хвостова затримка зростає.
Коренева причина: Компресія переміщає вузьке місце в CPU-кеш, пропускну здатність пам’яті або однопотокові стадії компресії; також змінює патерн I/O.
Виправлення: Проведіть бенчмарк з представницькими даними. Слідкуйте за гарячими ядрами. Використайте швидші алгоритми, підберіть розмір записів/блоків або компресуйте лише холодні шляхи.
7) «Вимкнули fsync заради швидкості» → все швидко, поки не стало не так
Симптом: Велике «поліпшення» продуктивності після зміни конфігурації; пізніше — корупція або втрачені транзакції після збою.
Коренева причина: Семантику надійності обміняли на затримку без бізнес-рішення.
Виправлення: Поверніть правильну надійність, спроєктуйте правильне буферування/логування і документуйте вимоги до надійності. Якщо доводиться послабити надійність, робіть це явно й публічно.
Чек-листи / покроковий план
Покроково: встановіть реальний ліміт (у спокійний час)
- Виберіть одне навантаження, яке має значення (ендпоінт, job, запит) і визначте успіх (p95/p99, пропускна здатність, рівень помилок).
- Прослідкуйте критичний шлях: клієнт → сервіс → даунстріми (БД, кеш, сховище, мережа).
- Інструментуйте черги: глибина worker pool, очікування в пулі БД, kernel PSI, глибина черги диска.
- Запустіть контрольований ріст навантаження: поступово збільшуйте пропозицію; тримайте паузи на кожному кроці.
- Знайдіть згин: де пропускна здатність перестає масштабуватися і хвостова затримка прискорюється.
- Запишіть базові показники: точка згину, p99, метрики насичення і головні внески.
- Встановіть захисні огорожі: ліміти конкуренції, таймаути, бюджети повторів і backpressure.
- Перевіряйте після змін: деплойти, оновлення kernel/прошивки, зміни типу інстансу, міграції сховища.
Чек-лист для інциденту: уникайте трясіння і здогадок
- Зупиніть кровотечу: якщо затримка вибухає, обмежте конкуренцію або увімкніть load shedding. Захистіть систему перш за все.
- Підтвердіть охоплення: які сервіси, які вузли, які орендарі, які регіони.
- Перевірте сигнали помилок: таймаути, повтори, TCP ретрансміти, I/O помилки.
- Перевірте черги: довжина черг додатку, очікування в пулі БД, run queue, глибина черги диска, PSI.
- Виберіть одну гіпотезу вузького місця і зберіть 2–3 вимірювання, які можуть її спростувати.
- Змінюйте одну річ за раз, якщо ви не в режимі екстреного утримання.
- Пишіть хронологію під час подій. Минуле «я» ненадійний свідок.
Правила прийняття рішень (суб’єктивні, бо ви зайняті)
- Якщо p99 затримка зростає, а пропускна здатність стоїть на місці — у вас черги. Знайдіть чергу, не додавайте роботи.
- Якщо помилки ростуть (таймаути/повтори), сприймайте продуктивність як надійність. Зменшіть навантаження; виправлення обмеження — друге.
- Якщо затримка пристрою зросла, але пропускна здатність помірна, підозрівайте sync-записи, writeback або прошивку/енергозбереження.
- Якщо CPU низький, але затримка висока, підозрівайте очікування: I/O, локи, пули, мережеві ретрансміти, GC.
- Якщо одне ядро заштовхнуте, припиніть масштабування по горизонталі і знайдіть серійну секцію або точку contention.
Часті запитання
1) Як швидко зрозуміти, що я CPU-bound або I/O-bound?
Використайте vmstat 1 і iostat -x 1. Високий wa і заблоковані задачі (b) вказують на I/O wait. Високий r з низьким wa натякає на насичення CPU або чергу runnable.
2) Чи завжди погано високий диск %util?
Ні. Це може означати, що пристрій зайнятий (що нормально) або насичений з глибокими чергами (погано). Дивіться aqu-sz і await. Зайнятий диск з низьким await — здоровий; зайнятий диск з високим await — вузьке місце.
3) Чому додавання потоків погіршує затримку, навіть якщо CPU проста?
Бо ви, ймовірно, насичуєте даунстрім-ресурс (підключення до БД, диск, лок або пул) і створюєте довші черги. Просто свободний CPU не гарантує запасу; це може означати, що ви заблоковані в іншому місці.
4) У чому різниця між пропускною здатністю як обмеженням і вузьким місцем хвостової затримки?
Обмеження пропускної здатності ставить верхню межу завершеної роботи; вузькі місця хвостової затримки псують досвід користувача першими і можуть з’являтися значно раніше за максимальну пропускну здатність. Хвостові проблеми часто викликані чергуванням, GC, джитером або сплесками contention.
5) Чи може кеш «виправити» вузьке місце назавжди?
Кеш може перемістити вузьке місце, зменшивши роботу, але створює нові режими відмов: stampede кешу, тиск пам’яті і складнощі консистентності. Ставте кеш як інженерну систему з межами й backpressure, а не як чарівну ковдру.
6) Як виміряти «точку згину» безпечно в продакшені?
Поступово нарощуйте навантаження у вікно з низьким ризиком, ізолюйте тестовий трафік, якщо можливо, і обмежте конкуренцію. Слідкуйте за p95/p99, помилками і метриками насичення. Зупиніться, коли помилки або хвостова затримка починають прискорюватись.
7) Чому бенчмарки показують відмінно, а в продакшені повільно?
Ваш бенчмарк, ймовірно, вимірює не те: послідовний I/O замість випадкового, QD=32 замість QD=1, гарячий кеш проти холодного, або ігнорує sync-семантику. Також в продакшені є конкуренція й шумні сусіди. Бенчмаркуйте навантаження, яке ви реально запускаєте.
8) Коли масштабувати вгору, а коли оптимізувати?
Масштабуйте вгору, коли ясно насичення ресурсу, яке масштабуються лінійно з потужністю (CPU для паралельних обчислень, пропускна здатність для стрімінгу). Оптимізуйте, коли вузьке місце — координація, хвостова затримка або contention; залізо рідко це вирішує.
9) Який найшвидший спосіб уникнути істерії щодо вузьких місць у команді?
Угодьте про невеликий набір стандартних вимірювань і порядок триажу. Змушуйте людей приносити докази: «iostat показує 20ms write await і чергу 40» краще, ніж «мені здається, це диск». Запишіть це в шаблон інциденту.
Наступні кроки, які ви справді можете зробити
Якщо хочете менше пожеж продуктивності, припиніть трактувати вузькі місця як фольклор. Розглядайте їх як вимірювані обмеження.
- Виберіть одну критичну користувацьку подорож і інструментуйте перцентили затримки, помилки та конкуренцію.
- Додайте метрики черг (черги додатку, пули, PSI, черги пристроїв). Вузькі місця ховаються у чеканні, а не у виконанні.
- Зніміть базову точку згину під контрольованим навантаженням. Тримайте її в операційному посібнику. Оновлюйте після суттєвих змін.
- Під час інцидентів виконуйте плейбук швидкої діагностики і збирайте фальсифікуючі докази перед змінами.
- Впровадьте backpressure (обмеження конкуренції, бюджети повторів, таймаути). Система з backpressure падає прогнозовано, а не театрально.
Реальний ліміт не є таємницею. Він просто ховається за вашими припущеннями, чекаючи, поки ви його виміряєте.