Чому монстр кешу може перемогти «швидший» CPU

Було корисно?

Ви придбали «швидший» CPU. Вищі частоти, новіша мікроархітектура, технічні характеристики, що звучать гордо.
А в продукції затримка стала гіршою.

Це не невдача. Це фізика плюс купа оптимістичних припущень про те, що означає «швидший».
У реальних системах CPU зазвичай не чекає самого себе. Він чекає на дані. Коли дані не в кеші,
CPU перетворюється на дуже дорогий обігрівач, який зрідка щось робить.

Що насправді означає «монстр кешу»

«Монстр кешу» — це не образа. Це комплімент системі (або чипу), яка виграє, тримаючи робочий набір
ближче до ядер. Монстр може бути:

  • CPU з великим кешем останнього рівня (LLC/L3), високою пропускною здатністю та хорошим префетчером.
  • Платформа з швидкими каналами пам’яті та правильною топологією NUMA.
  • Додаток, який поводиться так, ніби поважає локальність (і не кидає вказівники як конфетті).
  • Стек зберігання з агресивним кешуванням (кеш сторінок, буфер кеш бази даних, ZFS ARC, Redis, CDN).

Секрет у тому, що «швидший CPU» часто означає «може завершувати більше інструкцій за секунду якщо дані вже там».
Якщо система часто промахується в кешах, вас не обмежує пікова обчислювальна потужність. Вас обмежує швидкість
отримання даних. А отримання даних у сучасній машині — це багатоступеневий бюрократичний процес.

Уявіть це як кухню. Ваш шеф-кухар може швидше різати (вищий IPC, вищі такти), погодьтесь. Але якщо інгредієнти
ще десь на вантажівці (cache miss), кухня буде дуже тиха.

Ієрархія пам’яті: куди йдуть час і цикли

Затримка — це справжня валюта

Інженери систем вчаться цьому не з простими словами: пропускна здатність робить заголовки, затримка пише ваші інцидентні звіти.
Один промах кешу може зупинити ядро на стільки, що ваш ефектний «швидший» CPU стає неважливим.
Це не теорія — саме тому інженерія продуктивності здебільшого вивчає очікування.

У гру вступає «сходи від близького до далекого»:

  • Регістри: практично миттєво.
  • L1 кеш: крихітний і надзвичайно швидкий.
  • L2 кеш: більший, але все ще швидкий.
  • L3/LLC: спільний, ще більший і повільніший.
  • DRAM: значно повільніший, і доступний для багатьох голодних ядер.
  • Пам’ять віддаленого NUMA-нодa: ще повільніше.
  • SSD/NVMe: порядок величини повільніше за DRAM.
  • Мережеве сховище: тепер ви торгуєтеся з географією.

Завдання CPU — виконувати інструкції. Ваше завдання — годувати ці інструкції даними з якомога ближчого рівня.
Бо кожен щабель вниз ви платите простоями циклів, зірваними хвостовими затримками та підозрілими графіками «завантаження CPU»,
які виглядають нормальними доти, поки користувачі не почнуть кричати.

Кеші — це не просто «мала RAM»

Кеші будуються навколо локальності:

  • Темпоральна локальність: якщо ви використали дані, можливо, використаєте їх ще незабаром.
  • Просторова локальність: якщо ви звернулися за цією адресою, можливо, скоро звернетеся до сусідніх адрес.

Апаратні кеші працюють з лініями кешу (зазвичай 64 байти). Це означає, що ви не тягнете одне ціле число, а й сусідів,
подобається це вам чи ні.

Ось чому «швидший CPU» часто програє «кращому розташуванню». Трохи повільніший CPU, який отримує кеш‑хіти, може
обіграти швидший, який постійно трясе LLC і проводить життя в DRAM круговерті.

Жорстка арифметика промахів

CPU може завершувати кілька інструкцій за цикл. Сучасне out-of-order виконання ховає частину затримки, але лише до
моменту, коли ядро може знайти іншу незалежну роботу. Коли навантаження — перехід по вказівниках, велика кількість гілкувань
або сувора серіалізація (поширено в базах даних, аллокаторах і багатьох високорівневих рантаймах), CPU швидко вичерпує
незалежну роботу. Тоді ви глухо стоїте. Жорстко.

Також тому «середня затримка» може брехати. Промах кешу додає не просто трохи часу; він може перетворити запит на
далеку викидну величину. Користувачі не складають тикети за p50. Вони складають їх, коли «вона зависла і потім оновилася».

Чому «швидший» CPU програє в продакшені

1) Ваше навантаження не обчислювально обмежене

Багато продакшн‑навантежень є обмеженими даними:

  • Бази даних, що читають індекси й переходять по вказівниках у B-деревах.
  • Lookup‑запити ключ/значення з випадковими паттернами доступу.
  • Мікросервіси, що парсять JSON, виділяють пам’ять, шукають у hashmap і логують.
  • Пайплайни спостереження, що стискають, пакетують і відправляють події з важкими метаданими.

CPU не «зайнятий» арифметикою. Він зайнятий очікуванням пам’яті, блокувань, завершення I/O, власності ліній кешу,
аллокатора, поки знаходиться вільне місце, або поки збирач сміття не зупинить світ і не прибере сміття.

2) «Швидші» CPU часто міняють кеш на ядра або частоти

Виробники постійно так роблять. Ви побачите CPU з:

  • Більшою кількістю ядер, але менше кешу на ядро.
  • Вищими boost‑частотами, але суворішими межами потужності (тому при тривалому навантаженні частота падає).
  • Різною топологією кешу (спільні LLC‑слайси, інша поведінка інтерконекту).

Якщо ваш додаток потребує кешу, то CPU з менше ядрами, але більшим LLC може перевершити «більший» CPU,
який виганяє ваш робочий набір у DRAM. Ось і є монстр кешу: не гламурно, але ефективно.

3) NUMA і топологія — множники продуктивності (і руйнівники)

NUMA — це не академічна дрібниця. Це причина, чому ваш новий сервер «таємниче повільніший» за старий.
Якщо потоки працюють на одному сокеті, а пам’ять виділяється на іншому, ви платите штраф віддаленого доступу за майже кожен промах.
Тепер ваше «швидше» ядро CPU смакує дані через довшу соломинку.

Топологія також впливає на контенцію в спільному кеші. Розмістіть забагато галасливих сусідів в одному домені LLC — і ваш
показник хітів руйнується. CPU швидкий. Просто голодний.

4) Сховище — це остаточний промах кешу

Коли ви промахуєтеся в пам’яті і падаєте на сховище, ви вже не порівнюєте такти. Ви порівнюєте мікросекунди й мілісекунди.

Саме тут монстри кешу показують зуби: page cache, буферні пулли баз даних, ZFS ARC, об’єктні кеші, CDN‑edge.
Хіт кешу може перетворити «потребує читання з диска» в «повертає миттєво». Це не 10% пришвидшення. Це виживання.

5) Хвостова затримка карає нетерплячих

Швидші CPU допомагають p50, якщо ви обмежені обчисленнями. Кешування допомагає p99, якщо ви обмежені даними.
Продакшн здебільшого — це бізнес p99. Ваша ротація on‑call цьому підтвердження.

Один цитат, бо інженери заслуговують бодай одного хорошого:
У всіх є тестове середовище. Дехто настільки щасливий, що має окреме продакшн середовище.
— Анонімний афоризм операційників

Жарт №1: Купувати швидший CPU, щоб виправити кеш‑промахи, — це як купувати швидшу машину, бо ви постійно забуваєте, де припаркувались.

Цікаві факти та історичний контекст (те, що пояснює сучасний біль)

  1. «Стіна пам’яті» отримала назву в середині 1990‑х: швидкості CPU росли швидше за затримки DRAM,
    тому архітектори змушені були покладатися на кеші та префетчинг.
  2. Ранні CPU не мали кешу на кристалі: кеші перемістилися на кристал з ростом бюджету транзисторів, бо зовнішній кеш
    мав надто велику затримку.
  3. Лінії кешу існують, бо передача пам’яті блочна: вибірка 64 байтів амортизує оверхед шини, але також означає,
    що ви можете тягнути сусідів, які вам не потрібні.
  4. Асоціативність — це компроміс: вища асоціативність зменшує конфліктні промахи, але потребує більше енергії й складності.
    Реальні чипи роблять прагматичні компроміси, що проявляються як «таємничі» обриви продуктивності.
  5. Політики інклюзивності LLC мають значення: деякі дизайни зберігають копії верхніх рівнів кешу в LLC,
    що впливає на поведінку виведення і ефективну ємність кешу.
  6. Сучасні CPU використовують складні префетчери: вони вгадують майбутні звернення пам’яті. Коли вгадують — ви виглядаєте генієм.
    Коли ні — вони можуть забруднювати кеши і споживати пропускну здатність.
  7. NUMA став масовим із ростом мульти‑сокетних серверів: локальний доступ до пам’яті швидший за віддалений. Ігнорування цього —
    найшвидший спосіб перетворити «більше сокетів» на «більше смутку».
  8. В Linux page cache — це ключова функція продуктивності: саме тому другий прогін задачі може бути значно швидшим за перший —
    якщо ви не обходите кеш через Direct I/O.
  9. Сховище еволюціонувало від HDD до SSD і NVMe: затримки впали значно, але все ще значно повільніше за DRAM.
    Хіт кешу залишається іншою всесвітом у порівнянні з читанням зі сховища.

Режими відмов: як кеш і I/O кусають вас

Промахи кешу, що виглядають як «проблеми з CPU»

Класична пастка — бачити високий CPU і вважати, що потрібно більше CPU. Іноді так.
Часто — ні. Високе завантаження CPU може означати «активне виконання інструкцій» або «спінінг, простої, очікування пам’яті».
Обидва виглядають як «CPU зайнятий» для нефахового ока.

Промахи кешу проявляються як:

  • Низький інструкцій на цикл (IPC) при високому завантаженні CPU.
  • Вищі показники LLC‑промахів під навантаженням.
  • Пропускна здатність, що перестає масштабуватися з додаванням ядер.
  • Стрибки затримки, коли робочий набір переходить межу кешу.

Фальшиве шарінг і «ping‑pong» ліній кешу

Можна мати багато кешу й все одно програти, бо ядра б’ються за лінії кешу. Коли два потоки на різних ядрах
записують у різні змінні, що лежать у тій самій лінії кешу, лінія скаче між ядрами, щоб підтримати когерентність.
Вітаємо: ви створили розподілену систему всередині CPU.

Промахи TLB: прихований промах кешу

Translation Lookaside Buffers (TLB) кешують трансляції віртуальної адреси в фізичну. Коли там промах — ви платите за
додатковий page‑walk перед тим, як навіть тягнути дані. Навантаження з великими адресними просторами, випадковим доступом
або фрагментованими купами може перетворити тиск на TLB у затримку.

Промахи кешу сховища: page cache, буферні пули і read amplification

Промахи в page cache стають читаннями з диска. Читання з диска стають затримкою. І якщо ваш шар зберігання робить read amplification
(поширено в деяких copy‑on‑write файлових системах, шарах шифрування або при невірно вирівняних I/O), ви можете «промахуватися»
більше одного разу на запит.

Багато «апгрейдів CPU» провалюються, бо шлях зберігання є вузьким місцем, і CPU просто більше часу проводить у очікуванні, але швидше.

Жарт №2: Найшвидший шлях зробити систему повільнішою — оптимізувати її до того, що вона почне робити «корисну» роботу, якої ви не просили.

Швидкий план діагностики

Коли продуктивність погіршилася після апгрейду CPU (або будь‑якого «поліпшення апаратури»), не гадати. Пройдіть стек зверху вниз
і змусьте систему видати, на що вона чекає.

Перш за все: визначте, чи ви обмежені обчисленнями, пам’яттю чи I/O

  • Обмеження обчисленнями: високий IPC, високий user CPU, мало застоєних циклів, масштабування покращується з додаванням ядер.
  • Обмеження пам’яттю: низький IPC, високі промахи кешу/TLB, багато застоєних циклів, масштабування ранньо вирівнюється.
  • Обмеження I/O: є iowait, висока затримка зберігання, потоки блокуються на читаннях, сплески промахів page cache.

Другий крок: перевірте поведінку кешу й IPC під реальним навантаженням

  • Використовуйте perf для перегляду cycles, instructions, cache misses, stalled cycles.
  • Слідкуйте за LLC‑промахами і лічильниками пропускної здатності пам’яті, якщо доступні.
  • Шукайте різкі обриви при збільшенні QPS (робочий набір перевищує кеш).

Третій крок: перевірте локальність NUMA та поведінку частоти CPU

  • Підтвердіть, що навантаження прив’язане адекватно або принаймні не бореться з планувальником.
  • Перевірте, чи виділення пам’яті локальні для CPU, що виконує потік.
  • Переконайтеся, що ви не тримаєтеся на thermal/power throttling при тривалому навантаженні.

Четвертий крок: простежте промах кешу до стеку зберігання

  • Перевірте сигнали хіт/місс page cache (readahead, major faults).
  • Виміряйте розподіл затримок сховища, а не тільки пропускну здатність.
  • Підтвердіть глибину черги, планувальник та поведінку файлової системи (особливо CoW/RAID шари).

П’ятий крок: тільки після цього думайте про зміну CPU

Якщо ви обмежені обчисленнями — так, купуйте CPU. Якщо ви обмежені пам’яттю або I/O — спочатку виправте локальність, кешування й розміщення даних.
Це дешевше і зазвичай ефективніше.

Практичні завдання: команди, виводи, що вони означають і які рішення приймати

Це перевірки для продакшну. Запускайте їх під навантаженням у тесті або під час інциденту (обережно). Кожне завдання містить:
команду, прикладний вивід, що це означає, і наступне рішення.

Завдання 1: Перевірка частоти CPU та поведінки тротлінгу

cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)|Socket|Thread|NUMA|MHz'
Model name:                           Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz
CPU(s):                               64
Thread(s) per core:                   2
Socket(s):                            2
NUMA node(s):                         2
CPU MHz:                              1199.842

Що це означає: Якщо MHz значно нижче за базову/очікувану під навантаженням, можливо, є обмеження по енергії/термічні або консервативний governor.

Рішення: Перевірте governor і стійку частоту під навантаженням; не припускайте, що boost‑частоти реально працюють у продакшні.

Завдання 2: Підтвердіть governor CPU (performance vs powersave)

cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave

Що це означає: «powersave» може обмежувати частоту і збільшувати затримки при імпульсних навантаженнях.

Рішення: Якщо це вузол з чутливими затримками, перемкніть на «performance» (або налаштуйте політику платформи) під час тестування і порівняйте.

Завдання 3: Швидкий огляд черги виконання та iowait

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
 8  0      0 242112  81240 912340    0    0     0    12 6200 9800 62 10 22  6  0
10  1      0 240980  81240 910120    0    0    64   980 6900 11200 55 11 18 16  0
11  2      0 239500  81240 908400    0    0   128  1400 7200 11800 48 12 16 24  0

Що це означає: Високе r вказує на конкуренцію за CPU; високе wa і b — на очікування I/O. Цей приклад показує зростання iowait.

Рішення: Якщо iowait росте з навантаженням, не витрачайте час на апгрейд CPU — ідіть по шляху I/O.

Завдання 4: Виявлення заблокованих задач (часто I/O або блокування)

cr0x@server:~$ ps -eo state,pid,comm,wchan:32 | awk '$1 ~ /D/ {print}'
D 18423 postgres         io_schedule
D 19011 java             futex_wait_queue_me

Що це означає: Стан D вказує на незворотний сон, зазвичай I/O‑очікування (io_schedule) або іноді шляхи блокувань ядра.

Рішення: Якщо багато потоків у стані D, затримки сховища або контенція файлової системи — головний підозрюваний.

Завдання 5: Виміряти промахи кешу та IPC за допомогою perf stat (швидко, висока цінність)

cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-references,cache-misses,branches,branch-misses -I 1000 -- sleep 3
#           time             counts unit events
     1.000353504   12,804,112,210      cycles
     1.000353504    6,210,554,991      instructions              #    0.49  insn per cycle
     1.000353504      401,220,118      cache-references
     1.000353504      121,884,332      cache-misses              #   30.37% of all cache refs
     1.000353504    1,002,884,910      branches
     1.000353504       21,880,441      branch-misses             #    2.18% of all branches

Що це означає: IPC ≈0.49 і високий відсоток промахів кешу кричить про затримки через пам’ять. «Швидший» CPU цього не виправить, якщо лише він не має суттєво кращої поведінки кеш/пам’ять.

Рішення: Зменшити робочий набір, покращити локальність, зафіксувати потоки/пам’ять для NUMA або змінити структури даних перед купівлею обчислювальної потужності.

Завдання 6: Перевірити розміщення NUMA і використання віддаленої пам’яті (якщо є numactl)

cr0x@server:~$ numastat -p 18423
Per-node process memory usage (in MBs) for PID 18423 (postgres)
Node 0          8123.45
Node 1           421.12
Total           8544.57

Що це означає: Пам’ять переважно на Node 0. Якщо потоки працюють на Node 1, ви платитимете штрафи за віддалений доступ.

Рішення: Вирівняйте CPU affinity і політику пам’яті: зафіксуйте процеси або виправте оркестрацію так, щоб виділення й виконання були поруч.

Завдання 7: Подивитися, де виконуються потоки (affinity і міграція)

cr0x@server:~$ ps -eLo pid,tid,psr,comm | awk '$4=="postgres"{print $0}' | head
18423 18423  12 postgres
18423 18424   3 postgres
18423 18425  47 postgres
18423 18426  19 postgres

Що це означає: Розкидані по CPU потоки можуть бути нормою, або це може бути ворожим до кешу, якщо навантаження ділить гарячі структури і лінії кешу перескакують.

Рішення: Якщо бачите контенцію і погане масштабування, протестуйте прив’язку до підмножини ядер або одного NUMA‑вузла і виміряйте p99.

Завдання 8: Перевірити major page faults (промахи page cache, що потрапляють на сховище)

cr0x@server:~$ pidstat -r -p 19011 1 3
Linux 6.2.0 (server) 	01/10/2026 	_x86_64_	(64 CPU)

12:00:01     UID       PID  minflt/s  majflt/s     VSZ     RSS   %MEM  Command
12:00:02    1001     19011   1200.00     85.00 12488192 2380040  7.42  java
12:00:03    1001     19011   1180.00     92.00 12488192 2381208  7.42  java

Що це означає: majflt/s вказує на великі відмови сторінок, що вимагають диск‑I/O (якщо лише не в кеші). Це часто вбиває затримку.

Рішення: Додайте пам’ять, зменшіть churn файлів або забезпечте, щоб гарячі файли залишалися в кеші; розгляньте кешування/буферизацію на рівні додатку.

Завдання 9: Виміряти затримку пристрою зберігання і черги

cr0x@server:~$ iostat -x 1 3
Linux 6.2.0 (server) 	01/10/2026 	_x86_64_	(64 CPU)

Device            r/s     w/s   r_await   w_await  aqu-sz  %util
nvme0n1         820.0   120.0     6.10     8.40    5.40   98.0

Що це означає: r_await/w_await — середні затримки; aqu-sz і високий %util вказують на черги та насичення.

Рішення: Якщо пристрій насичений, зменшіть I/O (більше кешування), розподіліть навантаження по пристроях або змініть паттерн доступу, перш ніж торкатись CPU.

Завдання 10: Виявити гарячі файли й джерела I/O (швидко й просто)

cr0x@server:~$ sudo lsof -nP | awk '{print $1,$2,$4,$9}' | egrep 'postgres|java' | head
postgres 18423 mem /usr/lib/x86_64-linux-gnu/libssl.so.3
postgres 18423  12u /var/lib/postgresql/15/main/base/16384/2619
java     19011  45u /var/log/app/service.log

Що це означає: Показує, які файли відкриті. Якщо бачите лог‑файли або несподівані шляхи вгорі, ваша «проблема CPU» може бути «логування до смерті».

Рішення: Зменшіть синхронне логування, пакетування записів, перенесіть логи на швидший том або змініть рівень логування.

Завдання 11: Перевірити файлову систему та опції монтування (Direct I/O, barriers, atime)

cr0x@server:~$ findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS /var/lib/postgresql
/var/lib/postgresql /dev/md0 ext4 rw,relatime,data=ordered

Що це означає: Опції як relatime мають значення; інші (режими журналювання, бар’єри) впливають на затримку й безпеку.

Рішення: Налаштовуйте лише з чіткою причиною. Якщо міняєте параметри, що стосуються безпеки, щоб ганяти біночні тести, напишіть постмортем наперед.

Завдання 12: Спостерігати тиск на page cache і поведінку reclaim

cr0x@server:~$ grep -E 'pgscan|pgsteal|pgfault|pgmajfault' /proc/vmstat | head -n 8
pgfault 1283394021
pgmajfault 228103
pgscan_kswapd 901223
pgscan_direct 188004
pgsteal_kswapd 720111
pgsteal_direct 141220

Що це означає: Зростання pgscan_direct вказує на direct reclaim (процеси, що самі звільняють пам’ять), що може псувати затримку.

Рішення: Якщо direct reclaim високий під навантаженням, зменшіть тиск на пам’ять: додайте RAM, зменшіть churn кешу, налаштуйте ліміти пам’яті або виправте надмірні купи.

Завдання 13: Перевірити активність swap (повільна катастрофа)

cr0x@server:~$ swapon --show
NAME      TYPE SIZE USED PRIO
/dev/sda3 partition 16G  2G   -2

Що це означає: Використання swap не завжди зло, але стійке свапування під навантаженням — фабрика кеш‑промахів з додатковими кроками.

Рішення: Якщо затримка важлива, уникайте swap‑трешингу: обмежте пам’ять, виправте витоки, правильно налаштуйте купи або додайте пам’ять.

Завдання 14: Перевірити тиск по пропускній здатності пам’яті (з натяком через perf)

cr0x@server:~$ sudo perf stat -a -e stalled-cycles-frontend,stalled-cycles-backend,LLC-loads,LLC-load-misses -I 1000 -- sleep 3
#           time             counts unit events
     1.000339812    3,110,224,112      stalled-cycles-frontend
     1.000339812    8,901,884,900      stalled-cycles-backend
     1.000339812      220,112,003      LLC-loads
     1.000339812       88,440,120      LLC-load-misses           #   40.17% of all LLC loads

Що це означає: Backend‑стали і високі LLC‑промахи сильно натякають на обмеження по затримці/пропускній здатності пам’яті.

Рішення: Потрібні поліпшення локальності, а не голі GHz. Розгляньте зміни структур даних, шардінг або колокацію гарячих даних.

Завдання 15: Перевірити ефективність ZFS ARC (якщо на ZFS)

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:00:01   12K  2.8K     23   920   33  1.7K   61   180    6   96G   112G
12:00:02   13K  4.9K     37  2.1K   43  2.6K   53   200    4   96G   112G

Що це означає: Зростання ARC miss% під навантаженням означає, що ваш робочий набір не вміщується в ARC; читання йдуть на диск.

Рішення: Додайте RAM, налаштуйте ліміти ARC, додайте швидший вторинний кеш за потреби або зменшіть робочий набір (надмірні індекси, холодні дані).

Завдання 16: Перевірити мережеву затримку як «віддалений промах кешу» (сервісні виклики)

cr0x@server:~$ ss -tin dst 10.20.0.15:5432 | head -n 12
State Recv-Q Send-Q Local Address:Port  Peer Address:Port
ESTAB 0      0      10.20.0.10:44912   10.20.0.15:5432
	 cubic wscale:7,7 rto:204 rtt:1.8/0.4 ato:40 mss:1448 cwnd:10 bytes_acked:1234567 bytes_received:2345678

Що це означає: RTT каже, чи ваш «повільний запит» насправді — «мережева джиттер‑проблема». У розподілених системах мережа — це ще один рівень кешу, але дорогий.

Рішення: Якщо RTT/джиттер високі, зосередьтесь на колокації, пулах з’єднань або кешуванні результатів ближче до викликача.

Три корпоративні історії з поля бою

Міні‑історія 1: Інцидент через хибне припущення (апгрейд CPU, що погіршив p99)

Середня компанія керувала API з важкими пошуками. Команда мала просту історію: запити повільні, CPU високий, купимо швидші CPU.
Закупівля доставила нові сервери з вищими тактами й більшим числом ядер. Бенчмарки на одиночному вузлі виглядали добре.
Розгортання почалося. Протягом кількох годин p99 злетів, і ераргінтні бюджети почали танути.

Хибне припущення було тонке: вони вірили, що «більше CPU» покращить навантаження, яке вже обмежене локальністю пам’яті.
Нові сервери мали більше ядер на сокет, але менше LLC на ядро і трохи інші NUMA‑характеристики.
Під реальним мульти‑тенантним навантаженням робочий набір більше не вкладався в кеш. Частота промахів LLC зросла.
IPC впав. Графіки CPU все одно виглядали «завантаженими», що довше вводило всіх в оману.

Відповідь на інцидент була незграбною, коли інженери зрозуміли, що їхній новий «швидший» флот став краще
у виконанні очікувань. Вони відкотили трафік, потім відтворили регресію в контрольованому тесті з perf‑лічильниками.
Куриво було постійне зростання LLC load misses і backend stalled cycles при тому самому QPS.

Виправлення не полягало в скасуванні закупівлі (це вже було зроблено), а в зміні розміщення й зменшенні перехресної контенції.
Вони прив’язали сервіс до меншої кількості ядер на NUMA‑вузол і запустили більше реплік замість спроби «використати всі ядра».
Це було контрінтуїтивно, але відновило p99. Пізніше вони переробили структури даних, щоб краще поважати кеш і зменшили переходи по вказівниках.

Пункти постмортему, що мали значення: тести прийняття продуктивності повинні включати метрики промахів кешу/IPC, а не тільки пропускну здатність.
«Завантаження CPU» саме по собі — це індикатор настрою, а не діагностика.

Міні‑історія 2: Оптимізація, що повернула удар (Direct I/O: page cache не був лиходієм)

Інша компанія запускала пайплайн, що інгестив події, писав їх на диск і періодично робив компактифікацію.
Вони бачили тиск на пам’ять і вирішили, що Linux page cache «краде» RAM у застосунку.
Інженер перемкнув опцію: Direct I/O для записів і читань, щоб «не забруднювати кеш».

Графіки виглядали гарно перший день. Використання пам’яті стабілізувалося. Потім почалися тривоги з затримок.
Не скрізь — тільки в найгірших місцях: компакти і певні шляхи читання, що залежали від повторного читання свіжих даних.
З Direct I/O ці читання обходили page cache повністю. Кожне повторне читання ставало операцією сховища.
NVMe швидкий, але не «як RAM, що вдає диск».

Бумеранг приніс додаткові проблеми. Без згладжування page cache I/O стало більш імпульсним.
Глибина черг стрибнула. Хвостові затримки теж. CPU й далі не був вузьким місцем; він просто проводив більше часу в iowait.
Команда оптимізувала графіки пам’яті ціною клієнт‑видимої латентності.

Остаточне вирішення було нудним: відкотили Direct I/O для гарячих шляхів читання, тримали його лише там, де дані справді холодні або послідовні,
і задали розумні cgroup‑ліміти пам’яті, щоб page cache не могла задушити процес.
Також додали невеликий кеш на рівні додатку для метаданих, щоб уникнути повторних випадкових читань.

Урок: «обходити кеші» не є стратегією продуктивності. Це зброя. Користуйтеся нею тільки якщо розумієте, що саме постраждає.

Міні‑історія 3: Нудна, але правильна практика, що врятувала день (вимір робочого набору і планування кешу)

Фінансова контора мала гібрид batch + API навантаження. Вони планували оновлення: нове покоління CPU, той самий об’єм RAM,
той самий сховище. SRE‑команда наполягла на попередньому профайлінгу: виміряти розмір робочого набору, показники хітів кешу і поведінку NUMA
під час пікового тижня, а не пікової години.

Це не було популярно, бо відкладало проєкт. Потрібно було збирати perf‑статі, показники page fault і гістограми затримки сховища.
Також потрібен був load test, що схожий на продакшн, а не на рекламний брошурний бенчмарк.
Команда все одно наполягла — їх вже не раз підпалювали.

Профайл показав передбачуваний обрив: коли датасет виріс понад певну межу, хіт‑рейт буферного кешу впав і читання влучали в диск.
p99 вибухнув. Поточна система виживала, бо мала трохи більший ефективний кеш (буфер пул бази + OS cache), ніж планована конфігурація.
«Швидший» CPU не мав значення, коли промахи кешу почали падати на диск.

Вони підкоригували план оновлення: більше RAM на вузол, трохи менше ядер і чітка стратегія NUMA‑розміщення.
Також запланували рутину, щоб найгарячіші партиції залишались у кеші в робочі години.
День релізу пройшов спокійно — що є найвищим компліментом в операціях.

Урок: нудні вимірювання перемагають захопливу заміну. Найкращі інциденти — ті, що ніколи не трапляються.

Типові помилки (симптом → причина → виправлення)

1) Симптом: CPU високий, пропуск — стабільний

Причина: Навантаження обмежене пам’яттю; низький IPC через промахи кешу або backend‑стали.

Виправлення: Використовуйте perf, щоб підтвердити низький IPC і високі LLC‑промахи. Зменшіть робочий набір, покращіть локальність даних, шардуйте або виберіть CPU з більшим кешем на ядро.

2) Симптом: p99 погіршився після роллауту «швидшого CPU»

Причина: Зміни в топології кешу/LLC на ядро; зміни NUMA‑розміщення; відмінності в префетчері; більше контенції на сокеті.

Виправлення: Порівняйте perf‑лічильники старого й нового обладнання під ідентичним навантаженням. Закріпіть на NUMA‑вузлах, зменшіть шаринг ядер, перегляньте розмір інстансу і розміщення.

3) Симптом: Багато iowait, але диски не «завантажені» по пропускній здатності

Причина: Насичення затримки: дрібні випадкові I/O, високе чергування або хвостова затримка сховища; пропускна здатність це ховає.

Виправлення: Використовуйте iostat -x і дивіться await і aqu-sz. Зменшіть випадкові читання через кешування, виправте запити/індекси або додайте пристроїв/резерву IOPS.

4) Симптом: Продуктивність падає при додаванні ядер

Причина: Контенція спільного кешу, контенція блокувань, фальшиве шарінг або насичення пропускної здатності пам’яті.

Виправлення: Масштабуйте вшир, а не вгору; закріпіть потоки; усуньте фальшиве шарінг; зменшіть загальний мутований стан; вимірюйте з perf і flame graphs.

5) Симптом: «Оптимізація» зменшує використання пам’яті, але збільшує затримку

Причина: Обхід кешів (Direct I/O), зменшення буферних пулів або агресивне витіснення призводять до читань зі сховища.

Виправлення: Відновіть кешування для гарячих шляхів, правильно встановіть пам’ять і вимірюйте хіт‑рейти кешу та major faults.

6) Симптом: Періодичні стрибки затримки кожні кілька секунд/хвилин

Причина: Пауза GC, цикли витіснення кешу, компактифікація або фонове звільнення пам’яті (kswapd/direct reclaim).

Виправлення: Перевірте majflt, сигнали reclaim у vmstat і логи GC. Зменшіть churn алокацій, налаштуйте heap, додайте пам’ять або рознесіть компакти по часу.

7) Симптом: Одиночний хост повільніший за ідентичних сусідів

Причина: Різні налаштування BIOS, політика енергоспоживання, мікрокод, наповнення каналів пам’яті або «шумний сусід», що насичує LLC/пам’ять.

Виправлення: Порівняйте lscpu, governor, NUMA і perf‑статистику між хостами. Уніфікуйте прошивки та kernel‑налаштування; ізолюйте галасливі навантаження.

8) Симптом: Високий system CPU, низький user CPU, «нічого» в профілі додатку

Причина: Накладні витрати ядра: page faults, тиск мережевого стека, перемикання контекстів або churn метаданих файлової системи.

Виправлення: Використовуйте vmstat, pidstat, perf top. Зменшіть системні виклики (пакетуйте), налаштуйте логування, усуньте churn файлів і випадкові точки синхронізації.

Чеклісти / покроковий план

Покроково: як вирішити, чи кеш перемагає CPU для вашого навантаження

  1. Зберіть профіль, схожий на продакшн: мікс QPS, паралелізм, розмір датасету і p50/p95/p99 затримки.
  2. Виміряйте IPC і промахи кешу під навантаженням: perf stat (cycles, instructions, LLC misses, stalled cycles).
  3. Виміряйте розподіл затримок сховища: iostat -x і таймінги на рівні застосунку.
  4. Виміряйте major faults: pidstat -r і /proc/vmstat; підтвердіть, чи читання потрапляють на диск.
  5. Підтвердіть NUMA‑розміщення: numastat, розміщення потоків і політика affinity.
  6. Перевірте масштабування: запустіть на 1, 2, 4, 8, N ядрах і дивіться, чи масштабування лінійне або спадає.
  7. Змінюйте одну річ за раз: прив’язка, розмір буферного пулу, розмір кешу, розташування даних — потім ретестуйте.
  8. Тільки після цього вирішуйте про апарат: більше кешу на ядро, більше RAM, швидша пам’ять або більше вузлів.

Операційний чекліст: перед роллаутом «апгрейду CPU»

  • Базові perf‑лічильники (IPC, LLC misses, stalled cycles) на старому обладнанні.
  • Підтвердження налаштувань BIOS: політика потужності/продуктивності, SMT, інтерлівання пам’яті.
  • Підтвердження паритету kernel і мікрокоду між старим і новим обладнанням.
  • Прогін навантаження з реальним розміром датасету і реалістичною skew‑моделлю доступу.
  • Порівняйте хвостові затримки, а не тільки пропускну здатність.
  • Плануйте відкат, що не потребує зборів і нарад.

Чекліст по сховищу/кешуванню: як зробити кешування на вашу користь

  • Знайте розмір вашого робочого набору (гарячі дані) і порівняйте з RAM/буферними кешами.
  • Перевагу давайте послідовному доступу, коли можливо; випадковий I/O — це податок.
  • Тримайте індекси й гарячі метадані в пам’яті; диск — для холодної істини, а не для гарячих запитів.
  • Виміряйте хіт‑рейти кешу й швидкість витіснення; не гадіть.
  • Не «оптимізуйте», вимикаючи безпеку (бар’єри запису, журналювання), якщо не готові нести відповідальність за втрату даних.

FAQ

1) Наскільки великий вплив має кеш CPU, чи це просто драма перфоманс‑фанатів?

Це суттєво. Багато реальних навантажень обмежені затримкою пам’яті. Невелика зміна в рівні промахів LLC може вплинути на p99
набагато сильніше, ніж помірне підвищення частоти CPU.

2) Якщо завантаження CPU 90%, хіба це не доводить, що я обмежений CPU?

Ні. Високе завантаження CPU може включати застоєні цикли, спін‑лупи, контенцію блокувань і накладні витрати ядра.
Використовуйте IPC і метрики stalled, щоб відрізнити «виконується» від «дорого чекається».

3) Який найпростіший метрик, що каже «монстр кешу перемагає»?

IPC плюс промахи LLC під виробничим навантаженням. Якщо IPC низький і промахів LLC багато — ви не голодуєте за CPU, а за локальністю і хітами кешу.

4) Чи більше RAM завжди вирішує проблеми кешу?

Більше RAM допомагає, якщо робочий набір може вміститися і ви можете його підтримувати гарячим (page cache, buffer pool, ARC).
Але RAM не виправить патологічні паттерни доступу, фальшиве шарінг або NUMA‑невідповідність.

5) Чи варто прив’язувати процеси до ядер?

Іноді. Прив’язка може покращити «теплоту» кешу і зменшити міграцію. Але вона може посилити дисбаланс навантаження.
Тестуйте під реальним навантаженням. Якщо прив’язка покращує p99 і зменшує LLC‑промахи — залишайте.

6) Чому апгрейд NVMe не сильно покращив затримки?

Бо ви вже попадали в page cache або buffer pool, або бо затримку домінують CPU‑стали, блокування чи мережеві стрибки.
Також NVMe швидкий, але може мати неприємні хвостові затримки під насиченням.

7) Хіба Direct I/O швидший, бо уникає дубль‑кешування?

Може бути, для специфічних навантажень (великі послідовні читання/записи, стрімінг). Для змішаних або шляхів із частими повторними читаннями
page cache — це функція продуктивності. Видалення його часто перетворює «швидкість пам’яті» на «швидкість сховища».

8) Як зрозуміти, чи мені NUMA шкодить?

Якщо у вас мульти‑сокетні системи, вважайте, що NUMA має значення. Підтвердіть через numastat і perf. Симптоми — погане масштабування,
підвищена затримка і залежність від того, куди планувальник поміщає потоки.

9) Чи може CPU з менше ядрами перевершити той, що має більше ядер?

Так, якщо CPU з менше ядрами має більше кешу на ядро, кращу затримку пам’яті або уникає насичення пропускної здатності.
Багато сервісів не лінійно масштабуються з ядрами, бо домінують доступи до даних і контенція.

10) Яка найпоширеніша «кеш‑помилка» в коді додатка?

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

Практичні наступні кроки

Якщо взяти одну операційну науку з цього тексту: припиніть ставити швидкість CPU в головну роль. У продакшні вона — актор другого плану.
Головні ролі — за локальністю, кешуванням і затримкою.

  1. Запустіть perf stat під реальним навантаженням і зафіксуйте IPC, промахи LLC і stalled cycles.
  2. Запустіть iostat -x і дивіться на затримки і глибину черг, а не тільки MB/s.
  3. Перевірте major faults і поведінку reclaim, щоб з’ясувати, чи ви падаєте з пам’яті на диск.
  4. Підтвердіть NUMA‑розміщення і протестуйте прив’язку як експеримент, а не як догму.
  5. Лише після цих вимірів вирішуйте: більше кешу (інший SKU CPU), більше RAM, кращий розподіл даних або інша архітектура (масштабування вшир).

Потім зробіть нудну річ: документуйте базову лінію, формалізуйте перевірки в runbook для роллаутів і зробіть «кеш і локальність»
першокласною вимогою продуктивності. Ваше майбутнє «я», застрягле в інцидент‑бриджі о 2:00 ночі, буде вам вдячне.

← Попередня
Як читати CPU-бенчмарки, щоб вас не обвели
Наступна →
ZFS fio для баз даних: тестування синхронних записів без самообману

Залишити коментар