P-states і C-states: що робить ваш CPU у простої

Звіт про інцидент завжди починається однаково: «CPU був лише на 12% і затримка все одно підскочила.»
Ви дивитесь панелі моніторингу, бачите багато запасу, і хтось пропонує «можливо база даних гальмує».
Тим часом справжній винуватець тихіший: процесор енергонезбережно заощаджує настільки агресивно, що його пробудження коштує вам мілісекунди, яких ви не закладали.

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

Продукційна ментальна модель: P-states vs C-states

Уявіть свій CPU як пристрій, що має два типи «режимів»:

  • P-states (Performance states): процесор виконує інструкції, але на різних точках частоти/напруги.
    Вищий P-state зазвичай означає вищу частоту і більше споживання. Нижчий P-state — повільніше, але економніше.
  • C-states (Idle states): процесор не виконує корисних інструкцій.
    Чим глибший C-state, тим більша частина ядра (а іноді й усього пакета) може бути вимкнена живленням, що економить енергію — але пробудження займає більше часу.

Це проста версія. Операційна версія додає два зауваження, які варто викарбувати у ваших рунах:

  • P-states не є «поставив і забув». ОС, прошивка та сам процесор можуть впливати на вибір частоти.
    У сучасних системах Intel апарат може приймати багато рішень навіть якщо ви вважаєте, що Linux керує.
  • C-states можуть бути як для окремого ядра, так і для пакета. Одне галасливе сусіднє ядро може не дозволити всьому сокету перейти в глибокий пакетний сон.
    Навпаки, «тиха» система може зайти так глибоко в сон, що наступний запит заплатить неприємний штраф за пробудження.

Ще один ментальний ярлик: P-states — про «наскільки швидко під час роботи». C-states — про «наскільки сплячий під час очікування».
Більшість інцидентів трапляється, коли ви оптимізуєте одне й забуваєте про інше.

Що фактично спрацьовує, коли CPU «в просте»

Коли в Linux немає нічого runnable для CPU, він виконує цикл idle. Цикл простою — це не просто спінінг
(якщо ви не змусите його). Зазвичай він видає інструкцію на кшталт HLT або використовує більш просунуті
механізми, що дозволяють процесору входити в глибші стани сну.

Core C-states: від C0 до «достатньо глибоко, щоб нервувати»

C0 означає «активний». Усі інші — якісь варіації «не виконуються інструкції». Точне відповідність відрізняється між вендорами,
але операційна модель послідовна:

  • C1: легкий сон. Швидке виходження. Мінімальна економія енергії.
  • C1E: покращений C1; часто агресивніше знижує напругу.
  • C3: глибший сон; вимикаються внутрішні тактові сигнали; більша латентність виходу.
  • C6/C7 та інші: дуже глибокий сон; можуть скидати кеші, вимикати частини ядра; латентність виходу може стати відчутною.

Латентність виходу — прихований податок. Якщо ваше навантаження сплескове і чутливе до затримки, глибокі C-states можуть перетворити
«переважно в просте» на «періодично повільно».

Package C-states: весь сокет дрімає

Пакетні C-states (часто позначаються як PC2/PC3/PC6/PC10) — де живуть великі енергозбереження.
Там же й несподіванки. Пакет може перейти в глибокий сон лише якщо виконані умови:

  • Всі ядра достатньо простоюють.
  • Uncore-компоненти (LLC, контролер пам’яті, інтерконект) можуть бути відключені/вимкнені.
  • Пристрої та прошивка погоджуються, що це безпечно.

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

P-states: вибір частоти вже не один регулятор

Стара історія була така: ОС вибирає частоту з таблиці; CPU її виконує. Сучасна історія: ОС встановлює політики і підказки,
а внутрішня логіка CPU часто виконує швидкі контурні регулювання. Драйвер intel_pstate від Intel, CPPC від AMD
і апаратно керовані P-states розмивають межу між «governor» і «прошивкою».

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

Жарт №1: Якщо хочете відчути себе безсилим, спробуйте посперечатися з CPU про те, що таке «максимальна частота».

Чому це важливо: затримки, джиттер, пропускна здатність і витрати

Затримка і хвостова затримка

Глибокі C-states додають латентність пробудження. Масштабування частоти додає латентність підйому. Зазвичай це мікросекунди до низьких мілісекунд,
що звучить мало, поки ви не працюєте з:

  • RPC-сервісами з жорсткими SLO (важливий p99, а не середнє)
  • бекендами зберігання, де час завершення IO видно користувачу
  • базами даних з блокуванням, де малі затримки масштабуються
  • системами низької затримки трейдингу, де джиттер — ризик для кар’єри

Іншими словами: якщо ваша система «переважно проста», але має швидко відповідати під час активності, це треба контролювати.

Пропускна здатність і стійка продуктивність

P-states і турбо вирішують, скільки роботи ви виконуєте на ват. Але турбо обмежене межами потужності (PL1/PL2 на Intel),
тепловими умовами та платформними обмеженнями. Якщо ви примусово увімкнете «режим продуктивності» всюди, ви можете виграти короткі бенчмарки, але програти в стійкій пропускній здатності,
коли досягнете меж потужності/тепла і почнеться жорстке троттлінг.

Енергія, охолодження і реальні гроші

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

Цинічна SRE-істина: ви не зможете купити собі вихід із джитеру затримки, якщо конфігурація флоту неконсистентна.
І ви не зможете налаштуваннями вирішити обмеження бюджету потужності, якщо ваш додаток спінить, нічого не роблячи.

Цікаві факти та коротка історія (бо цей безлад має корені)

  • ACPI стандартизував стани живлення, щоб ОС могли керувати енергоспоживанням між різними вендорами замість унікальних BIOS-інтерфейсів.
  • Ранні ери SpeedStep зробили масштабування частоти загальнодоступним; до того «енергоменеджмент» був переважно «вимкнути екран».
  • Сучасна турбо-поведінка обмежена потужністю, а не частотою: процесори намагаються триматися в межах енергетичних і теплових конвертів, а не за фіксованим тактом.
  • C-states існували до хмари, але хмара зробила їхні компроміси болючими: multi-tenant навантаження сплескові й непередбачувані.
  • Tickless ядра (NO_HZ) зменшили періодичні таймерні переривання, щоб CPU могли довше бути в простій і досягати глибших C-states.
  • Intel ввів апаратне управління P-states, щоб реагувати швидше, ніж цикл планувальника ОС.
  • RAPL (Running Average Power Limit) дав софту спосіб вимірювати/обмежувати енергію CPU, зробивши енергію першокласною метрикою.
  • Пакетні C-states стали важливими, коли «uncore» енергія (LLC, контролер пам’яті, інтерконект) почала конкурувати з енергією ядра.
  • Віртуалізація усе ускладнила: «простой» в гості не означає простій на хості; зупинка в VM передбачає політику гіпервізора.

Як Linux керує P-states і C-states

The control plane: drivers, governors, and policies

У Linux масштабування частоти CPU зазвичай керує підсистема cpufreq. Два поширені драйвери:

  • intel_pstate (Intel): часто за замовчуванням на сучасних Intel. Може працювати в «active» режимі, де CPU сильно бере участь у рішеннях.
  • acpi-cpufreq: більш традиційний ACPI-драйвер з явними таблицями частот.

Governor-и — політики на кшталт performance, powersave і (залежно від драйвера) schedutil.
Не сприймайте назви governor-ів як універсальні істини; їхня поведінка може відрізнятися в залежності від драйвера.

The idle plane: cpuidle, C-state drivers, and latency constraints

C-states у Linux керуються підсистемою cpuidle. Вона обирає стан простою на основі:

  • очікуваної тривалості простою (як довго до наступної події, що розбудить CPU)
  • латентності виходу з кожного стану
  • QoS-обмежень (підказки про чутливість до латентності від ядра/користувацького простору)
  • чого дозволяє платформа і прошивка

BIOS/UEFI: the place where “we’ll just change one setting” becomes folklore

Налаштування прошивки можуть перевизначити або обмежити все:

  • Максимально дозволений C-state (наприклад, лімітувати до C1)
  • Обмеження пакетних C-state
  • Увімкнення/вимкнення турбо
  • Energy/Performance Bias (Intel’s EPB)
  • «Профілі енергії» вендора, що роблять кілька речей одночасно

У продакшені найпоширеніший збій — не «неправильний ядро». Це «різні BIOS за замовчуванням у різних партіях».

One reliability quote (paraphrased idea)

Парафраз ідеї, приписаної John Ousterhout: складність — корінь багатьох проблем надійності.
Енергоменеджмент — це складність із ватметром.

Практичні завдання: команди, значення виводу і рішення

Єдине налаштування, яке має значення — те, яке ви можете перевірити. Нижче реальні завдання, які я очікую від on-call інженера,
з командами, прикладами виводу і тим, яке рішення з них випливає.

Task 1: Identify the active CPU frequency driver and governors

cr0x@server:~$ cpupower frequency-info
analyzing CPU 0:
  driver: intel_pstate
  CPUs which run at the same hardware frequency: 0
  available cpufreq governors: performance powersave
  current policy: frequency should be within 800 MHz and 3900 MHz.
                  The governor "powersave" may decide which speed to use
  current CPU frequency: 1200 MHz (asserted by call to hardware)
  boost state support:
    Supported: yes
    Active: yes

Що це означає: У вас intel_pstate; вибір governor-ів обмежений і поведінка специфічна для драйвера.
Поточна частота низька, бо політика це дозволяє. Турбо увімкнено.

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

Task 2: Check min/max frequency policy per CPU

cr0x@server:~$ for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_{min,max}_freq; do echo "$f: $(cat $f)"; done | head
/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq: 800000
/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: 3900000
/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq: 800000
/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq: 3900000

Що це означає: ОС дозволяє широкий діапазон частот. Якщо продуктивність усе ще погана, обмеження десь інше
(ліміти потужності, терміка, C-states, контенція).

Рішення: Якщо scaling_max_freq несподівано низький, підозрівайте профіль налаштувань, обмеження в середовищі контейнерів або події обмеження платформи.

Task 3: Inspect turbo/boost status

cr0x@server:~$ cat /sys/devices/system/cpu/cpufreq/boost
1

Що це означає: Турбо/буст увімкнено.

Рішення: Для сервісів, чутливих до латентності, турбо часто допомагає (швидший час обслуговування).
Для детермінованої латентності турбо може додавати теплову варіативність; розгляньте фіксацію політики лише після вимірювань.

Task 4: Verify CPU idle state availability and residency (core C-states)

cr0x@server:~$ sudo cpupower idle-info
CPUidle driver: intel_idle
CPUidle governor: menu
analyzing CPU 0:
  Number of idle states: 4
  Available idle states: POLL C1 C1E C6
  C1: exit latency 2 us
  C1E: exit latency 10 us
  C6: exit latency 85 us

Що це означає: Існує глибокий C6 з ~85 µs латентністю виходу (приклад). Це не катастрофа, але не безкоштовно.

Рішення: Якщо ваш p99 корелює з періодами простою, розгляньте обмеження найглибшого C-state тільки на уражених вузлах і повторне тестування.

Task 5: Check per-state time and usage counts for idle states

cr0x@server:~$ for s in /sys/devices/system/cpu/cpu0/cpuidle/state*; do \
  echo "$(basename $s) name=$(cat $s/name) disable=$(cat $s/disable) time=$(cat $s/time) usage=$(cat $s/usage)"; \
done
state0 name=POLL disable=0 time=122 usage=18
state1 name=C1 disable=0 time=983421 usage=24011
state2 name=C1E disable=0 time=221934 usage=9120
state3 name=C6 disable=0 time=55290321 usage=110432

Що це означає: CPU0 проводить багато часу в C6. Це добре для енергії. Це може бути погано для латентності при пробудженні.

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

Task 6: Check package C-state residency (Intel, via turbostat)

cr0x@server:~$ sudo turbostat --Summary --quiet --show PkgWatt,PkgTmp,Pkg%pc2,Pkg%pc6,Pkg%pc10 --interval 1 --num_iterations 3
PkgWatt  PkgTmp  Pkg%pc2  Pkg%pc6  Pkg%pc10
  32.15     54      2.12     8.41     61.77
  28.02     52      1.88     7.96     68.10
  35.44     55      2.30     9.02     58.33

Що це означає: Пакет часто досягає PC10 (глибокий сон). Споживання низьке. Чудово для ефективності.
Також класична причина «холодного» стартового латенсу при пробудженні.

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

Task 7: Look for power limit and throttling signals (Intel RAPL / thermal)

cr0x@server:~$ sudo turbostat --quiet --show Bzy_MHz,Avg_MHz,Busy%,CoreTmp,PkgTmp,PkgWatt,CorWatt,GFXWatt --interval 1 --num_iterations 2
Bzy_MHz  Avg_MHz  Busy%  CoreTmp  PkgTmp  PkgWatt  CorWatt  GFXWatt
   4200     1850  22.15       72      79    165.2     92.1     0.0
   4100     1902  23.40       74      81    165.0     93.0     0.0

Що це означає: Boost-частоти є, але пакетна потужність висока. Якщо температура зросте, може початися троттлінг.

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

Task 8: Confirm kernel tick mode and timer behavior (idle disruption)

cr0x@server:~$ grep -E 'NO_HZ|CONFIG_HZ' -n /boot/config-$(uname -r) | head -n 5
114:CONFIG_HZ=250
501:CONFIG_NO_HZ_COMMON=y
504:CONFIG_NO_HZ_IDLE=y
507:CONFIG_NO_HZ_FULL is not set

Що це означає: Tickless idle увімкнено (NO_HZ_IDLE), що допомагає досягати глибших C-states. Не повністю безтаймерний режим.

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

Task 9: Identify interrupt hotspots that prevent idle or cause wake storms

cr0x@server:~$ sudo cat /proc/interrupts | head -n 15
           CPU0       CPU1       CPU2       CPU3
  0:         21         18         19         22   IO-APIC   2-edge      timer
  1:          0          0          0          0   IO-APIC   1-edge      i8042
 24:     883421     102331      99321      90122   PCI-MSI  327680-edge  eth0-TxRx-0
 25:     112331     843221     121112     110998   PCI-MSI  327681-edge  eth0-TxRx-1

Що це означає: Черги NIC завантажені на певних CPU. Це може не дозволяти ядрам простоювати і може викликати сплескові пробудження.

Рішення: Розгляньте налаштування irq affinity (або поведінку irqbalance), якщо бачите гарячі точки на одному ядрі або стрибки латентності, що збігаються з перериваннями.

Task 10: Check irqbalance status and whether it’s fighting your pinning

cr0x@server:~$ systemctl status irqbalance --no-pager
● irqbalance.service - irqbalance daemon
     Loaded: loaded (/lib/systemd/system/irqbalance.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-01-10 08:21:10 UTC; 2h 12min ago
       Docs: man:irqbalance(1)
   Main PID: 912 (irqbalance)

Що це означає: irqbalance активний. Добрий стандарт — якщо не робите ручне прив’язування IRQ для низької латентності і забули його відключити.

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

Task 11: See if a tuning profile is enforcing aggressive power savings

cr0x@server:~$ tuned-adm active
Current active profile: virtual-guest

Що це означає: Активний tuned-профіль може змінювати governor і інші параметри, що впливають на латентність.

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

Task 12: Verify current governor quickly across all CPUs

cr0x@server:~$ grep -H . /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 2>/dev/null | head
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor:powersave
/sys/devices/system/cpu/cpu1/cpufreq/scaling_governor:powersave
/sys/devices/system/cpu/cpu2/cpufreq/scaling_governor:powersave
/sys/devices/system/cpu/cpu3/cpufreq/scaling_governor:powersave

Що це означає: Усі CPU в режимі powersave.

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

Task 13: Temporarily change governor (and understand what you’re risking)

cr0x@server:~$ sudo cpupower frequency-set -g performance
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2
Setting cpu: 3

Що це означає: Ви запросили governor performance. На intel_pstate це змінює поведінку політики, а не встановлює фіксований такт.

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

Task 14: Limit deepest C-state (surgical test, not a lifestyle)

cr0x@server:~$ echo 1 | sudo tee /sys/devices/system/cpu/cpu0/cpuidle/state3/disable
1

Що це означає: Ви відключили один стан простою (тут state3 може бути C6). Це змушує CPU0 переходити в менш глибокий сон.

Рішення: Якщо p99 покращиться і споживання зросте прийнятно, застосуйте це персистентно (аргументи ядра, tuned або systemd unit) і задокументуйте.

Task 15: Check for virtualization effects: are you tuning the guest while the host decides?

cr0x@server:~$ systemd-detect-virt
kvm

Що це означає: Ви віртуалізовані. Гостьові налаштування живлення можуть мати обмежений ефект; політика хоста і планування гіпервізора важливіші.

Рішення: Якщо вам потрібна поведінка з низькою латентністю, працюйте з командою платформи: прив’язка CPU, governor на хості і політика C-states — реальні важелі.

Task 16: Check CPU pressure and scheduling contention (because “idle” can be a lie)

cr0x@server:~$ cat /proc/pressure/cpu
some avg10=0.00 avg60=0.10 avg300=0.08 total=18873412
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

Що це означає: CPU pressure низький; планувальник не бореться. Якщо латентність погана, зосередьтеся на поведінці пробудження/сну, перериваннях, IO або блокуваннях.

Рішення: Якщо some або full високі, не гоніться спочатку за C-states — виправте контенцію, CPU-ліміти або галасливих сусідів.

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

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

First: decide if you’re chasing CPU power behavior or something else

  1. Перевірте CPU pressure (контенція планувальника):
    якщо PSI високий, ви не «в прості», вас перевантажили або обмежили.
  2. Перевірте чергу виконання і steal time (особливо в VM):
    низька завантаженість може співіснувати з великою латентністю, якщо вас чекають у плануванні.
  3. Перевірте iowait і латентність сховища:
    багато «простою CPU» насправді означає «чекає на IO».

Second: confirm what power policy is active

  1. Драйвер + governor через cpupower frequency-info.
  2. Турбо увімкнено? через /sys/devices/system/cpu/cpufreq/boost.
  3. Тунінг профіль або вендорський сервіс, що нав’язує політику.

Third: measure C-state residency and wake-related disruption

  1. Використання/час по core C-state через /sys/.../cpuidle або cpupower idle-info.
  2. Пакетні C-states через turbostat (якщо доступно).
  3. Гарячі точки переривань через /proc/interrupts і інструменти для афінності IRQ.

Make one change at a time, on one node, with a timer

Найшвидший спосіб витратити тиждень — перемикати BIOS-настройки, параметри ядра і tuned-профілі в одному оці
технічного вікна. Змінюйте по одному, вимірюйте p95/p99 і споживання/терміку, потім приймайте рішення.

Три міні-історії з корпоративних фронтів

Mini-story 1: The incident caused by a wrong assumption

Команда розгорнула новий «легкий» внутрішній API-шлюз. Він був ефективний: низький CPU, короткі сплески, багато мережевих переривань.
На дашбордах завантаження CPU трималося в межах 15–25%. Всі віталися з тим, що не передпродукували.
Потім p99 затримка подвоїлася у годину низького навантаження, саме коли трафік спав.

Перше припущення було класичним: «менше навантаження — більше запасу». Але сервіс був сплесковим.
У тихі періоди CPU заходили в глибокі пакетні C-states. Коли приходив наступний сплеск, обробка запиту платила за пробудження
плюс за латентність розгону частоти. Індивідуально маленькі затримки, разом — некрасиво.

Друге припущення: «Ми в performance governor, отже частота висока.» Насправді — ні.
Половина флоту мала інший профіль BIOS. Ті хости дозволяли глибші пакетні стани і мали більш енергетично-орієнтовану політику.
Флот був гетерогенний, а балансувальник навантаження щасливо змішував хости з різною поведінкою при пробудженні.

Виправлення не було героїчним. Вони стандартизували прошивкові профілі, потім канарили tuned-профіль з низькою латентністю лише на вузлах шлюзу.
Для батч-воркерів глибокий сон зберегли. Латентність стабілізувалася, енергія залишилася в межах, і інцидент закінчився не постмортем-романом,
а коротким чеклистом, доданим до процесу провізування.

Mini-story 2: The optimization that backfired

Команда зі зберігання (думаємо: розподілена блокова служба) захотіла урізати споживання. Вони примусово увімкнули глибші C-states і встановили governor-и powersave по всіх
нодах зберігання. На папері це було відповідально: ноди здебільшого чекали на IO, і CPU не виглядав завантаженим.

Що вони пропустили — так це поведінку зберігання під змішаним навантаженням. Шляхи завершення IO чутливі до латентності і керовані перериваннями.
Сервіс тихо сидів, а потім різко опрацьовував шквал завершень, перевірок контрольної суми і мережевих відповідей.
З глибокими C-states латентність від переривання до обробника зростала. З агресивним масштабуванням частоти ядра починалися повільно, а потім розганялися.

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

Вони відкочували зміни на фронтендах зберігання, але зберегли енергозбереження для фонових компакцій.
Справжній урок — охоплення: політика живлення залежить від робочого навантаження. Застосовуйте її за ролями, а не за кластером, і завжди дивіться p99 і показники повторів.

Mini-story 3: The boring but correct practice that saved the day

Інша компанія керувала Kubernetes-флотом з мішаними поколіннями інстансів. Їхня платформа зробила щось надзвичайно непривабливе:
вони вели матрицю апаратних можливостей і тест провізування, який записував доступність C-states, статус турбо і резидентність простою.
Кожне оновлення BIOS мало пройти ті самі тести перш ніж потрапити в золотий образ.

Одного кварталу оновлення прошивки вендора змінило значення за замовчуванням для пакетних C-state лімітів.
Нічого не зламалося відразу. Ось у чому хитрість — такі зміни не завжди голосно ламають систему. Вони просто змінюють характеристики латентності.

Їхні тести це вловили, бо зафіксована резидентність пакетних C-states суттєво змінилася на простих нодах.
Не довелося чекати, поки клієнт поскаржиться. Вони призупинили розгортання, підправили політику прошивки і задокументували різницю.

Результат був нудний: жодного інциденту. Платформна команда не отримала похвали.
Але команді додатків ніколи не довелося вивчати пакетні C-states о 3:00 ночі, що й є найвищою формою операційного успіху.

Жарт №2: Найкраща зміна в енергоменеджменті — та, що ніколи не потрапляє на слайди постмортему.

Поширені помилки (симптоми → корінь → виправлення)

1) “CPU is low but p99 latency is high”

Симптоми: низька середня завантаженість CPU; великі пікові хвостові затримки у тихі періоди; краща латентність під стабільним навантаженням.

Корінь: глибокі C-states і/або агресивне масштабування частоти спричиняють штрафи на пробудження та розгін; сплесковий трафік викликає повторні переходи.

Виправлення: виміряйте резидентність C-state (ядра і пакет). Проведіть канарку з профілем низької латентності або обмежте найглибші C-states на уражених вузлах.
Переконайтесь у консистентності BIOS-профілів по флоту.

2) “Frequency stuck low even under load”

Симптоми: cpupower frequency-info показує низьку поточну частоту; пропускна здатність нижча за очікувану; температура CPU помірна.

Корінь: масштабування максимальної частоти обмежене політикою, взаємодією квот контейнера або платформними лімітами потужності.

Виправлення: перевірте scaling_max_freq і tuned-профіль; підтвердіть турбо; інспектуйте ліміти потужності/троттлінг через turbostat.
У контейнерах перевірте CPU quota і cpuset-призначення.

3) “Performance improved on one node but not another”

Симптоми: той самий софт, різна латентність; зміни налаштувань працюють непослідовно по хостах.

Корінь: гетерогенні прошивкові дефолти, відмінності мікрокоду або різні драйвери частоти (intel_pstate vs acpi-cpufreq).

Виправлення: уніфікуйте BIOS-настройки; забезпечте консистентні параметри ядра; інвентаризуйте вибір драйверів і версії мікрокоду.

4) “IRQ storms prevent idle and waste power”

Симптоми: пакет ніколи не доходить до глибоких C-states; підвищене споживання в простої; певні CPU показують величезні лічильники переривань.

Корінь: дисбаланс афінності переривань, неправильно налаштовані черги NIC, шумні пристрої або поведінка таймерів.

Виправлення: інспектуйте /proc/interrupts; налаштуйте афінність IRQ; сконфігуруйте кількість черг належним чином; перевірте налаштування irqbalance.

5) “Disabled C-states and now throughput got worse”

Симптоми: збільшилось споживання; з’явився троттлінг по терміці; стійка продуктивність знизилась після короткого початкового буста.

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

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

6) “We pinned CPUs, but latency still jitters”

Симптоми: налаштована ізоляція CPU; все одно спостерігається джиттер; час від часу довгі хвости.

Корінь: менеджмент живлення все ще переходить ядра/пакет; переривання потрапляють на ізольовані CPU; конкуренція по гіперпотоку.

Виправлення: узгодьте афінність IRQ з ізоляцією; розгляньте обмеження глибоких C-states для ізольованих ядер; перевірте політику SMT для критичних по латентності навантажень.

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

Checklist A: Standardize a fleet power baseline (the boring part that prevents surprises)

  1. Зробіть інвентаризацію моделей CPU, версій мікрокоду і статусу віртуалізації по вузлах.
  2. Запишіть драйвер частоти (intel_pstate/acpi-cpufreq) і governor-и в систему конфігурації.
  3. Задокументуйте налаштування BIOS/UEFI (обмеження C-state, турбо, EPB) для кожного апаратного покоління.
  4. Визначте політики за ролями: критичні за латентністю, збалансовані, батч/ефективність.
  5. Забезпечте застосування tuned-профілів або еквіваленту через автоматизацію; без ручних «сніжинок».
  6. Періодично знімайте резидентність пакетних C-states і ват на канаркових ідлах, щоб виявляти відхилення після оновлень прошивки.

Checklist B: Tune a latency-sensitive service node (safely)

  1. Базові виміри: зафіксуйте p95/p99, частоту помилок/повторів і споживання енергії в простої і під типовим сплеском.
  2. Підтвердіть, що це не контенція CPU: перевірте PSI CPU і черги виконання.
  3. Виміряйте резидентність пакетних C-states на простому і під сплеском.
  4. Канарка: змініть tuned-профіль або governor на одному вузлі.
  5. Якщо ще є сплески, протестуйте обмеження найглибшого C-state (тимчасово) і повторіть виміри.
  6. Підтвердіть терміки і стійкі ліміти потужності; слідкуйте за троттлінгом.
  7. Розгортайте за роллю, а не по флоту. Документуйте політику з «чому», а не лише «що».

Checklist C: Tune an efficiency-first batch worker node

  1. Підтвердіть, що навантаження орієнтоване на пропускну здатність і толерує джиттер.
  2. Дозвольте глибокі пакетні C-states і збалансовані governor-и.
  3. Стежте за «бурями» переривань, що не дають пакету заснути (марні ватти).
  4. Моніторте енергію на завдання (або на оброблені GB), а не лише час виконання.

FAQ

1) Are P-states and C-states independent?

Переважно так, але не повністю. C-states визначають, що відбувається під час простою; P-states — продуктивність під час роботи.
На практиці вони взаємодіють через терміку і ліміти потужності: глибший простій може покращити турбо-головку, а відключення простою може зменшити стійкий буст.

2) Should I always use the performance governor on servers?

Ні. Для фронтендів, чутливих до латентності, це може допомогти. Для батч-флотів це часто марнотратно.
Також на intel_pstate performance не означає «фіксований максимум». Це означає більш агресивну політику.
Приймайте рішення за роллю і вимірюйте p99 та ватти.

3) If C-states add latency, why not disable them everywhere?

Бо ви заплатите енергією, теплом і іноді троттлінгом — плюс зменшиться турбо-головка.
Вимикання глибоких C-states може бути інструментом для конкретних ролей. Рідко це гарний дефолт для весього флоту.

4) Why does latency get better under steady load?

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

5) How do I know whether the OS or hardware is controlling frequency?

Почніть з cpupower frequency-info, щоб побачити драйвер. На сучасних Intel intel_pstate в active режимі означає, що апарат відіграє велику роль.
Також зверніть увагу, чи поточна частота «asserted by call to hardware» у виводі.

6) Does virtualization change the story?

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

7) What’s the difference between core C-states and package C-states operationally?

Core C-states впливають на глибину сну одного ядра і латентність виходу. Пакетні стани впливають на компоненти на рівні сокета і можуть економити значно більше енергії.
Пакетні стани також можуть давати більш помітні штрафи на «перший запит після простою», залежно від платформи.

8) Can interrupt tuning fix C-state-related latency?

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

9) How do I decide between “low latency” and “energy efficient” modes?

Керуйтеся SLO і моделлю вартості вашої робочого навантаження. Якщо у вас суворі p99 цілі і сплесковий трафік, надавайте перевагу низькій латентності на відповідних вузлах.
Якщо у вас батчі або еластичні черги, надавайте перевагу ефективності. Уникайте змішування політик в одному пулі за одним балансувальником.

10) What’s a safe first experiment if I suspect C-states?

Канарка одного вузла: зніміть базові метрики, потім переключіться на tuned-профіль з низькою латентністю або тимчасово обмежте найглибший стан простою.
Якщо p99 покращується без троттлінгу або неприйнятного зростання споживання — ви довели причинно-наслідковий зв’язок.

Висновок: практичні наступні кроки

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

Наступні кроки, які дійсно працюють у продакшені:

  1. Виміряйте перед налаштуванням: зберіть драйвер/владний governor, резидентність C-state і p95/p99 латентність на канарковому вузлі.
  2. Уніфікуйте політику прошивки: неконсистентні BIOS-дефолти — мовчазний вбивця флоту.
  3. Розділяйте за ролями: вузли з низькою латентністю і вузли з оптимізацією енергії не повинні мати одну й ту ж політику.
  4. Робіть зміни відкотними: перемикання через tuned-профілі або керування конфігурацією, а не ручні SSH-зміни.
  5. Слідкуйте за хвостовою латентністю і повторами: середні значення збрехають вам з прямим виразом обличчя.

Кеш CPU (L1/L2/L3) простими словами: чому перемагає пам’ять

Ваш сервіс «вузький за CPU». Дашборди так кажуть. CPU на 80–90%, затримки погані, і перша реакція команди — додати ядра.
Ви додаєте ядра, і нічого не змінюється. Або стає гірше. Вітаю: ви щойно зустріли справжнього боса — пам’ять.

Кеші процесора (L1/L2/L3) існують тому, що сучасні ядра можуть виконувати арифметику швидше, ніж система може підкидати їм дані.
Більшість виробничих проблем продуктивності — це не «CPU повільний». Це «CPU чекає». Цей текст пояснює кеші без балаканини, а потім показує,
як довести, що відбувається, на реальному Linux-хості командами, які можна виконати сьогодні.

Чому перемагає пам’ять (і чому CPU більшість часу чекає)

CPU — абсурдні. Сучасне ядро може виконувати кілька інструкцій за такт, робити спекулятивне виконання, перестановки, векторизацію й загалом поводитися як
переповнений кофеїном бухгалтер, що робить податкові звіти о 4 ранку. Тим часом DRAM відносно повільна. Ядро може завершувати інструкції за субнаносекунди;
звернення до DRAM може зайняти десятки або сотні наносекунд залежно від топології, навантаження й того, чи ви випадково зайшли в віддалий NUMA.

Практичний висновок: ваш CPU витрачає багато часу, просто чекаючи на завантаження з пам’яті. Не на диск. Не на мережу. Навіть не на «повільний код» у звичному сенсі.
Він чекає на наступну кеш-лінію.

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

Ось ментальна модель, яка витримує роботу у виробництві: продуктивність визначається тим, як часто ви промахуєтесь по кешу та
наскільки дорогі ці промахи. Найдорожчі промахи — ті, що втікають за межі корпусу й йдуть у DRAM, а найпекельніші — віддалий NUMA DRAM,
доступ до якого йде через інтерконектор, поки інші ядра борються за пропускну здатність.

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

L1/L2/L3 простими словами

Уявіть рівні кешу як поступово більші й поступово повільніші «комори» між ядром і DRAM.
Назви історичні й прості: L1 найближчий до ядра, L2 далі, L3 зазвичай спільний для ядер сокета (не завжди), а потім DRAM.

Для чого кожен рівень

  • L1 cache: крихітний і надзвичайно швидкий. Часто розділений на L1i (інструкції) і L1d (дані). Це перше місце, куди дивиться ядро.
  • L2 cache: більше, трохи повільніше, зазвичай приватний для ядра. Ловить те, що випало з L1.
  • L3 cache: набагато більший, повільніший, часто спільний між ядрами. Зменшує звернення до DRAM і діє як амортизатор при контенції.

Що означають «хіт» і «міс» в операційній практиці

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

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

Чому не можна «просто використати L3»

Люди інколи говорять, ніби L3 — це магічний спільний пул, який вмістить ваш робочий набір. Це не так. L3 — спільний, зазнає контенції і часто
інклюзивний або частково інклюзивний залежно від архітектури. Також пропускна здатність і затримка L3 кращі за DRAM, але це не безкоштовно.

Якщо робочий набір більше за L3 — ви йдете в DRAM. Якщо він більший за DRAM… це називається «своп», і це крик про допомогу.

Кеш-лінії, локальність і правило «доторкнувся — купив»

CPU не підвантажують по одному байту в кеш. Вони підвантажують кеш-лінії, зазвичай 64 байти на x86_64. Коли ви завантажуєте одне значення,
ви часто тягнете й сусідні значення. Це добре, якщо ваш код використовує сусідню пам’ять (просторова локальність). Це погано, якщо вам потрібне лише одне поле,
а решта — сміття, бо ви засмітили кеш непотрібними даними.

Локальність — це вся гра:

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

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

Сухий операційний переклад: якщо ви бачите високий CPU, але також багато прострілених циклів (stalled cycles), у вас не «проблема обчислення».
У вас проблема з підгодовуванням ядра. Ваш найгарячіший шлях коду, ймовірно, домінований кеш-промахами або неправильною передбачуваністю розгалужень, а не математикою.

Жарт №1: кеш-промахи — це як «швидкі питання» в корпоративному чаті — кожне здається дрібницею, поки не зрозумієш, що весь день ти чекаєш на них.

Префетчинг: спроба CPU бути корисним

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

Ось чому «я оптимізував цикл» іноді нічого не дає. Цикл не проблема; проблема — ланцюжок залежностей пам’яті.

Частина, яку ніхто не хоче відлажувати: когерентність і false sharing

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

False sharing: коли потоки сваряться через кеш-лінію, яку вони насправді не «ділять»

False sharing — це коли два потоки оновлюють різні змінні, що випадково живуть у тій самій кеш-лінії. Логічно вони не ділять дані, але протокол когерентності
обробляє всю лінію як одиницю. Отже кожний запис викликає інвалідизації й передання права власності, і продуктивність падає вниз по схилу.

Симптоми: виглядає, ніби «більше потоків зробило повільніше» з великою кількістю часу CPU, але без реального прогресу. Ви побачите багато трафіку cache-to-cache
і промахів когерентності, якщо подивитесь правильними інструментами.

Жарт №2: false sharing — це коли дві команди «володіють» тією ж клітинкою таблиці; змінення коректні, але процес — ні.

Записо-важкі навантаження платять додатково

Читання можна шарити. Записи вимагають ексклюзивного права на лінію, що спричиняє дії когерентності. Якщо у вас гарячий лічильник, який оновлюють багато потоків,
цей лічильник стане послідовним вузьким місцем, навіть якщо «ядер багато».

Ось чому існують пер-поточні лічильники, шардові блокування і батчинг. Ви не витончений. Ви уникаєте рахунку за фізику.

NUMA: податок затримки при масштабуванні

На багатьох серверах пам’ять фізично приєднана до CPU-сокетів. Доступ до «локальної» пам’яті швидший, ніж до пам’яті, приєднаної до іншого сокета.
Це — NUMA (Non-Uniform Memory Access). Це не рідкісний випадок. Це за замовчуванням на багатьох реальних залізячних системах.

Ви можете ігнорувати NUMA, поки не зможете. Режим відмови проявляється, коли:

  • ви масштабували потоки між сокетами,
  • ваш алокатор розподіляє сторінки по вузлах,
  • або планувальник мігрує потоки подалі від їхньої пам’яті.

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

Цікаві факти й історія, які можна повторити на нарадах

  1. «Memory wall» став загальною проблемою в 1990-х: швидкість CPU росла швидше за затримку DRAM, тому кеші стали обов’язковими.
  2. Кеш-лінії — це архітектурний вибір: 64 байти звично на x86, але інші архітектури використовували різні розміри; це баланс між пропускною здатністю й засміченням.
  3. L1 часто розділений на кеш інструкцій і даних, бо їх змішування призводить до конфліктів; доступи до коду і даних мають різні шаблони.
  4. Спільне використання L3 корисне: воно допомагає при читанні даних багатьма потоками і зменшує звернення до DRAM, але створює контенцію під навантаженням.
  5. Існують апаратні префетчери, бо послідовний доступ — поширений; вони можуть суттєво пришвидшити стрімінгове читання без змін коду.
  6. Протоколи когерентності (як варіанти MESI) — велика причина того, що мульти-ядерність «просто працює», але вони також накладають реальні витрати при контенції записів.
  7. TLB — це теж кеш: Translation Lookaside Buffer кешує трансляції адрес; промахи TLB можуть вдарити як промахи кешу.
  8. Великі сторінки зменшують навантаження на TLB, відображаючи більше пам’яті на запис; вони можуть допомогти деяким навантаженням і нашкодити іншим.
  9. Ранні сюрпризи масштабування у 2000-х навчали команди, що «більше потоків» — не план продуктивності, якщо пам’ять і блокування не опрацьовані.

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

Коли система повільна, ви хочете швидко знайти обмежувальний ресурс, а не писати поему про мікроархітектуру. Це польовий чекліст.

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

  • Перевірте завантаження CPU та метрики рівня виконання: черга на виконання, перемикання контекстів, навантаження IRQ.
  • Подивіться на stalled cycles / промахи кешу за допомогою perf, якщо можете.
  • Якщо інструкцій за такт мало, а промахів кешу багато — швидше за все це затримка пам’яті або обмеження пропускної здатності пам’яті.

Друге: вирішіть, чи це затримкова або пропускна проблема

  • Затримкова: слідування по покажчиках, випадковий доступ, багато LLC-промахів, низька пропускна здатність пам’яті.
  • Пропускна: стрімінг, великі скани, багато ядер читають/пишуть, висока пропускна здатність пам’яті близько до меж платформи.

Третє: перевірте NUMA і топологію

  • Чи потоки працюють на одному сокеті, а виділення пам’яті йдуть на інший?
  • Чи ви трете LLC між сокетами?
  • Чи додаток чутливий до хвостових затримок (зазвичай так), роблячи віддалу пам’ять тихим вбивцею?

Четверте: перевірте «очевидне, але нудне»

  • Чи відбувається свопінг або тиск на пам’ять (reclaim storms)?
  • Чи досягаєте ви ліміти пам’яті cgroup?
  • Чи насичено одне блокування або лічильник (false sharing, контендований mutex)?

Перефразована ідея (приписують): повідомлення Gene Kim про операції — швидкі цикли зворотного зв’язку кращі за героїчні вчинки — спочатку вимірюйте, потім змінюйте по одному пункту.

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

Це призначено для виконання на Linux-хості, де ви діагностуєте продуктивність. Деякі команди потребують root або прав для perf.
Ідея — не запам’ятовувати команди, а пов’язати виводи з рішеннями.

Завдання 1: Визначити розміри кешів і топологію

cr0x@server:~$ lscpu
Architecture:             x86_64
CPU(s):                   64
Thread(s) per core:       2
Core(s) per socket:       16
Socket(s):                2
L1d cache:                32K
L1i cache:                32K
L2 cache:                 1M
L3 cache:                 35.8M
NUMA node(s):             2
NUMA node0 CPU(s):        0-31
NUMA node1 CPU(s):        32-63

Що це означає: у вас два сокети, два NUMA-вузли й L3 на сокет (часто). Ваш робочий набір, що вилітає з ~36MB на сокет,
починає платити DRAM-ціни.
Рішення: якщо сервіс чутливий до затримки, плануйте NUMA-усвідомленість (pinning, політика пам’яті) і тримайте гарячі структури даних маленькими.

Завдання 2: Перевірити розмір кеш-лінії (і припинити гадати)

cr0x@server:~$ getconf LEVEL1_DCACHE_LINESIZE
64

Що це означає: межі ризику false sharing — 64 байти.
Рішення: у низькорівневому коді вирівнюйте гарячі пер-поточні лічильники/структури на межі 64B, щоб уникнути пінг-понгу кеш-ліній.

Завдання 3: Підтвердити NUMA-відстані

cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 0 size: 256000 MB
node 0 free: 120000 MB
node 1 cpus: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
node 1 size: 256000 MB
node 1 free: 118000 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

Що це означає: віддалена пам’ять ≈ 2× за «відстанню». Не буквально 2× затримка, але орієнтовно важливо.
Рішення: якщо ви чутливі до хвостових затримок, тримайте потоки й їхню пам’ять локальними (або зменшіть крос-сокетний трафік, обмеживши CPU affinity).

Завдання 4: Перевірити, чи ядро не «бореться» з вами автоматичним NUMA balancing

cr0x@server:~$ cat /proc/sys/kernel/numa_balancing
1

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

Завдання 5: Подивитися розміщення пам’яті процесу по NUMA

cr0x@server:~$ pidof myservice
24718
cr0x@server:~$ numastat -p 24718
Per-node process memory usage (in MBs) for PID 24718 (myservice)
Node 0          38000.25
Node 1           2100.10
Total           40100.35

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

Завдання 6: Перевірити тиск пам’яті і свопінг (прірва продуктивності)

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
 3  0      0 1200000  80000 9000000   0    0     2    15  900 3200 45  7 48  0  0
 5  0      0 1180000  80000 8900000   0    0     0     0 1100 4100 55  8 37  0  0
 7  0      0 1170000  80000 8850000   0    0     0     0 1300 5200 61  9 30  0  0

Що це означає: немає своп-ін/аут (si/so = 0), отже ви не в категорії «все жахливо». CPU завантажений, але не чекає на IO.
Рішення: переходьте до аналізу кешу/пам’яті; не витрачайте час на звинувачення диска.

Завдання 7: Подивитися, чи ви обмежені пропускною здатністю (швидке зчитування пропускної здатності пам’яті)

cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-references,cache-misses,LLC-loads,LLC-load-misses -I 1000 -- sleep 5
# time(ms)  cycles        instructions   cache-references  cache-misses  LLC-loads    LLC-load-misses
     1000   5,210,000,000  2,340,000,000  120,000,000       9,800,000     22,000,000   6,700,000
     2000   5,300,000,000  2,310,000,000  118,000,000      10,200,000     21,500,000   6,900,000
     3000   5,280,000,000  2,290,000,000  121,000,000      10,500,000     22,300,000   7,100,000

Що це означає: instructions/cycle низький (приблизно 0.43 тут), а промахи кешу/LLC значні. CPU багато чекає.
Рішення: трактуйте це як домінування затримки пам’яті, якщо лічильники пропускної здатності не показують насичення; шукайте випадковий доступ, слідування по покажчиках або NUMA.

Завдання 8: Визначити топ-функції і чи вони затримують (профілювання з perf)

cr0x@server:~$ sudo perf top -p 24718
Samples: 2K of event 'cycles', Event count (approx.): 2500000000
  18.50%  myservice  myservice  [.] hashmap_lookup
  12.20%  myservice  myservice  [.] parse_request
   8.90%  libc.so.6  libc.so.6   [.] memcmp
   7.40%  myservice  myservice  [.] cache_get
   5.10%  myservice  myservice  [.] serialize_response

Що це означає: гарячі точки — це пошук/порівняння — класичні кандидати на кеш-промахи і невдалі передбачення гілок.
Рішення: перевірте структури даних: чи ключі розсипані? чи ви слідуєте по покажчиках? чи можна упакувати дані? чи можна зменшити кількість порівнянь?

Завдання 9: Перевірити міграцію планувальника (тихий каталізатор NUMA)

cr0x@server:~$ pidstat -w -p 24718 1 3
Linux 6.5.0 (server)  01/09/2026  _x86_64_  (64 CPU)

01:02:11      UID       PID   cswch/s nvcswch/s  Command
01:02:12     1001     24718   1200.00    850.00  myservice
01:02:13     1001     24718   1350.00    920.00  myservice
01:02:14     1001     24718   1100.00    800.00  myservice

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

Завдання 10: Перевірити чергу запуску і насичення по CPU (не плутайте «завантажений» з «прогресує»)

cr0x@server:~$ mpstat -P ALL 1 2
Linux 6.5.0 (server)  01/09/2026  _x86_64_  (64 CPU)

01:03:01 AM  CPU   %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
01:03:02 AM  all   62.0  0.0   9.0   0.1    0.0  0.5    0.0    0.0    0.0   28.4
01:03:02 AM   0    95.0  0.0   4.0   0.0    0.0  0.0    0.0    0.0    0.0    1.0
01:03:02 AM  32    20.0  0.0   5.0   0.0    0.0  0.0    0.0    0.0    0.0   75.0

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

Завдання 11: Перевірити CPU affinity і обмеження cgroup

cr0x@server:~$ taskset -pc 24718
pid 24718's current affinity list: 0-15

Що це означає: процес зафіксовано на CPU 0–15 (підмножина одного сокета). Це може бути навмисно або випадково.
Рішення: якщо зафіксовано, переконайтесь, що пам’ять локальна для цього вузла; якщо випадково, виправте unit-файл або CPU set в оркестраторі.

Завдання 12: Перевірити рівень промахів LLC на процес (perf stat на PID)

cr0x@server:~$ sudo perf stat -p 24718 -e cycles,instructions,LLC-loads,LLC-load-misses -- sleep 10
 Performance counter stats for process id '24718':

     18,320,000,000      cycles
      7,410,000,000      instructions              #    0.40  insn per cycle
        210,000,000      LLC-loads
         78,000,000      LLC-load-misses           #   37.14% of all LLC hits

      10.001948393 seconds time elapsed

Що це означає: ≈37% LLC load miss — великий червоний прапорець, що ваш робочий набір не вміщується в кеш або доступ випадковий.
Рішення: зменшіть робочий набір, підвищіть локальність або змініть розкладку даних. Також перевірте NUMA-локальність.

Завдання 13: Знайти page faults і major faults (підказки про TLB і paging)

cr0x@server:~$ pidstat -r -p 24718 1 3
Linux 6.5.0 (server)  01/09/2026  _x86_64_  (64 CPU)

01:04:10      UID       PID  minflt/s  majflt/s     VSZ     RSS  %MEM  Command
01:04:11     1001     24718   8200.00      0.00  9800000 4200000  12.8  myservice
01:04:12     1001     24718   7900.00      0.00  9800000 4200000  12.8  myservice
01:04:13     1001     24718   8100.00      0.00  9800000 4200000  12.8  myservice

Що це означає: високі minor faults можуть бути нормою (demand paging, mapped files), але якщо fault-и зростають під навантаженням,
це може корелювати зі шматуванням сторінок і тиском на TLB.
Рішення: якщо fault-и корелюють зі спайками затримки, перевірте поведінку алокатора, використання mmap і розгляньте великі сторінки тільки після вимірів.

Завдання 14: Перевірити статус transparent huge pages (THP)

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

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

Завдання 15: Перевірити лічильники пропускної здатності пам’яті (інструменти Intel/AMD відрізняються)

cr0x@server:~$ sudo perf stat -a -e uncore_imc_0/cas_count_read/,uncore_imc_0/cas_count_write/ -- sleep 5
 Performance counter stats for 'system wide':

       8,120,000,000      uncore_imc_0/cas_count_read/
       4,010,000,000      uncore_imc_0/cas_count_write/

       5.001234567 seconds time elapsed

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

Завдання 16: Виявити контенцію блокувань (часто неправильно діагностують як «кеш-проблеми»)

cr0x@server:~$ sudo perf lock report -p 24718
Name                 acquired  contended   total wait (ns)   avg wait (ns)
pthread_mutex_lock      12000       3400      9800000000         2882352

Що це означає: потоки витрачають реальний час у очікуванні блокувань. Це може посилювати ефекти кешу (лінії кешу «стрибають» з правом власності).
Рішення: зменшіть гранулярність блокувань, шардируйте або змініть алгоритм. Не «оптимізуйте пам’ять», якщо вузьке місце — mutex.

Завдання 17: Слідкувати за зайнятістю LLC і затримками пам’яті (якщо підтримується)

cr0x@server:~$ sudo perf stat -p 24718 -e cpu/mem-loads/,cpu/mem-stores/ -- sleep 5
 Performance counter stats for process id '24718':

        320,000,000      cpu/mem-loads/
         95,000,000      cpu/mem-stores/

       5.000912345 seconds time elapsed

Що це означає: інтенсивний трафік load/store вказує, що робота орієнтована на пам’ять. Поєднайте з метриками LLC misses, щоб вирішити,
чи це кеш-дружній робочий процес.
Рішення: якщо багато load-ів і високі промахи, зосередьтесь на локальності структур даних і зменшенні слідування по покажчиках.

Завдання 18: Підтвердити, що ви випадково не тротлите (частота має значення)

cr0x@server:~$ cat /proc/cpuinfo | grep -m1 "cpu MHz"
cpu MHz		: 1796.234

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

Три корпоративні міні-історії з передової

Міні-історія 1: Інцидент через хибне припущення

Платіжний сервіс почав таймаутити щодня приблизно в один і той же час. Команда назвала це «насичення CPU», бо дашборди показували CPU на 90%,
і flame graph підсвічував JSON-парсинг та деякий хешинг. Вони зробили те, що роблять команди: додали інстанси, збільшили пул потоків і підняли пороги автоскейлінгу.
Інцидент став гіршим. Хвостові затримки виросли.

Хибне припущення було тонким: «високий CPU означає, що ядро зайняте обчисленнями». Насправді ядра були зайняті очікуванням. perf stat показав низький IPC
і високий LLC miss rate. Шлях запиту мав кешований lookup, який непомітно розріс: більше ключів, більше метаданих, більше об’єктів з покажчиками,
і робочий набір більше не вміщувався навіть близько до L3.

Зміна масштабування вивела систему на новий режим відмови. Більше потоків означало більше випадкових доступів паралельно, що збільшило memory-level parallelism,
але також — контенцію. Контролер пам’яті «нагрівся», пропускна здатність зросла, і середня затримка піднялася разом з нею. Класика: чим більше тиснеш,
тим сильніше пам’ять відповідає опором.

Виправлення не було героїчним. Вони зменшили накладні витрати об’єктів, упакували поля в суміжні масиви для гарячого шляху і обмежили набір enrichment на запит.
Вони також перестали пінити процес через обидва сокети без контролю розміщення пам’яті. Коли локальність покращилася, завантаження CPU залишилося високим,
але пропускна здатність піднялася, а хвостові затримки впали. Графіки CPU виглядали так само. Система поводилася інакше. Ось урок.

Міні-історія 2: Оптимізація, що відбилася бумерангом

Команда намагалася пришвидшити аналітичне API, «покращивши кеш». Вони замінили простий вектор структур на hash map з ключем-рядком, щоб уникнути лінійних сканів.
Мікробенчмарки на ноутбуці виглядали чудово. Продакшн не погодився.

Нова структура зруйнувала локальність. Старий код сканував суміжний масив: передбачувано, префетч-дружньо, кеш-дружньо. Новий код робив випадкові пошуки,
кожен з яких включав слідування по покажчиках, хешування рядка і кілька залежних завантажень. На реальних серверах під навантаженням це перетворило
цикл дружній до L2/L3 на DRAM-вечірку.

Гірше — хеш-таблиця внесла шлях зміни розміру зі спільним доступом. При спалахах трафіку відбувалися resize-операції, блокування конкурували, і кеш-лінії
«стрибають» між ядрами. Команда бачила вищий CPU і вирішила, що «потрібно більше CPU». Але «більше CPU» збільшило контенцію, і p99 стало гірше.

Вони відкочували зміни і реалізували нудне компромісне рішення: зберегли відсортований вектор для гарячого шляху і робили періодичні перебудови поза потоком запиту,
з стабільним вказівником на снапшот. Вони прийняли O(log n) з хорошою локальністю замість O(1) з жахливими константами. Продакшн знову став нудним, а це — успіх.

Міні-історія 3: Нудна, але правильна практика, що врятувала ситуацію

Сервіс поруч зі сховищем — багато читань метаданих, іноді записи — був перенесений на нову апаратну платформу. Усі очікували покращення. Його не було.
Були спорадичні спайки затримки і періодичні падіння пропускної здатності, але нічого очевидного: немає свопінгу, диски в порядку, мережа нормальна.

У команди була одна звичка, яка врятувала їх: «пакет триажу продуктивності», який вони запускали для будь-якої регресії. Він включав lscpu,
топологію NUMA, perf stat для IPC і LLC misses та швидку перевірку частоти CPU і governor-ів. Нудно. Надійно.

Пакет одразу показав дві несподіванки. По-перше, нові хости мали більше сокетів, і сервіс планувався через сокети без узгодженого розміщення пам’яті.
По-друге, частота CPU була нижчою під тривалим навантаженням через енергетичні налаштування в образі.

Виправлення були процедурними: вони оновили базове налаштування хоста (governor, налаштування прошивки там, де доречно) і закріпили сервіс на одному NUMA-вузлі
з пам’яттю, прив’язаною до нього. Жодних змін у коді. Затримки стабілізувалися. Ролл-аут завершився. Постмортем був короткий — і це розкіш.

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

1) «CPU високий, отже потрібен ще CPU»

Симптоми: CPU 80–95%, пропускна здатність не зростає, p95/p99 гіршає при додаванні потоків/інстансів.
Причина: низький IPC через кеш-промахи або затримки пам’яті; CPU «зайнятий очікуванням».
Виправлення: виміряйте IPC і LLC misses за допомогою perf stat; зменшіть робочий набір, покращіть локальність або вирішіть NUMA. Не масштабируйте потоки сліпо.

2) «Hash map завжди швидше за скан»

Симптоми: стало повільніше після переходу на «O(1)» структуру; perf показує гарячі точки в hashing/strcmp/memcmp.
Причина: випадковий доступ і слідування по покажчиках викликають звернення в DRAM; погана локальність перемагає великі-O на реальному залізі.
Виправлення: віддавайте перевагу суміжним структурам для гарячих шляхів (масиви, вектори, відсортовані вектори). Бенчмарк з продакшн-подібними даними й конкурентністю.

3) «Більше потоків = більше пропускної здатності»

Симптоми: пропускна здатність спочатку росте, потім падає; контекстні перемикання зростають; LLC misses ростуть.
Причина: насичення пропускної здатності пам’яті, контенція локів або false sharing стають домінантними.
Виправлення: обмежте кількість потоків близько до «коліна» кривої; шардируйте локи/лічильники; уникайте спільних гарячих записів; закріпіть потоки, якщо NUMA важлива.

4) «NUMA не має значення; Linux сам усе вирішить»

Симптоми: хороша середня затримка, жахлива хвостова; регресії при переході на багатосокетні хости.
Причина: віддалені звернення до пам’яті і крос-сокетний трафік; міграція планувальника руйнує локальність.
Виправлення: використовуйте numastat і numactl; закріпіть CPU і пам’ять; розгляньте запуск одного процесу на сокет для передбачуваності.

5) «Якщо вимкнути кеші, можна тестувати гірший випадок»

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

6) «Великі сторінки завжди допомагають»

Симптоми: THP увімкнено і періодичні паузи; активність компакції; спайки затримки під час росту пам’яті.
Причина: накладні витрати на алокацію/компакцію THP; невідповідність патернам алокації.
Виправлення: бенчмарк always vs madvise vs never; якщо використовуєте великі сторінки, виділяйте їх заздалегідь і моніторьте хвостову затримку.

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

Чек-лист A: Доведіть, що це пам’ять, а не обчислення

  1. Зніміть топологію CPU: lscpu. Зафіксуйте сокети/NUMA і розміри кешів.
  2. Перевірте свопінг/тиск на пам’ять: vmstat 1. Якщо si/so > 0 — виправляйте пам’ять першочергово.
  3. Виміряйте IPC і LLC misses: perf stat (системно або по PID). Низький IPC + високі LLC misses = підозра на затримки пам’яті.
  4. Знайдіть гарячі функції: perf top. Якщо гарячі точки — lookup/compare/alloc, очікуйте проблеми з локальністю.

Чек-лист B: Визначте, чи затримково- чи пропускно-обмежено

  1. Якщо LLC miss rate високий, але лічильники пропускної здатності помірні: ймовірно, затримково-обмежене слідування по покажчиках.
  2. Якщо лічильники пропускної здатності близькі до меж платформи і ядра не допомагають: ймовірно, пропускно-обмежене сканування/стрім.
  3. Змініть одну річ і переміряйте: зменшіть конкурентність, зменшіть робочий набір або змініть шаблон доступу.

Чек-лист C: Виправте NUMA перед переписуванням коду

  1. Змапте NUMA-вузли: numactl --hardware.
  2. Перевірте пам’ять процесу по вузлах: numastat -p PID.
  3. Перевірте CPU affinity: taskset -pc PID.
  4. Вирівняйте: закріпіть CPU на один вузол і прив’яжіть пам’ять до того ж вузла (тестуйте у стейджингу).

Чек-лист D: Зробіть дані кеш-дружніми (нудне — перемагає)

  1. Сплющуйте структури з великою кількістю покажчиків у гарячих шляхах.
  2. Упаковуйте гарячі поля разом; відокремлюйте холодні поля (hot/cold split).
  3. Віддавайте перевагу масивам/векторам і передбачуваній ітерації замість випадкового доступу.
  4. Шардуйте записо-важкі лічильники; батчіть оновлення.
  5. Бенчмарк з продакшн-подібними розмірами; ефекти кешу з’являються, коли дані досить великі, щоб мати значення.

Питання й відповіді

1) Чи L1 завжди швидший за L2, а L2 завжди швидший за L3?

Загалом так у термінах затримки, але реальна продуктивність залежить від контенції, шаблону доступу й того, чи лінія вже присутня через префетчинг.
Також характеристики пропускної здатності відрізняються; L3 може давати високу агреговану пропускну здатність, але з більшою затримкою.

2) Чому мій CPU показує 90% використання, якщо він «чекає пам’яті»?

Тому що «використання CPU» здебільшого означає, що ядро не простає. Зупинений конвеєр все ще виконує інструкції, обробляє промахи, робить спекуляцію
і спалює цикли. Вам потрібні лічильники (IPC, промахи кешу, stalled cycles), щоб побачити очікування.

3) У чому різниця між кешем CPU і Linux page cache?

Кеші CPU — апаратні та крихітні (KB/MB). Linux page cache — керується ОС, використовує DRAM і кешує файлові дані (GBs).
Вони взаємодіють, але вирішують різні задачі на різних масштабах.

4) Чи можу я «збільшити L3 кеш» за допомогою софту?

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

5) Чому зв’язані списки і дерева з покажчиками працюють погано?

Вони руйнують просторову локальність. Кожен покажчик веде до іншої кеш-лінії, часто далеко. Це означає залежні завантаження і часті звернення до DRAM,
що зупиняє ядро.

6) Коли мені турбуватися про false sharing?

Коли у вас кілька потоків оновлюють різні поля/лічильники в щільних циклах і продуктивність погіршується з додаванням потоків.
Це часто буває в лічильниках метрик, кільцевих буферах і наївних масивах стану на підключення.

7) Чи завжди кеш-промахи — це погано?

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

8) Чи допоможуть швидші CPU вирішити проблеми з пам’яттю?

Іноді вони роблять їх гіршими. Швидші ядра можуть вимагати дані швидше і швидше вдаритися об «memory wall». Платформа з кращою пропускною здатністю пам’яті,
кращою NUMA-топологією або більшими кешами може бути важливішою за сирі GHz.

9) Чи варто закріпити все на одному сокеті?

Для сервісів, чутливих до затримки, закріплення на одному сокеті (і прив’язка пам’яті) може дати великий виграш: передбачувана локальність, менше віддалих звернень.
Для задач, орієнтованих на пропускну здатність, розподіл по сокетах може допомогти — якщо ви зберігаєте локальність і уникаєте спільних гарячих записів.

10) Яку метрику відслідковувати в дашбордах, щоб рано ловити проблеми кешу?

Якщо можете, експортуйте IPC (instructions per cycle) і LLC miss rate або stalled cycles з інструментів perf/PMU. Якщо ні, слідкуйте за патерном:
CPU піднімається, пропускна здатність не росте, затримка піднімається при масштабуванні. Цей патерн кричить «пам’ять».

Висновок: що робити наступного тижня

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

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

  • Додайте perf stat (IPC + LLC misses) у стандартний набір інструментів інцидент-реагування для сторінок «CPU-bound».
  • Задокументуйте NUMA-топологію для кожного класу хостів і вирішіть, чи сервісам варто бути зафіксованими (і як) за замовчуванням.
  • Аудитуйте гарячі шляхи на локальність: сплющуйте структури, розділяйте hot/cold поля і уникайте спільних гарячих записів.
  • Бенчмарк з реалістичними розмірами наборів даних. Якщо ваш бенчмарк вміщується в L3 — це не бенчмарк; це демо.
  • Коли пропонують оптимізацію, ставте одне питання: «Що це робить з промахами кешу і трафіком пам’яті?»

MySQL vs PostgreSQL: JSON-навантаження — швидкий шлях чи довгостроковий біль

Ви додаєте колонку JSON, бо «потрібна просто гнучкість». Потім ваші дашборди гальмують, репліки відстають,
а хтось просить «швидкий ad-hoc запит», який перетворюється на повний перегляд таблиці в мільйонах рядків. JSON — це
скотч у моделюванні даних: іноді врятує ситуацію, іноді саме через нього зникла потреба в порятунку.

MySQL і PostgreSQL обидва підтримують JSON, але вони заохочують дуже різні звички. Один дозволить швидко випустити фічу і
потроху накопичувати технічний борг. Інший дасть потужні індекси й обмеження — але також достатньо мотузки, щоб зв’язати светр з зайвого роздування й блокувань, якщо не бути обережним.

Рішення на одній сторінці: що вибрати і коли

Використовуйте PostgreSQL коли…

  • Потрібні складні запити (containment, перевірка наявності, вкладені фільтри) і ви хочете, щоб оптимізатор мав варіанти. JSONB + GIN у PostgreSQL — це набір інструментів для «дорослого» використання.
  • Вам потрібні обмеження навколо напівструктурованих даних: CHECK-обмеження, expression-індекси, згенеровані колонки та функціональні індекси — усе це перша категорія громадян.
  • Очікуєте, що JSON залишиться довше, ніж квартал. PostgreSQL зазвичай краще «старіє», коли JSON стає частиною основної схеми.
  • Ви компетентно керуєте VACUUM. PostgreSQL винагородить вас, але тільки якщо ви поважаєте роботу MVCC з прибиранням.

Використовуйте MySQL коли…

  • Використання JSON — це переважно зберігання документа й вибірка, а не важке аналітичне фільтрування. Якщо запити — «взяти за id, повернути blob», MySQL цілком підходить.
  • Ви покладаєтесь на згенеровані колонки, щоб проектувати «гарячі» JSON-шляхи в індексовані скалярні значення. Це практичний шлях MySQL до передбачуваності.
  • Ви вже стандартизувалися на MySQL операційно і JSON — невелика частина навантаження. Послідовні операції кращі за теоретичну елегантність.

Що я порадив би команді в продакшені

Якщо ваші колонки JSON — це тимчасове рішення (швидкий інгест, нормалізувати пізніше), обирайте ту СУБД, якою ваша команда вже вміє керувати. Але якщо JSON — це контракт інтерфейсу (події, конфігурації, feature flags, атрибути користувача) і ви очікуєте запитів всередині нього в масштабі, PostgreSQL зазвичай безпечніший довгостроковий вибір.

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

Цитата, яка має бути в кожному on-call runbook: «Сподівання — це не стратегія.» — широко повторюваний операційний афоризм (парафраз). З JSON сподіватися, що база «сама розбереться», — це як купити собі інцидент на вихідні.

Факти та історія: як ми дійшли до цього

Зберігання JSON у базі виглядає сучасно, але індустрія обдумувала цю ідею десятиліттями: «зберігати гнучкі дані поряд зі структурованими й запитувати їх без втрати транзакційної цілісності.» Деталі різняться, і саме через них ви читаєте це, а не спите.

8 фактів, які варто тримати в голові

  1. PostgreSQL додав JSON у 9.2 (2012), а потім представив JSONB у 9.4 (2014) для бінарного зберігання та кращої індексації.
  2. MySQL представив власний тип JSON у 5.7 (2015); до того це був TEXT з надією та регулярними виразами.
  3. JSONB нормалізує порядок ключів і видаляє дублікати ключів (перемагає останній ключ). Це чудово для індексації, але може дивувати, якщо ви хочете зберегти «рівно те, що надіслали».
  4. MySQL теж зберігає JSON у бінарному форматі і валідовує JSON при вставці, уникаючи деяких жахів з «невалідними blob».
  5. GIN-індекси в PostgreSQL спочатку були створені для повнотекстового пошуку, потім стали робочою конячкою для containment у JSONB.
  6. Згенеровані колонки в MySQL існують з 5.7, і вони — причина того, чому багато MySQL-деплойментів з JSON не розвалюються.
  7. MVCC у PostgreSQL означає, що оновлення створюють нові версії рядків; великі оновлення JSON можуть посилювати роздування, якщо VACUUM відстає.
  8. Формати реплікації важливі: row-based binlog у MySQL і logical decoding у PostgreSQL поводяться по-різному при частих оновленнях JSON і «гарячих» рядках.

Семантика JSON: що насправді зберігають рушії

MySQL: JSON — це тип, але ставтеся до нього як до документа, якщо ви не проектуєте поля

Тип JSON у MySQL — це не «TEXT з міткою». Він валідовується, зберігається в бінарному представленні й маніпулюється JSON-функціями.
Це хороша новина. Операційна реальність така, що ви рідко отримаєте стабільну продуктивність, якщо не зробите одне з двох:
(1) зберігати JSON переважно для запису й читати за первинним ключем, або (2) виносити часто запитувані шляхи в згенеровані колонки й індексувати їх.

MySQL охоче дозволить вам написати запит, що виглядає селективним, але не є індексованим. Оптимізатор зробить, що зможе, а потім просканує.
Іноді ви можете врятувати ситуацію функціональними індексами (залежить від версії) або згенерованими колонками, але потрібно діяти навмисно.

PostgreSQL: JSONB — для запитів; JSON (текст) — для збереження точного вводу

PostgreSQL дає дві філософії:
json зберігає оригінальний текст (включаючи пробіли та порядок ключів), а
jsonb зберігає декомпозований бінарний формат, оптимізований для операторів і індексації.
Якщо вам потрібна продуктивність, майже завжди варто обирати JSONB.

Оператори PostgreSQL виразні: containment (@>), перевірка наявності (?, ?|, ?&),
вилучення шляхом (->, ->>, #>, #>>), а також запити JSON path.
Ця виразність може стати пасткою: люди пишуть хитрі фільтри, які виглядають дешевими, але стають CPU-залежними під час декомпресії або зависають на індексі, що не відповідає предикату.

Жарт 1/2: JSON — як ящик для мотлоху — все вміщується, поки вам не треба знайти ножиці.

Індексація JSON: де народжується або втрачається продуктивність

Індексація в MySQL: згенеровані колонки — це дорослий хід

В MySQL індексація довільних JSON-виразів покращувалась з часом, але найнадійніша операційна схема досі:
визначити згенеровані колонки для кількох JSON-шляхів, які ви запитуєте постійно, привести їх до стабільних скалярних типів і проіндексувати.
Це дає три переваги:

  • Дає оптимізатору звичайний B-tree індекс, який він розуміє.
  • Уникає повторного вилучення JSON під час виконання.
  • Змушує вас визнати, які поля насправді є частиною «реальної схеми».

Ловушка: зміни в схемі стають повільнішими і політично більш чутливими, бо тепер JSON-blob має щупальця в DDL і міграціях.
Це не баг. Це ціна за те, що ви робите з напівструктурованих даних структуру (бо вона нею стає, щойно ви на неї опираєтесь).

Коли індексація JSON у MySQL дає збій на практиці

  • Надто динамічні предикати (різні JSON-шляхи залежно від вводу користувача) штовхають до сканувань.
  • Порівняння JSON-рядків з числами викликає неявні приведення типів і ламає використання індексів.
  • Використання функцій у WHERE без індексованого виразу змушує оптимізатор знехтувати індексом і робити все повільно.

Індексація в PostgreSQL: GIN потужний, але потрібно обирати клас оператора

Історія індексації JSONB у PostgreSQL сильніша, але це не магія. GIN-індекси можуть прискорити containment і перевірку наявності ключів,
але вони мають різні класи операторів:

  • jsonb_ops: індексує більше типів операцій, але може бути більшим за розміром.
  • jsonb_path_ops: компактніший і швидший для containment, але підтримує менше операторів.

Якщо ваше навантаження — «знайти рядки, де JSON містить ці пари», jsonb_path_ops часто є правильним вибором.
Якщо потрібна гнучка перевірка наявності й ширша підтримка операторів — jsonb_ops.
Оберіть неправильно, і ви отримаєте індекс, що існує лише для того, щоб робити VACUUM нещасним.

Expression-індекси: практичний міст між JSON і реляційним

Якщо ви часто фільтруєте по одному витягнутому полю (наприклад, payload->>'customer_id'), expression-індекс може бути компактнішим за широкий GIN
за розміром і передбачуваністю. Також ним легше оперувати, оцінюючи селективність.

Жарт 2/2: GIN-індекс — як кофеїн — неймовірний, коли влучно застосований, шкода, коли перестаратися.

Шаблони запитів, що розділяють «нормально» від «горить»

Шаблон 1: «Отримати за id і повернути JSON» (відносно безпечно)

І MySQL, і PostgreSQL добре з цим справляються. Домінуючий витрат — це I/O і розмір рядка, а не JSON-функції.
Де команди потрапляють у пастку — повільне наростання: JSON росте, рядок збільшується, ефективність кешу падає, і раптом «прості читання» стають дисковими читаннями.

Шаблон 2: «Фільтрація за ключами JSON з великою кардинальністю» (індекс або помирай)

Якщо ви фільтруєте за user_id, tenant_id, order_id всередині JSON, ви фактично фільтруєте за реляційним ключем.
Не прикидайтеся, що це гнучко. Виведіть це: згенерована колонка + індекс у MySQL, expression-індекс у Postgres або зробіть це справжньою колонкою.
Це не ідеологія. Це про уникнення повних сканів і нестабільних планів виконання.

Шаблон 3: «Ad-hoc аналітика по JSON» (бережіться повільного наростання)

JSON привабливий для аналітики, бо самопояснюваний. У продакшн OLTP-базах це пастка.
Ad-hoc аналітика зазвичай:

  • Використовує функції над багатьма рядками, викликаючи навантаження на CPU.
  • Примушує послідовні скани, бо предикати не відповідають індексам.
  • Серіалізує ваше навантаження на одній великій таблиці й одному «гарячому» дисковому підсистемі.

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

Шаблон 4: часткові оновлення JSON (гарячі рядки, важкі логи)

Обидві СУБД можуть оновлювати шляхи всередині JSON, але характеристики продуктивності різняться, а операційний вплив схожий:
часті оновлення великих JSON-документів означають більше записаних байтів, більше коливань індексів, більше роботи для реплікації і більше інвалідизації кешу.

Практичне правило: якщо поле JSON часто оновлюється й часто читається, воно заслуговує на реальну колонку або окрему таблицю.
JSON — не безкоштовний квиток на нормалізацію; це відкладений рахунок.

Оновлення, WAL/binlog і відставання реплік

MySQL: реалії обсягу binlog і row-based реплікації

У MySQL великі оновлення JSON можуть породжувати великі binlog-події — особливо при row-based реплікації. Якщо ви оновлюєте багато рядків або великі документи, ваші репліки платять за це.
Відставання реплік рідко є «проблемою репліки». Це проблема ампліфікації записів додатком.

Також слід стежити за розміром транзакцій і частотою комітів. Навантаження, що оновлює JSON пачками, може створити неприємні сплески: тиск на fsync,
затримки flush binlog і беклог SQL-потоку репліки.

PostgreSQL: тиск WAL + churn MVCC

PostgreSQL записує WAL для змін, а MVCC означає, що оновлення створюють нові версії рядків. Часті оновлення великого поля JSONB дадуть:
більше WAL, більше «мертвих» кортежів, більше роботи для vacuum і потенційно більше роздування індексів.

Відставання реплік проявляється як backlog у WAL sender або затримка відтворення. Важливо відрізняти:
репліка не встигає застосовувати зміни (CPU/I/O при застосуванні) проти
праймар породжує забагато WAL (ампліфікація записів).

Операційні поради

  • Вимірюйте байти WAL/binlog за секунду під час піку. Це найближче до «правди» про ампліфікацію записів.
  • Розділяйте або виокремлюйте гарячі JSON-поля, якщо частота оновлень висока.
  • У PostgreSQL налаштуйте autovacuum для таблиць з інтенсивними оновленнями JSON, інакше борг vacuum проявиться як борг латентності.

Зберігання і I/O: роздування, перезапис сторінок і поведінка кешу

Розмір рядка і кеш: ваш невидимий податок

Колонки JSON збільшують рядки. Більші рядки означають менше рядків на сторінці. Менше рядків на сторінці означає більше читань сторінок для тієї самої кількості логічних рядків.
Це проявляється як:

  • Вищий churn буферного пулу в MySQL (InnoDB).
  • Більше обертання shared_buffers у PostgreSQL.
  • Більший тиск на сторінковий кеш ОС.

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

Роздування у PostgreSQL: MVCC означає, що ви винні колектору vacuum

PostgreSQL не оновлює на місці; він створює нові версії рядків. Якщо JSONB великий і часто оновлюється, накопичуються мертві кортежі, а індекси
піддаються змінам. Autovacuum може впоратися з багатьма випадками, але йому потрібні правильні пороги. Налаштування за замовчуванням призначені бути безпечними для початківців, а не оптимальними для вашого безладу.

MySQL: вторинні індекси та тиск undo/redo

InnoDB має власну ампліфікацію записів: redo logs, undo logs, doublewrite buffer, обслуговування вторинних індексів.
Великі оновлення JSON збільшують кількість затриманих байтів і можуть штовхнути вас у збій flush-логів. Ви побачите це як періодичні сплески латентності,
«раптом повільні коміти» і відставання реплік.

Практичні завдання: 14 команд, які можна запустити сьогодні

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

Завдання 1 (MySQL): підтвердити використання JSON і тиск на розмір

cr0x@server:~$ mysql -e "SELECT table_schema, table_name, column_name, data_type FROM information_schema.columns WHERE data_type='json' ORDER BY table_schema, table_name;"
+--------------+------------+-------------+-----------+
| table_schema | table_name | column_name | data_type |
+--------------+------------+-------------+-----------+
| app          | events     | payload     | json      |
| app          | users      | attrs       | json      |
+--------------+------------+-------------+-----------+

Значення: тепер ви знаєте, які таблиці кандидати на JSON-проблеми.
Рішення: скоротіть список до топ-1–3 таблиць за кількістю рядків і частотою оновлень. Саме там індексація й вибір схеми мають значення.

Завдання 2 (MySQL): перевірити розміри таблиць і індексів

cr0x@server:~$ mysql -e "SELECT table_name, table_rows, ROUND(data_length/1024/1024,1) AS data_mb, ROUND(index_length/1024/1024,1) AS index_mb FROM information_schema.tables WHERE table_schema='app' ORDER BY data_length DESC LIMIT 10;"
+------------+------------+---------+----------+
| table_name | table_rows | data_mb | index_mb |
+------------+------------+---------+----------+
| events     |    4821031 |  8120.4 |   2104.7 |
| users      |     820114 |  1190.8 |    412.2 |
+------------+------------+---------+----------+

Значення: таблиці з великою вагою JSON зазвичай роздувають data_mb.
Рішення: якщо data_mb росте швидше, ніж бізнес, потрібно обмежити розмір payload, стиснути дані на вході або нормалізувати гарячі поля.

Завдання 3 (MySQL): знайти повільні JSON-предикати в slow log

cr0x@server:~$ sudo pt-query-digest /var/log/mysql/mysql-slow.log --limit 5
#  1.2s user time, 40ms system time, 27.31M rss, 190.55M vsz
# Query 1: 0.68 QPS, 0.31x concurrency, ID 0xA1B2C3D4 at byte 91234
# Time range: 2025-12-28T00:00:00 to 2025-12-28T01:00:00
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Exec time     62   180s    120ms     12s    540ms     3s   900ms   300ms
# Rows examine  90  1200M      10   2.5M   360k   1.1M   500k   200k
# Query: SELECT ... WHERE JSON_EXTRACT(payload,'$.customer.id') = ?

Значення: rows examined — ваш «податок на сканування». JSON_EXTRACT у WHERE без індексу — типовий підозрюваний.
Рішення: створіть згенеровану колонку для цього шляху (або функціональний індекс, якщо доречно) і перепишіть запит, щоб використовувати її.

Завдання 4 (MySQL): перевірити, чи використовує запит індекс

cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM app.events WHERE JSON_UNQUOTE(JSON_EXTRACT(payload,'$.customer.id'))='12345' LIMIT 10\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4821031
     filtered: 10.00
        Extra: Using where

Значення: type: ALL і відсутність key означають повний перегляд таблиці.
Рішення: не налаштовуйте спочатку буфери. Виправляйте схему/запит: згенерована колонка + індекс або редизайн.

Завдання 5 (MySQL): додати згенеровану колонку для гарячого JSON-шляху

cr0x@server:~$ mysql -e "ALTER TABLE app.events ADD COLUMN customer_id VARCHAR(64) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(payload,'$.customer.id'))) STORED, ADD INDEX idx_events_customer_id (customer_id);"
Query OK, 0 rows affected (2 min 41 sec)
Records: 0  Duplicates: 0  Warnings: 0

Значення: STORED-згенерована колонка матеріалізує значення, індекс стає доступним.
Рішення: перепишіть запити додатка, щоб фільтрувати за customer_id замість JSON_EXTRACT у WHERE. Потім перевірте EXPLAIN.

Завдання 6 (MySQL): перевірити, чи оптимізатор тепер використовує новий індекс

cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM app.events WHERE customer_id='12345' LIMIT 10\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
         type: ref
possible_keys: idx_events_customer_id
          key: idx_events_customer_id
      key_len: 258
          ref: const
         rows: 120
        Extra: Using index

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

Завдання 7 (MySQL): перевірити відставання репліки та тиск на застосування

cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep "Seconds_Behind_Source|Replica_SQL_Running|Replica_IO_Running|Last_SQL_Error"
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 87
Last_SQL_Error:

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

Завдання 8 (PostgreSQL): перелічити JSON/JSONB колонки та їх таблиці

cr0x@server:~$ psql -d appdb -c "SELECT table_schema, table_name, column_name, data_type FROM information_schema.columns WHERE data_type IN ('json','jsonb') ORDER BY 1,2,3;"
 table_schema | table_name | column_name | data_type
--------------+------------+-------------+-----------
 public       | events     | payload     | jsonb
 public       | users      | attrs       | jsonb
(2 rows)

Значення: охоплення. Як і в MySQL: знайдіть кілька таблиць, що мають найбільший вплив.
Рішення: фокусуйтеся на таблицях з великою частотою оновлень і запитами, що впливають на клієнтів.

Завдання 9 (PostgreSQL): знайти найгірші JSON-запити за загальним часом

cr0x@server:~$ psql -d appdb -c "SELECT calls, total_exec_time::bigint AS total_ms, mean_exec_time::numeric(10,2) AS mean_ms, rows, query FROM pg_stat_statements WHERE query ILIKE '%jsonb%' OR query ILIKE '%->%' OR query ILIKE '%@>%' ORDER BY total_exec_time DESC LIMIT 5;"
 calls | total_ms | mean_ms | rows |                   query
-------+----------+---------+------+-------------------------------------------
 18211 |   932144 |   51.20 |    0 | SELECT ... WHERE payload @> $1
  4102 |   512030 |  124.82 |    0 | SELECT ... WHERE (payload->>'customer')= $1
(2 rows)

Значення: у вас гарячі запити, а не теоретичні проблеми.
Рішення: запустіть EXPLAIN (ANALYZE, BUFFERS) на топ-обвинувачах і побудуйте правильний індекс для форми предикату.

Завдання 10 (PostgreSQL): переглянути план запиту JSONB з інформацією про буфери

cr0x@server:~$ psql -d appdb -c "EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM events WHERE payload @> '{\"customer\":{\"id\":\"12345\"}}'::jsonb LIMIT 10;"
                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..8.44 rows=10 width=8) (actual time=0.088..0.146 rows=10 loops=1)
   Buffers: shared hit=42
   ->  Index Scan using idx_events_payload_gin on events  (cost=0.42..22134.77 rows=26235 width=8) (actual time=0.086..0.141 rows=10 loops=1)
         Index Cond: (payload @> '{"customer": {"id": "12345"}}'::jsonb)
         Buffers: shared hit=42
 Planning Time: 0.412 ms
 Execution Time: 0.182 ms
(7 rows)

Значення: index scan + переважно попадання у буфери = здорово.
Рішення: зберігайте цей індекс, якщо він підтримує основні продуктові шляхи. Якщо він лише для ad-hoc запитів — не платіть за нього вартість записів.

Завдання 11 (PostgreSQL): створити цільовий GIN-індекс (обрати клас оператора)

cr0x@server:~$ psql -d appdb -c "CREATE INDEX CONCURRENTLY idx_events_payload_pathops ON events USING gin (payload jsonb_path_ops);"
CREATE INDEX

Значення: concurrent build уникає блокування записів (ціною часу і додаткової роботи).
Рішення: використовуйте jsonb_path_ops, коли домінує containment; інакше розглядайте jsonb_ops або expression-індекси.

Завдання 12 (PostgreSQL): побудувати expression-індекс для одного гарячого шляху

cr0x@server:~$ psql -d appdb -c "CREATE INDEX CONCURRENTLY idx_events_customer_id_expr ON events ((payload->'customer'->>'id'));"
CREATE INDEX

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

Завдання 13 (PostgreSQL): перевірити сигнали роздування і ефективність autovacuum

cr0x@server:~$ psql -d appdb -c "SELECT relname, n_live_tup, n_dead_tup, last_autovacuum, last_vacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
 relname | n_live_tup | n_dead_tup |    last_autovacuum     |     last_vacuum
---------+------------+------------+------------------------+------------------------
 events  |    4809123 |     912044 | 2025-12-28 00:41:12+00 | 2025-12-22 03:11:02+00
 users   |     820104 |      12033 | 2025-12-28 00:38:01+00 | 2025-12-23 02:08:40+00
(2 rows)

Значення: мертві кортежі в events великі; autovacuum працює, але може бути недопровідним для рівня churn.
Рішення: налаштуйте autovacuum для конкретної таблиці, зменшіть частоту оновлень великого JSONB або виокреміть гарячі змінні поля.

Завдання 14 (Системний рівень): визначити, чи ви обмежені I/O або CPU

cr0x@server:~$ iostat -x 1 5
Linux 6.1.0 (db01) 	12/29/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.11    0.00    6.34   18.90    0.00   52.65

Device            r/s     rkB/s   rrqm/s  %rrqm  r_await rareq-sz     w/s     wkB/s   w_await wareq-sz  aqu-sz  %util
nvme0n1         320.0  18240.0     0.0   0.00    4.20    57.00   410.0  24576.0    9.80    59.95   6.10   92.0

Значення: високий %util і значний iowait вказують на насичення сховища. JSON-навантаження часто збільшують I/O через більші рядки і churn індексів.
Рішення: спочатку виправте шаблони запитів/індексів; якщо все ще насичено — масштабувати IOPS (кращі диски) або знизити ампліфікацію записів (зміни в схемі/дизайні).

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

Коли JSON-запити повільні, люди витрачають години на суперечки про «вибір СУБД» замість того, щоб знайти реальний горловий вузол.
Цей план — порядок дій під час інциденту — бо він швидко сходиться до висновку.

Перше: доведіть, чи це скан, пропущений індекс або сирий I/O

  • MySQL: запустіть EXPLAIN для повільного запиту. Якщо type: ALL, зупиніться і виправте предикат/індекс.
  • PostgreSQL: запустіть EXPLAIN (ANALYZE, BUFFERS). Якщо бачите sequential scans на великих таблицях — потрібен відповідний індекс або перепис запиту.
  • Система: перевірте iostat -x. Якщо сховище завантажене, скани і роздування будуть головними підозрюваними.

Друге: кількісно оцініть ампліфікацію записів і тиск на реплікацію

  • MySQL: перевіряйте відставання реплік і патерни росту binlog; великі оновлення JSON часто корелюють із стрибками відставання.
  • PostgreSQL: перевіряйте генерацію WAL і мертві кортежі; інтенсивні оновлення JSON можуть перетворити vacuum на постійну фонову кризу.

Третє: перевірте ефективність кешу і наростання розміру рядка

  • Чи ваш гарячий робочий набір все ще в пам’яті, або ріст JSON його витіснив?
  • Чи ви додали широкий GIN-індекс, який подвоїв вартість записів?
  • Чи хтось почав робити ad-hoc фільтри по неіндексованих ключах JSON?

Четверте: виправте найменшу річ, що змінює криву

  • Підніміть гарячі ключі в реальні колонки (найкраще) або згенеровані/expression колонки (наступний крок).
  • Додайте правильний індекс під форму предикату, потім валідуйте з EXPLAIN.
  • Якщо оновлення — проблема, виокреміть змінні поля з JSON blob.

Поширені помилки: симптом → корінь → виправлення

Помилка 1: «Запит виглядає селективним, але повільний»

Симптоми: латентність росте з розміром таблиці; EXPLAIN показує повний скан; CPU піки під час навантаження.

Корінь: вилучення JSON у WHERE без індексованого виразу (MySQL), або невідповідність між оператором і індексом (PostgreSQL).

Виправлення: MySQL: STORED згенерована колонка + B-tree індекс; PostgreSQL: expression-індекс або правильний клас оператора GIN; перепишіть предикат, щоб він відповідав індексу.

Помилка 2: «Додали GIN-індекс і записи стали повільніші»

Симптоми: зростає латентність вставок/оновлень; стрибає швидкість WAL/binlog; відставання реплік погіршується після створення індексу.

Корінь: широкий GIN-індекс на великому JSONB з частими оновленнями; висока вартість обслуговування індексу.

Виправлення: замінити на вузькі expression-індекси; використовувати jsonb_path_ops, якщо тільки containment; виокремити змінні поля; пересвідчитися, що цей запит потрібен в OLTP.

Помилка 3: «Postgres повільнішає з часом; vacuum не встигає»

Симптоми: ростуть розміри таблиць і індексів; запити гальмують; autovacuum працює постійно; багато мертвих кортежів.

Корінь: часті оновлення великих полів JSONB створюють багато мертвих кортежів; пороги autovacuum не налаштовані під рівень churn таблиці.

Виправлення: налаштувати autovacuum під таблицю; зменшити частоту/розмір оновлень; перемістити змінні дані в окрему таблицю; розглянути партиціювання для таблиць подій.

Помилка 4: «MySQL відстає після додавання JSON-фіч»

Симптоми: Seconds_Behind_Source зростає під час сплесків; репліки відновлюються повільно; коміти стрибкоподібні.

Корінь: великі row-based binlog-події від оновлень JSON; надто великі транзакції; забагато вторинних індексів на проєктованих полях JSON.

Виправлення: зменшити обсяг оновлень JSON; змінити батчинг; обмежити індексовані проекції лише дійсно гарячими шляхами; перевірити налаштування binlog/redo log і патерни комітів.

Помилка 5: «Ми зберегли все в JSON і тепер нам потрібні обмеження»

Симптоми: неконсистентні значення в JSON; валідація на рівні додатка дрейфує; запити мають обробляти відсутні ключі і неправильні типи.

Корінь: схема віддана на виконання коду додатка; відсутні інструменти примусових обмежень; міграції відкладені занадто довго.

Виправлення: підніміть ключові поля в колонки; додайте CHECK-обмеження (Postgres) або забезпечте через згенеровані колонки + NOT NULL (MySQL); введіть версіонування payload-ів.

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

1) Інцидент через хибне припущення: «JSON фактично безкоштовний для запитів»

Середня SaaS-компанія випустила «activity feed», підкріплений таблицею подій. Кожна подія мала JSON payload.
Продуктова команда хотіла фільтрацію: «показати тільки події, де payload.actor.role = ‘admin’». Легко, подумали вони.
Backend використовував MySQL, і перша реалізація використовувала JSON_EXTRACT у WHERE.

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

Хибне припущення було не в тому, що «MySQL не може робити JSON». Воно полягало в тому, що «якщо предикат вузький, база оптимізує його».
Бази оптимізують те, що ви індексуєте, а не те, на що ви сподіваєтесь. Вилучення JSON без індексованого виразу — не вузька операція; це дорога операція, повторювана по багатьох рядках.

Виправлення було болісно простим: додати STORED згенеровану колонку для actor_role, індексувати її і змінити запит.
Постмортем додав правило: будь-який ключ JSON, який використовується в гарячому WHERE, має бути спроєктований і проіндексований або переміщений в реальну колонку.
Гнучка схема лишалася, але тільки там, де вона не на критичному шляху.

2) Оптимізація, що обернулася проти: «Просто додайте великий GIN-індекс»

Інша компанія працювала на PostgreSQL і мала одну величезну таблицю events з JSONB payload-ами.
Вони хотіли швидкий ad-hoc пошук для клієнтської підтримки, тому хтось додав широкий GIN-індекс на весь payload з використанням класу оператора за замовчуванням.
Швидкість запитів покращилася миттєво. Усі аплодували і перейшли далі.

Через два тижні латентність записів почала повільно повзти вгору. Активність autovacuum стала постійною. З’явилося відставання реплік під час піку.
GIN-індекс був дорогим у підтримці, бо payload-и були великі й часто оновлювалися полями enrichment.
Індекс також швидко ріс, збільшуючи навантаження на чекпойнти і I/O. Перемога в пошуку підтримки перетворилася на проблему «кожен API-ендпоінт став повільнішим».

Фіаско полягало не в тому, що GIN поганий. Воно полягало в тому, що вони проіндексували все для робочого навантаження, яке фактично не було критичним.
Індекс перетворив базу на пошукову систему. PostgreSQL це може робити, але ви платите в ампліфікації записів і роздуванні.

Остаточне вирішення: прибрати широкий індекс, додати два expression-індекси для кількох ключів, які використовувала служба підтримки,
і перенести повнотекстовий пошук з OLTP-шляху. Підтримка отримала свій робочий процес, а продакшн перестав платити податок на кожен запис.

3) Нудна, але правильна практика, що врятувала день: «Зробіть JSON контрактом, версіонуйте й тестуйте»

Фінтех-подібна команда зберігала метадані верифікації клієнта в JSONB у PostgreSQL. Там були вкладені поля, опціональні ключі й блоки від вендорів.
Вони знали, що ці дані будуть еволюціонувати, і також знали, що потрібно надійно запитувати кілька полів для звітів з комплаєнсу.
Тому вони зробили те, що здається нецікавим: додали колонку schema_version типу integer і писали явні міграції для змін форми payload.

Також вони підняли кілька критичних полів до реальних колонок: customer_id, verification_status і vendor_name.
Усе інше жило в JSONB. На додачу були CHECK-обмеження, що гарантували відповідність status відомому набору,
і тести додатка, які перевіряли сумісність схем JSON за версіями.

Через місяці вендор трохи змінив формат payload (поле перемістилося глибше).
Команди, що зберігають сирий JSON без контракту, зазвичай дізнаються про це тоді, коли звіти ламаються о 2-й ночі.
Ця команда виявила проблему в CI, бо тест валідації схеми впав і інструмент міграції змусив явне перетворення.

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

Контрольні списки / покроковий план

Якщо ви починаєте нову фічу з великою кількістю JSON

  1. Запишіть топ-5 шаблонів запитів, які ви очікуєте протягом наступних шести місяців (не лише тиждень запуску).
  2. Класифікуйте поля: незмінні vs змінні; часто фільтруються vs рідко фільтруються; велика кардинальність vs мала.
  3. Підніміть «часто фільтровані, з великою кардинальністю» поля в реальні колонки (переважно) або згенеровані/expression колонки.
  4. Оберіть стратегію індексування, специфічну для СУБД:
    • MySQL: STORED згенеровані колонки + B-tree індекси; уникайте JSON_EXTRACT у гарячих WHERE.
    • PostgreSQL: expression-індекси для гарячих шляхів; GIN для containment/перевірки наявності; вибирайте клас оператора свідомо.
  5. Встановіть бюджети розміру payload (м’які й жорсткі обмеження). Ріст JSON безшумний, поки не стане критичним.
  6. Плануйте еволюцію: додайте schema_version, документуйте трансформації і зробіть міграції рутиною.

Якщо ви вже випустили і все повільно

  1. Знайдіть топ-3 запити за сумарним часом (slow log / pg_stat_statements).
  2. Запустіть EXPLAIN з реальністю (MySQL EXPLAIN, Postgres EXPLAIN ANALYZE BUFFERS). Не гадати.
  3. Додайте найменший індекс, що відповідає предикату (індекс на згенеровану колонку або expression-індекс) і перевірте зміну плану.
  4. Виміряйте вартість на стороні запису після індексації (латентність комітів, швидкість WAL/binlog, відставання реплік).
  5. Якщо оновлень багато, виокреміть змінні поля з JSON у окрему таблицю з правильним ключем.
  6. Введіть обмеження для ad-hoc запитів (таймаути, read replicas або виділений шлях для звітності).

Якщо ви вирішуєте між MySQL і PostgreSQL для JSON сьогодні

  • Вибирайте PostgreSQL, якщо запити по JSON — це продуктова фіча, а не реалізаційна деталь.
  • Вибирайте MySQL, якщо JSON переважно для зберігання і ви готові проектувати гарячі ключі в індексовані згенеровані колонки.
  • Оберіть СУБД, якою ваша команда вміє керувати в умовах інциденту. Теоретично краща функціональність не підніме вашого on-call о 3-й ранку.

FAQ

1) Чи завжди PostgreSQL кращий для JSON, ніж MySQL?

Ні. PostgreSQL зазвичай кращий для складних запитів і гнучкої індексації. MySQL може бути відмінним, якщо ви тримаєте використання JSON простим
або проектуєте гарячі шляхи в згенеровані колонки. «Завжди» — це шлях до аутейджів.

2) Чи варто зберігати JSON як TEXT/VARCHAR натомість?

Зазвичай ні. Ви втрачаєте валідацію і багато JSON-операторів. Якщо ви справді ніколи не запитуєте внутрішнє вміст JSON і лише зберігаєте/отримуєте його,
TEXT може працювати — але ви берете на себе ризик гігієни даних. Нативні типи JSON безпечніші для коректності.

3) Коли ключ JSON має стати реальною колонкою?

Якщо він використовується в join-ах, у гарячих WHERE, для сортування або потрібен для обмежень — це колонка. Якщо часто оновлюється,
скоріше за все, це колонка або окрема таблиця. JSON — для змінності, але не для основної ідентичності.

4) Чи вирішують GIN-індекси проблеми продуктивності JSONB у PostgreSQL?

Вони вирішують деякі проблеми. Але можуть створити інші (вартість записів, роздування, обслуговування).
Використовуйте GIN, коли ваші предикати відповідають containment/перевірці наявності і дані стабільні настільки, щоб виправдати write tax.

5) Який еквівалент GIN-індексу JSONB у MySQL?

Прямого еквіваленту немає. У MySQL зазвичай створюють згенеровані колонки, що витягують скалярні значення, і індексують їх.
Це інша філософія: ви вирішуєте заздалегідь, що важливо.

6) Як запобігти «випадковим ключам скрізь» у JSON?

Ставтеся до JSON як до контракту: версіонуйте його, валідовуйте і документуйте дозволені форми.
Примусьте критичні інваріанти базою даних (Postgres) або через згенеровані колонки + NOT NULL/касти типів (MySQL).

7) Чому часткові оновлення JSON досі дорогі?

Бо «часткове оновлення» на рівні SQL може все одно означати значне перезаписування й churn індексів на рівні зберігання,
плюс обсяг WAL/binlog. Великі документи, що часто оновлюються, будуть дорогими незалежно від того, як гарно виглядає SQL.

8) Чи можна використовувати JSON для мульти-тенантних даних і просто фільтрувати по tenant_id всередині JSON?

Можна, але не варто. Ізоляція орендарів належить реальній колонці з індексом.
Покладання tenant_id у JSON полегшує випадкове сканування по орендарях і ускладнює примусові обмеження й межі продуктивності.

9) Який найбезпечніший «гібридний» шаблон?

Зберігайте ключові поля як колонки (id, status, timestamps, foreign keys), зберігайте опційні/вендор-специфічні поля в JSON/JSONB,
і індексуйте лише невелику підмножину JSON-шляхів, які ви фактично запитуєте. Решта лишається гнучкою без росту витрат на основні запити.

Висновок: наступні кроки, що не принизять вас пізніше

JSON у MySQL і PostgreSQL вже не новинка. Це інструмент для продакшену — і як усі такі інструменти, він винагороджує дисципліну.
MySQL схильний вимагати від вас проектування структури з JSON і явного індексування. PostgreSQL дає більше виразних можливостей для запитів і індексування,
але виставить рахунок у WAL, роздуванні і обслуговуванні, якщо ви індексуєте занадто широко або часто оновлюєте великі JSONB-поля.

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

  1. Визначте топ-3 JSON-запити за загальним часом і запустіть EXPLAIN з реальними статистиками виконання.
  2. Підніміть топ-3 ключі JSON, які використовуються для фільтрації/джоїнів, у колонки або згенеровані/expression колонки й індексуйте їх.
  3. Виміряйте ампліфікацію записів (WAL/binlog rate) до і після індексації; слідкуйте за відставанням реплік.
  4. Введіть бюджет розміру payload і застосовуйте його на етапі інгесту.
  5. Версіонуйте ваші JSON-пейлоади. Інакше майбутній ви витратить вихідні на розшифровку «чому цей ключ іноді існує».

Обирайте СУБД, що відповідає операційним сильним сторонам вашої команди, а потім проектуйте використання JSON так, ніби воно стане постійним — бо так зазвичай і буває.

ZFS SMB: Виправлення проблеми «Копіювання у Windows повільне»

Windows Explorer показує «Копіювання… 112 MB/s» протягом трьох секунд, потім швидкість падає до 0 B/s і зависає, ніби роздумує про сенс життя. Користувачі звинувачують «мережу». Мережа звинувачує «зберігання». Зберігання звинувачує «Windows». Усі помиляються по-своєму.

Якщо ви працюєте з SMB на базі ZFS (зазвичай Samba на Linux або інколи пристрій зберігання), ви можете зробити копіювання з Windows стабільно швидким. Але не через хаотичне крутіння налаштувань. Потрібно виявити, де з’являється затримка, а потім виправити ту частину, яка «бреше».

Що насправді означає «повільне копіювання» (і чого це не стосується)

«Windows копіювання повільне» — це не одна проблема. Це видима користувачу ознака для конвеєра, який включає: поведінку клієнта Windows, семантику протоколу SMB, деталі реалізації Samba, групи транзакцій ZFS та шляхи запису, і фізичну латентність носіїв. Ваше завдання — знайти етап, де пропускна здатність перетворюється на очікування.

Три шаблони копіювання, які треба розрізняти

  • Великі послідовні копії (наприклад, ISO, VHDX): мають йти ближче до лінійної швидкості, поки сервер може комітити записи достатньо швидко.
  • Багато дрібних файлів (наприклад, дерева вихідного коду): домінують операції з метаданими (створення, setattr, close, rename), а не пропускна здатність.
  • Змішані навантаження (домашні шари + ВМ + сканери): «повільно» часто означає head-of-line blocking — один поганий шаблон псує чергу для всіх.

Чого це зазвичай не стосується

Зазвичай це не «SMB повільний». SMB3 може бути дуже швидким. Зазвичай це не «ZFS повільний». ZFS може наситити серйозні мережі. Зазвичай це спайки затримки від sync-записів, дрібних випадкових I/O, посилення метаданих або поганого вирівнювання кешування, які проявляються в клієнті, що показує оптимістичні сплески швидкості.

Ще одне переналаштування: Windows Explorer — не бенчмарк, а візуалізатор тривоги. Той графік — швидше емоційний індикатор, ніж осцилограф.

Цікаві факти та історичний контекст (щоб поведінка стала зрозумілішою)

  1. SMB1 vs SMB2/3 змінило все. SMB2 (ера Vista/2008) зменшила балакучість, збільшила розміри читань/записів і додала конвеєрування. Багато історій «SMB повільний» насправді — «ви застрягли на SMB1».
  2. Samba починалася як проект з реверс-інженерії. Вона виросла з «змушує UNIX говорити з Windows» до рівня enterprise SMB-сервера. Деякі значення за замовчуванням консервативні, бо Samba має витримувати дивні клієнти.
  3. ZFS групує записи. ZFS комітить дані в transaction group (TXG). Це робить пропускну здатність чудовою, але створює помітний «пульс», якщо фаза коміту затягується.
  4. Sync-записи — це зобов’язання, а не відчуття. Коли SMB-клієнт просить забезпечити надійність, ZFS має безпечно закомітити. Якщо ваш пул не вміє робити low-latency fsync, ви отримаєте класичний графік «швидко, потім нуль».
  5. SMB durable handles і leases змінили поведінку close/open. Сучасний Windows агресивно кешує. Це добре, поки додаток не вимагає семантики durable і не перетворює кешування на синхронний біль.
  6. Recordsize важливіший для файлових шарів, ніж багато кому здається. recordsize ZFS формує I/O-амліфікацію. Неправильний recordsize не лише марнує місце — він додає зайві IOPS при дрібному випадковому доступі.
  7. Стиснення часто допомагає SMB, навіть на швидких CPU. Багато офісних файлів добре стиснюються, знижуючи навантаження на диск і мережу. Перемога часто в латентності, а не в пропускній здатності.
  8. SMB signing став поширенішим з міркувань безпеки. Увімкнення підпису — це податок на CPU. Налаштування «secure» інколи перетворюється на «securely slow», коли серверний CPU слабкий або обмежений одним потоком.

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

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

По-перше: класифікуйте навантаження

  • Один великий файл? Багато дрібних файлів? Додаток пише з вимогами до збереження (durability)?
  • Швидкість падає через фіксовані інтервали (кожні кілька секунд)? Це пахне TXG commit latency.
  • Трапляється лише на певних шарах (shares)? Це пахне властивостями dataset або опціями SMB share.

По-друге: вирішіть, чи це мережа, CPU чи латентність зберігання

  • Мережа: помилки інтерфейсу, повторні передачі, неправильний MTU, погане хешування LACP, Wi‑Fi клієнти, які поводяться як сервери.
  • CPU: один ядро завантажене в smbd, наклад від signing/encryption, переривання, насичення softirq.
  • Шлях зберігання: високий await на vdev, блокування шляху ZFS sync, відсутній/повільний SLOG, пул майже заповнений або фрагментований.

По-третє: перевірте поведінку sync (зазвичай це винуватець)

  • Перевірте властивість dataset sync та налаштування Samba, які примушують sync (наприклад, strict sync).
  • Виміряйте fsync-латентність з самого хоста SMB, а не з вашого ноутбука.
  • Якщо потрібна семантика sync, забезпечте правильний SLOG-пристрій (з захистом від втрати живлення) або прийміть обмеження продуктивності ваших основних vdev.

По-четверте: ізолюйте випадок «багато дрібних файлів»

  • Метадані — це навантаження. Перевірте atime, поведінку xattr та продуктивність дрібних блоків.
  • Переконайтеся, що розташування пулу відповідає очікуванням по IOPS для метаданих (компроміс між mirror та RAIDZ).

По-п’яте: налаштовуйте лише те, що показали вимірювання

Якщо ви не можете показати before/after у латентності I/O, завантаженні CPU або повторних передачах, ви не налаштовуєте — ви декоруєте.

Припиніть здогадки: вимірюйте, куди йде час

SMB-копії — це переговори між клієнтом, який буферизує, і сервером, який комітить. Explorer показує «швидкість» на основі того, як швидко дані приймаються в буфери, а не того, наскільки швидко вони надійно записані. Тоді як ZFS може швидко прийняти дані в ARC і «грязні» буфери, а потім призупинитись під час коміту TXG. Саме ця пауза викликає нуль на графіку.

Ваш план вимірювань має відповісти на три питання:

  1. Чи клієнт чекає на сервер (латентність), чи він просто не надсилає (обмеження клієнта)?
  2. Чи сервер чекає на скидання на диск (sync-путь), чи на CPU (signing/encryption), чи на мережу?
  3. Чи ZFS підсилює навантаження (несумісний recordsize, фрагментація, тиск на метадані)?

Інженерія надійності має просте правило: вимірюйте систему, яка у вас є, а не ту, якою ви хотіли б її бачити.

Парафраз (Gene Kim): «Покращення потоку означає знаходження та усунення обмеження». Ось у чому справа.

Реалії ZFS, які ускладнюють роботу з SMB

TXG і шаблон «швидко, потім нуль»

ZFS накопичує «брудні» дані в пам’яті і періодично комітить їх на диск як transaction group. Якщо фаза коміту затягується, система пригальмовує записувачів. Очима клієнта: швидкий сплеск, потім пауза. Повторюється. Це не «мережева нестабільність». Це збереження надійності зберігання.

Sync-записи: податок на надійність

Коли навантаження виконує синхронні записи (або сервер їх трактує як такі), ZFS має гарантувати, що дані на стабільному носії перед підтвердженням. На пулах без швидкого intent log (SLOG) sync-записи лягають на основні vdev. Якщо це RAIDZ на HDD, результат передбачуваний: біль з часовими відмітками.

Recordsize, ashift та I/O-ампліфікація

recordsize ZFS керує максимальною роздільною здатністю блоків для даних файлу. SMB-шерінги часто містять змішані розміри файлів; занадто великий recordsize не завжди шкодить послідовним читанням, але може нашкодити випадковим записам і частковим перезаписам. Занадто малий — збільшує накладні витрати на метадані і погіршує ефективність стиснення.

Метадані не «безкоштовні»

Копії дрібних файлів навантажують метадані: записи в директоріях, ACL, xattr, timestamps. ZFS може це обробити, але тільки якщо макет пулу і кешування розумні. Якщо ви побудували широкий RAIDZ для ємності і потім використовуєте його для метаданих важкого SMB-шару, ви фактично купили автобус і заявили його на мотогонку.

Заповненість пулу та фрагментація

Коли пул заповнюється, розміщення блоків ускладнюється, фрагментація зростає, і латентність зростає. Користувачі SMB відчують це як «все працювало минулого місяця». ZFS не забуває, як писати; йому просто виснажуються легкі місця для розміщення блоків.

Реалії SMB, які ускладнюють роботу з ZFS

Семантика копіювання Windows: буферизація, close і надійність

Windows може буферизувати записи й вимагати надійності лише при закритті файлу, залежно від прапорів додатка та конфігурації сервера. Деякі додатки (і деякі засоби безпеки) вимагають write-through семантики. Це миттєво перетворює навантаження з «переважно async» на «сильно sync».

Signing і шифрування: безпека має ціну CPU

SMB-підпис часто вимагається політиками. Може бути увімкнено шифрування для певних шарів. Обидва споживають CPU. Якщо сервер SMB має скромний CPU з швидким NIC, ви можете вдаритися в стелю, де мережа проста, а одне ядро працює на межі можливостей у криптоопераціях.

SMB3 Multichannel: чудово, коли працює, неважливо, коли ні

Multichannel може використовувати кілька NIC і черг RSS. При неправильному конфігу ви отримаєте єдиний TCP-потік, застряглий на одній черзі. Тоді хтось скаже «але у нас подвійний 10GbE», ніби сервер зобов’язаний зацікавитися.

Opportunistic locks, leases і антивірус

Кешування на клієнті (oplocks/leases) зменшує чати. Але засоби безпеки на кінцевих точках люблять відкривати файли, змушувати оновлювати атрибути і загалом ламати поведінку кешування. Це може перетворити копію «багато дрібних файлів» в шаленство системних викликів.

Жарт №1: Трасування проблем SMB схоже на офісну політику — кожен наполягає, що він вузьке місце, і всі вони якимось чином праві.

Практичні завдання: команди, виводи, рішення

Нижче — реальні завдання, які ви можете виконати на SMB/ZFS сервері. Кожне містить, що означає вивід і яке рішення потрібно прийняти далі. Приклади орієнтовані на Linux + Samba + OpenZFS, бо саме там більшість тикетів «Windows копіювання повільне» живе.

Завдання 1: Підтвердіть стан пулу (бо продуктивність часто — симптом помираючого диска)

cr0x@server:~$ sudo zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 04:12:19 with 0 errors on Tue Dec 10 03:20:01 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_A   ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_B   ONLINE       0     0     0

errors: No known data errors

Значення: «ONLINE» і чистий scrub означають, що ви не боретесь із прихованими ретраями чи навантаженням resilver. Якщо ви бачите DEGRADED, resilvering або checksum errors, припиніть тонке налаштування продуктивності і спочатку виправте апаратні проблеми.

Рішення: Якщо будь-який vdev показує помилки або триває resilver, заплануйте усунення та перевірте продуктивність після стабілізації.

Завдання 2: Перевірте заповненість пулу (майже повні пули повільні в передбачуваний спосіб)

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME   USED  AVAIL  REFER  MOUNTPOINT
tank   38.2T 2.1T   96K    /tank

Значення: ~95% заповнений (38.2T used, 2.1T avail) — це зона ризику для багатьох навантажень. Розміщення ускладнюється; фрагментація зростає.

Рішення: Якщо використання понад ~80–85% і продуктивність важлива, плануйте звільнення місця або розширення. Жоден параметр Samba не переможе фізику.

Завдання 3: Визначте dataset, який використовує SMB share, і виведіть ключові властивості

cr0x@server:~$ sudo zfs get -H -o property,value recordsize,compression,atime,sync,xattr,acltype,primarycache,logbias tank/shares/engineering
recordsize	1M
compression	lz4
atime	on
sync	standard
xattr	sa
acltype	posixacl
primarycache	all
logbias	latency

Значення: У вас recordsize 1M (добре для великих послідовних файлів, ризиковано для часткових перезаписів), atime увімкнено (додаткові записи метаданих), sync standard (sync шанується), xattr у SA (часто добре), logbias latency (віддає перевагу SLOG якщо є).

Рішення: Якщо шар — «багато дрібних файлів», розгляньте recordsize=128K і atime=off. Якщо це образи ВМ, інша стратегія потрібна (ймовірно не через SMB).

Завдання 4: Виміряйте I/O-латентність пулу під час копіювання (істина в iostat)

cr0x@server:~$ sudo zpool iostat -v tank 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        38.2T  2.1T     12   2400   3.1M   210M
  mirror-0                  38.2T  2.1T     12   2400   3.1M   210M
    ata-SAMSUNG_SSD_1TB_A      -      -      6   1250   1.6M   108M
    ata-SAMSUNG_SSD_1TB_B      -      -      6   1150   1.5M   102M
--------------------------  -----  -----  -----  -----  -----  -----

Значення: Висока кількість операцій запису (2400/s) з помірною пропускною спроможністю вказує на дрібні записи або sync-важке навантаження. Якщо пропускна здатність низька, але ops високі, ви обмежені IOPS або flush-путтям.

Рішення: Якщо записи дрібні й часті, дослідіть синхронні семантики, навантаження на метадані та невідповідність recordsize. Якщо ops низькі і пропускна здатність низька — підозрюйте мережу або обмеження SMB.

Завдання 5: Спостерігайте латентність по vdev через iostat (await — як сигнал тривоги)

cr0x@server:~$ sudo iostat -x 1 3
Linux 6.6.15 (server) 	12/25/2025 	_x86_64_	(16 CPU)

Device            r/s     w/s   rkB/s   wkB/s  avgrq-sz avgqu-sz   await  r_await  w_await  svctm  %util
nvme0n1           2.0   950.0    64.0 118000.0   248.0     3.20    3.4     1.2      3.4    0.6   58.0
nvme1n1           1.0   910.0    32.0 112000.0   246.0     3.05    3.3     1.1      3.3    0.6   55.0

Значення: ~3.3ms write await — це нормально для NVMe. Якщо під час копій ви бачите десятки/сотні ms, зберігання обмежує вашу пропускну здатність.

Рішення: Високий await + низький CPU + чиста мережа = проблема шляху зберігання (sync-записи, заповнений пул, повільні vdev або проблеми з SLOG).

Завдання 6: Перевірте, чи взагалі є у вас SLOG (і чи він щось робить)

cr0x@server:~$ sudo zpool status tank | sed -n '1,120p'
  pool: tank
 state: ONLINE
config:

        NAME                         STATE     READ WRITE CKSUM
        tank                         ONLINE       0     0     0
          mirror-0                   ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_A    ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_B    ONLINE       0     0     0
        logs
          nvme-SLOG_INTEL_OPTANE     ONLINE       0     0     0

Значення: Є окремий лог-пристрій. Добре. Але наявність не означає продуктивність; пристрій має бути швидким і захищеним від втрати живлення.

Рішення: Якщо існують sync-важкі навантаження і SLOG відсутній, вирішіть, чи потрібна вам семантика sync. Якщо так — додайте відповідний SLOG. Якщо ні — не підмінюйте це sync=disabled, якщо ви не готові втрачати підтверджені дані при відключенні живлення.

Завдання 7: Спостерігайте TXG-тротлінг і поведінку «брудних» даних

cr0x@server:~$ sudo arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c  avail
12:10:01   320    12      4     1    0    11    3     0    0   84.2G  96.0G  21.4G
12:10:02   410    16      3     2    0    14    3     0    0   84.2G  96.0G  21.4G
12:10:03   390    10      2     1    0     9    2     0    0   84.2G  96.0G  21.4G

Значення: ARC не має суттєвих промахів; кешування не є вашим поточним обмеженням. Якщо ARC малий відносно пам’яті або промахи стрімко зростають, можливо ви обмежені пам’яттю або маєте неправильні налаштування.

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

Завдання 8: Підтвердіть версію Samba і чи погоджується SMB3

cr0x@server:~$ smbd -V
Version 4.19.5-Debian

cr0x@server:~$ sudo smbstatus -b | sed -n '1,60p'
Samba version 4.19.5-Debian
PID     Username     Group        Machine                                   Protocol Version  Encryption           Signing
-------------------------------------------------------------------------------------------------------------------------
23144   user1        domain users  10.10.20.55 (ipv4:10.10.20.55:53122)     SMB3_11           -                    partial

Значення: SMB3_11 погоджено, це добре. Signing показано «partial» (залежить від конфігурації і клієнта). Якщо ви бачите SMB1 — у вас проблеми більші, ніж налаштування.

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

Завдання 9: Перевірте конфіг Samba на наявність «вбивць» sync (strict sync, sync always)

cr0x@server:~$ sudo testparm -sv | sed -n '/^\[engineering\]/,/^\[/{p}'
[engineering]
	path = /tank/shares/engineering
	read only = No
	vfs objects = acl_xattr
	strict sync = Yes
	sync always = No

Значення: strict sync = Yes змушує Samba виконувати більше flush-ів. Це класичне налаштування «ми ввімкнули його для безпеки», яке може знищити пропускну здатність.

Рішення: Якщо у вас немає суворої вимоги відповідності, виставте strict sync = No і перевірте коректність додатків. Якщо потреба є — інвестуйте в SLOG і низьколатентне зберігання.

Завдання 10: Перевірте, чи увімкнено SMB signing/encryption і чи є CPU-обмеження

cr0x@server:~$ sudo smbstatus -b | awk 'NR==1 || NR==2 || $0 ~ /SMB3/'
Samba version 4.19.5-Debian
PID     Username     Group        Machine                                   Protocol Version  Encryption           Signing
23144   user1        domain users  10.10.20.55 (ipv4:10.10.20.55:53122)     SMB3_11           AES-128-GCM          mandatory

cr0x@server:~$ top -b -n 1 | sed -n '1,20p'
top - 12:12:41 up 34 days,  3:01,  2 users,  load average: 9.12, 8.40, 7.95
Tasks: 291 total,   2 running, 289 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12.1 us,  2.0 sy,  0.0 ni, 78.0 id,  0.0 wa,  0.0 hi,  7.9 si,  0.0 st
MiB Mem :  256000.0 total,  21000.0 free,  95000.0 used, 140000.0 buff/cache

Значення: Шифрування увімкнено. CPU здебільшого простає, тож шифрування, ймовірно, зараз не є вузьким місцем. Якщо ви бачите завантаження ядра або високі softirq — потрібно перевірити.

Рішення: Якщо encryption/signing обов’язкові і CPU гарячий — оновіть CPU, використовуйте системи з AES-NI, налаштуйте RSS і multiqueue, або звузьте шифрування до конкретних чутливих шарів.

Завдання 11: Перевірте лінк NIC, дуплекс та лічильники помилок (дешеві перевірки, дорогі наслідки)

cr0x@server:~$ ip -s link show dev bond0
2: bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 9c:dc:71:aa:bb:cc brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
    1223344556677 1023344556      0       0       0  120034
    TX:  bytes packets errors dropped carrier collsns
    1334455667788 1124455667      0       0       0       0

cr0x@server:~$ ethtool bond0 | sed -n '1,25p'
Settings for bond0:
	Supported ports: [ ]
	Supported link modes:   Not reported
	Speed: 20000Mb/s
	Duplex: Full
	Auto-negotiation: off

Значення: Немає помилок, повний дуплекс, очікувана швидкість. Якщо ви бачите помилки або дропи — виправляйте мережу перш ніж чіпати ZFS.

Рішення: Якщо є помилки: перевірте кабелі, порти комутатора, узгодженість MTU та налаштування offload. Налаштування продуктивності поверх втрат пакетів — це театр.

Завдання 12: Перевірте повторні передачі TCP та тиск на сокети (SMB над хворою мережею — ілюзія)

cr0x@server:~$ ss -s
Total: 884
TCP:   211 (estab 104, closed 72, orphaned 0, timewait 72)

Transport Total     IP        IPv6
RAW	  0         0         0
UDP	  11        8         3
TCP	  139       113       26
INET	  150       121       29
FRAG	  0         0         0

cr0x@server:~$ netstat -s | sed -n '1,80p'
Tcp:
    154239 active connection openings
    149802 passive connection openings
    1124 failed connection attempts
    1821 connection resets received
    0 connections established
    224159 segments received
    231008 segments sent out
    214 segments retransmitted

Значення: Повторні передачі є, але не у величезній кількості. Якщо під час копій retransmits зростають, ви побачите паузи, що не пов’язані зі зберіганням. SMB чутливий до стрибків латентності.

Рішення: Високі retransmits: перевірте буфери комутатора, невідповідність MTU, драйвер/прошивку NIC або перевантажений шлях через фаєрвол.

Завдання 13: Визначте, чи навантаження sync-важке (серверний тест fsync)

cr0x@server:~$ sync; sudo bash -c 'time dd if=/dev/zero of=/tank/shares/engineering/.fsync-test bs=1M count=256 conv=fdatasync status=none'
real	0m1.92s
user	0m0.00s
sys	0m0.28s

cr0x@server:~$ sudo rm -f /tank/shares/engineering/.fsync-test

Значення: Це вимірює «запис, а потім примусове забезпечення надійності». Якщо це повільно (наприклад, 10–60s), ваш пул не може швидко комітити sync-записи для SMB-навантажень, які їх вимагають.

Рішення: Повільний fsync: додайте/перевірте SLOG, зменшіть кількість примусових sync у Samba, якщо це прийнятно, або переробіть зберігання для низьколатентних записів.

Завдання 14: Переконайтеся, що dataset випадково не змушує sync вимкненим (або увімкненим) там, де ви не планували

cr0x@server:~$ sudo zfs get -H -o name,property,value sync tank/shares/engineering tank/shares/finance
tank/shares/engineering	sync	standard
tank/shares/finance	sync	always

Значення: Finance share має примусово встановлене sync=always. Це може бути свідомо (додатки потребують надійності) або помилкова конфігурація, що робить його повільним.

Рішення: Якщо знайдено sync=always, уточніть у власників додатків причини. Якщо ніхто не може обґрунтувати — поверніть на standard і протестуйте.

Завдання 15: Перевірте стиснення ZFS і реальне співвідношення (бо «ми увімкнули стиснення» ≠ «воно працює»)

cr0x@server:~$ zfs get -H -o name,property,value compression,compressratio tank/shares/engineering
tank/shares/engineering	compression	lz4
tank/shares/engineering	compressratio	1.62x

Значення: 1.62x означає, що ви економите I/O і місце. Якщо ratio ~1.00x, стиснення мало допомагає, але звичайно LZ4 не шкодить.

Рішення: Тримайте LZ4 за замовчуванням. Відключайте лише якщо виміряли насичення CPU і дані майже не піддаються стисненню.

Завдання 16: Пошукайте патологічну фрагментацію (особливо якщо пул старий і майже повний)

cr0x@server:~$ sudo zdb -bbbs tank | sed -n '1,40p'
Block Size Histogram:
 512: 0
 1K : 0
 2K : 1048576
 4K : 2097152
 8K : 1048576
 16K: 524288
 32K: 262144
 64K: 131072
 128K: 65536
 256K: 32768
 512K: 16384
 1M : 8192

Значення: Це грубий огляд; на практиці ви звʼяжете фрагментацію з поведінкою розміщення і латентністю. Велике різноманіття дрібних блоків у dataset, призначеному для великих послідовних записів, може бути підказкою.

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

Три міні-історії з корпоративної практики

Міні-історія 1: Інцидент через хибне припущення

У них був новий ZFS-файловий сервер і два uplink по 10GbE. Розгортання виглядало добре перший тиждень, переважно тому, що тест був «скопіювати 20GB ISO один раз», і всі пішли додому задоволені.

Потім настав кінець кварталу. Фінанси завантажили тисячі дрібних PDF і таблиць у шар з Windows-додатку, який вимагав write-through. Користувачі повідомили, що копії «зависають кожні кілька секунд». Мережа не бачила насичення, тому її визнали вільною від вини. Команда зберігання бачила багато вільної RAM і гадала, що ARC все вирівняє. Але ні.

Хибне припущення було тонким: «Якщо пул може робити 1GB/s послідовних записів, він може робити офісні копії». Це різні види навантаження. Послідовна пропускна спроможність — це парадний заїзд; sync-важкі метадані — це смуга перешкод.

Коли хтось запустив простий тест dd ... conv=fdatasync на dataset, стало очевидно. Латентність коміту sync була вузьким місцем. Пул був RAIDZ на HDD. Чудово для ємності, жахливо для низьколатентних записів.

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

Міні-історія 2: Оптимізація, що відкотилася

Інша компанія мала повільні копії домашніх директорій. Хтось прочитав форум і вирішив «більший recordsize — значить швидше». Тому вони виставили recordsize=1M для всіх SMB dataset, включно з домашніми та проектними деревами.

Великі файли трохи прискорилися. Потім скарги стали дивнішими: збереження дрібних документів загальмувало, доступ до Outlook PST став ривкоподібним, і деякі додатки почали «не відповідати» при збереженні. Сервер SMB не впав; він просто виконував зайву роботу.

Чому? Часткові перезаписи великих блоків можуть створити write amplification. Невелика зміна в файлі може викликати read-modify-write великого блоку, особливо коли навантаження випадкове і додаток робить багато дрібних оновлень. ZFS — copy-on-write, тож і так веде детальний облік; додаткова ампліфікація — це як просити його жонглювати на біговій доріжці.

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

Відкат був дисциплінований: вони розділили dataset за робочим навантаженням. Домашні директорії отримали 128K recordsize, atime off. Великі архіви залишилися на 1M. Продуктивність стабілізувалась. Урок закарбувався: налаштування — не шведський стіл, де можна все навантажити.

Міні-історія 3: Нудна, але правильна практика, що врятувала ситуацію

Команда, що керувала ZFS + Samba кластером, мала одну непримітну звичку: щотижневі звіти scrub і щомісячні базові знімки продуктивності. Не панелі для керівництва. Просто текстовий файл з zpool status, zpool iostat під навантаженням і базові лічильники помилок NIC.

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

Оскільки були історичні дані, вони не витратили півдня на суперечки про прапори Samba. Вони замінили диск у вікні технічного обслуговування, зробили resilver, і затримки копій зникли.

Нічого героїчного. Жодних магічних налаштувань. Просто помічати, що «регрес продуктивності» часто означає «апарат старіє повільно». Ось як виглядає нудна компетентність у продакшені.

Рішення з налаштування, які справді дають ефект

1) Визначте свою позицію щодо sync явно (не дозволяйте цьому статися випадково)

SMB-навантаження можуть бути sync-важкими, особливо з певними додатками і політиками. У вас є три варіанти:

  • Шанувати sync і платити за це: тримайте sync=standard, уникайте налаштувань Samba, які примушують додаткові flush, і розгорніть реальний SLOG, якщо потрібен.
  • Примусово sync завжди: sync=always для шарів з вимогами відповідності. Очікуйте нижчу пропускну спроможність; проектуйте зберігання відповідно.
  • Вимкнути sync: sync=disabled — бізнес-рішення, щоб ризикувати втратою підтверджених записів при відключенні живлення або збої. Це може бути прийнятно для тимчасових шарів, але не видавайте це за «безкоштовну продуктивність». Це інша угода щодо надійності.

2) Розділіть dataset за навантаженням (один шар — одна поведінка)

Один dataset для всього — найшвидший спосіб переконатися, що нічого не буде добре. Розділіть:

  • Домашні директорії (метадані-важкі, дрібні файли)
  • Інженерні проектні дерева (багато дрібних файлів, переважно для читання)
  • Медіа-архіви (великі послідовні файли)
  • Зони доставки додатків (можуть вимагати суворої надійності)

Потім задайте властивості на dataset: recordsize, atime, sync, стиснення, поведінка ACL.

3) Підібрати recordsize «достатньо правильно»

  • Загальні SMB-шари: почніть з recordsize=128K.
  • Архіви великих файлів: розгляньте recordsize=1M, якщо більшість файлів великі і послідовні.
  • Бази даних/образи ВМ поверх SMB: уникайте цього, якщо можете; якщо ні — використовуйте спеціальні налаштування і ретельно тестуйте. SMB-файлообслуговування і datastore для ВМ — не випадкове поєднання.

4) Вимкніть atime для SMB-шарів (якщо немає реальної потреби)

atime=on додає записи метаданих при читанні. Більшість організацій не використовують час доступу для чогось суттєвого, і Windows точно не потребує, щоб ваш ZFS-сервер писав додаткові метадані щоразу при відкритті файлу.

5) Тримайте LZ4 стиснення за замовчуванням

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

6) Використовуйте реальний SLOG, коли він потрібен (і не економте)

SLOG-пристрій — це не «будь-який SSD». Він має мати низьку латентність під sync-навантаженням і захист від втрати живлення. Інакше ви побудували дорогий генератор латентності.

7) Samba: уникайте «strict sync», якщо не можете його обґрунтувати

strict sync може знищити пропускну спроможність для навантажень, що генерують багато fsync-пунктів (включно з певною поведінкою Windows при закритті файлів). Якщо вам потрібна сувора семантика — зробіть зберігання здатним її підтримувати. Якщо ні — не платіть за це.

8) SMB signing/encryption: сфокусуйте застосування

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

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

Типові помилки: симптом → корінь проблеми → фікс

1) Симптом: Копія починається швидко, потім падає до 0 B/s повторно

Корінь проблеми: TXG commit stalls через sync-записи або повільну латентність flush (немає SLOG, повільні vdev, пул занадто заповнений).

Виправлення: Виміряйте fsync (dd ... conv=fdatasync), перевірте налаштування Samba щодо sync, додайте якісний SLOG або переробіть пул для низької латентності, звільніть місце.

2) Симптом: Великі файли копіюються нормально; багато дрібних файлів повзають

Корінь проблеми: Навантаження, що залежить від метаданих (ACL, xattr, timestamps) плюс обмеження дрібних випадкових I/O.

Виправлення: atime=off, переконайтеся у відповідних властивостях dataset, розгляньте зеркала для пулів з високими вимогами метаданих, перевірте, чи VFS-модулі Samba не додають накладних витрат; прийміть, що це IOPS, а не bandwidth.

3) Симптом: Швидкість обмежується приблизно ~110 MB/s на «10GbE»

Корінь проблеми: Клієнт/сервер погодили 1GbE, неправильне хешування LACP або один TCP-потік без multichannel.

Виправлення: Перевірте швидкість лінку через ethtool, перевірте конфіг switch, протестуйте SMB multichannel і переконайтеся, що клієнт не знаходиться в сегменті 1GbE.

4) Симптом: Продуктивність гірша після увімкнення SMB signing або encryption

Корінь проблеми: CPU-обмеження в крипто/підписі, одномодульні «гарячі» місця, недостатні RSS-черги.

Виправлення: Виміряйте завантаження CPU по ядрах під час передачі, увімкніть multiqueue/RSS, оновіть CPU, звужуйте зону застосування signing/encryption або використайте обладнання з прискоренням.

5) Симптом: Копії час від часу зависають «саме на кілька секунд»

Корінь проблеми: Повторні передачі в мережі, bufferbloat або конгестія комутатора; іноді часи TXG збігаються з паузами.

Виправлення: Перегляньте retransmits (netstat -s), дропи інтерфейсу і лічильники комутатора. Якщо чисто — поверніться до перевірки латентності зберігання та sync.

6) Симптом: Один шар повільний; інший на тому ж сервері ок

Корінь проблеми: Різниця властивостей dataset (sync=always, дивний recordsize, atime on), відмінності в конфігурації Samba (strict sync, VFS-модулі) або квоти/резервування, що впливають на розміщення.

Виправлення: Порівняйте виходи zfs get і блоки testparm -sv для обох шарів. Нормалізуйте усвідомлено.

7) Симптом: «Windows каже, що потрібно 2 години», але сервер виглядає простою

Корінь проблеми: Сканування на стороні клієнта (антивірус, індексація), накладні витрати на дрібні файли або клієнт чекає на кожну операцію метаданих.

Виправлення: Відтворіть на чистому клієнті, протестуйте robocopy з опціями і перевірте метрики сервера під час операції. Не налаштовуйте сервери, щоб компенсувати проблемні кінцеві точки.

Контрольні списки / покроковий план

Покроково: виправлення «швидко, потім нуль» SMB-копій на ZFS

  1. Підтвердіть стан пулу: zpool status -v. Якщо degraded або помилки — зупиніться і виправте диски.
  2. Перевірте заповненість пулу: zfs list. Якщо >85% використано — плануйте очищення/розширення.
  3. Визначте dataset і властивості: zfs get recordsize,atime,sync,compression.
  4. Перегляньте конфіг Samba: testparm -sv на предмет strict sync, aio та VFS-модулів.
  5. Виміряйте sync-латентність: серверний dd ... conv=fdatasync. Якщо повільно — це головний підозрюваний.
  6. Перевірте наявність/продуктивність SLOG: zpool status для логів і впевніться, що клас пристрою відповідний.
  7. Спостерігайте латентність дисків під навантаженням: iostat -x і zpool iostat під час відтворення.
  8. Перевірте здоровʼя мережі: ip -s link, retransmits (netstat -s) і швидкість лінку (ethtool).
  9. Застосовуйте по одній зміні: наприклад, відключіть strict sync на тестовому шарі або додайте SLOG; потім повторіть ту саму передачу і порівняйте.
  10. Запишіть результат: зніміть латентність, пропускну здатність і чи зникли паузи. Памʼять тьмяніє; тикети — ні.

Базовий контрольний список (нудні речі, за які ви подякуєте собі)

  • Щотижневий scrub запланований; звіти scrub переглядаються.
  • Щомісячний знімок: zpool status, ключові властивості zfs get, ip -s link і повторюваний тест throughput + fsync.
  • Макет dataset документований за категорією навантаження.
  • Ясна політика щодо sync: які шари вимагають гарантій надійності.
  • Контроль змін для конфігурації Samba; без «однорядкових фіксів» у продакшені о 2-й ночі.

ЧаПи

1) Чому Windows Explorer показує швидку швидкість, а потім 0 B/s?

Explorer показує на основі буферизації та короткочасного прийому. ZFS і Samba можуть швидко прийняти дані, а потім призупинитися під час коміту sync-записів або TXG. Вимірюйте латентність на сервері.

2) Чи robocopy швидший за Explorer?

Іноді. Велика перевага в тому, що robocopy передбачуваніший і скриптуємий, та показує повторні спроби і поведінку по файлах. Він не вирішить синхронну латентність на сервері.

3) Чи варто ставити sync=disabled, щоб прискорити?

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

4) Чи потрібен мені SLOG для SMB?

Якщо ваше навантаження генерує багато sync-записів (або налаштування Samba примушують суворе флешування), хороший SLOG може кардинально змінити ситуацію. Якщо навантаження переважно async — SLOG мало допоможе.

5) Який recordsize слід використовувати для SMB-шарів?

Починайте з 128K для загального призначення. Використовуйте 1M для великих послідовних архівів. Уникайте глобальних змін; розділяйте dataset за навантаженням.

6) Чи ввімкнення LZ4 сповільнить роботу?

Зазвичай ні, і часто воно пришвидшує роботу, зменшуючи I/O. Якщо CPU вже завантажений (шифрування/підпис, важке навантаження), виміряйте перед прийняттям рішення.

7) Чи RAIDZ поганий для SMB?

Не «поганий», але RAIDZ менш дружній до дрібних випадкових записів і метаданих-важких навантажень, ніж mirror. Якщо ваш SMB-випадок — багато дрібних файлів і sync-поведінка, mirrored vdev часто виграють по латентності.

8) Чому один SMB-шар повільний, а інші — ок?

Різні властивості dataset або опції Samba. Шукайте sync=always, atime=on, дивний recordsize або strict sync лише на одному шарі.

9) Чи SMB Multichannel вирішує все?

Ні. Воно може підвищити пропускну здатність і стійкість, але не виправить латентність зберігання або sync-паузи. Також вимагає коректних NIC, драйверів і підтримки на клієнті.

10) Як зрозуміти, що проблема в CPU?

Під час передачі одне або кілька ядер будуть постійно завантажені, часто в smbd або ядровому networking/crypto. Тим часом диски і NIC не будуть насичені. Це ваш знак.

Наступні кроки на цей тиждень

Виконайте ці кроки у порядку. Кожен крок робить рішення очевиднішим, і жоден не вимагає віри.

  1. Виберіть один відтворюваний тест трансфер (один великий файл і одна папка «багато дрібних файлів») і тримайте його постійним.
  2. Запустіть швидкий план діагностики і збережіть виводи: zpool iostat, iostat -x, ip -s link, netstat -s, smbstatus.
  3. Доведіть або виключіть синхронну латентність за допомогою серверного тесту dd ... conv=fdatasync на dataset.
  4. Розділіть dataset за навантаженням, якщо ще не зроблено. Встановіть atime=off і розумні recordsize для кожної категорії.
  5. Виправте реальне вузьке місце: додайте якісний SLOG для sync-важких шарів, звільніть місце якщо пул занадто повний, або розвʼяжіть CPU/мережеві проблеми якщо саме вони — джерело.
  6. Напишіть односторінковий ранбуk з базовими командами і «нормальними» виводами. Майбутнє ви купить каву минулому вам.

Мета не в ідеальному графіку. Мета — передбачувана продуктивність в межах контракту на надійність, який ви справді хочете надавати. Коли ви свідомо обираєте цей контракт, ZFS і SMB перестають бути містичними і стають… просто вимогливими.

Docker: резервні копії, які ви ніколи не перевіряли — як правильно провести репетицію відновлення

У вас є резервні копії. У вас навіть зелена галочка в якомусь дашборді. Потім вузол помирає, на виклик запускають відновлення,
і раптом єдине, що ви відновлюєте — це ваша повага до закону Мерфі.

Docker полегшує деплой додатків. Він також полегшує забути, де насправді живуть дані: томи, bind mounts,
секрети, файли з оточенням, реєстри та кілька «тимчасових» директорій, які хтось колись захардкодив о 2 ранку.

Репетиція відновлення — це продукт, а не ритуал

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

Репетиція відновлення має одну задачу: перетворити припущення на вимірювання. Яке у вас RPO (скільки даних можна втратити)
та RTO (скільки часу можна бути недоступним)? Які частини повільні? Які частини крихкі? Які частини потребують конкретного
знання та кофеїну однієї людини?

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

Одна цитата, яку варто тримати на столі: Надія — не стратегія. (приписують генералу Ґордонові Р. Саллівану)

Що саме ви відновлюєте в Docker

Docker не «містить» стан. Він просто полегшує його загубити. Для репетицій відновлення розглядайте систему як шари:
стан хоста, стан контейнера, стан даних і стан деплойменту. Потім вирішіть, що саме ви обіцяєте відновити.

1) Стан даних

  • Іменовані томи (керовані Docker): зазвичай під /var/lib/docker/volumes.
  • Bind mounts: будь-де в файловій системі хоста; часто вони не входять у ту ж політику бекапів, що томи.
  • Зовнішнє сховище: NFS, iSCSI, Ceph, EBS, SAN LUNs, ZFS datasets, LVM тощо.
  • Бази даних: Postgres/MySQL/Redis/Elastic/тощо. Метод бекапу важить більше за те, де вона зберігається.

2) Стан деплойменту

  • Compose файли, файли оточення та overrides.
  • Секрети та механізм їх доставки (Swarm secrets, файли, SOPS, Vault шаблони тощо).
  • Теги образів: «latest» — це не план відновлення.
  • Доступ до реєстру: якщо ви не можете витягти образи, ви не зможете запустити сервіс.

3) Стан хоста

  • Конфігурація Docker Engine, драйвер зберігання, прапорці демона.
  • Ядро + деталі файлової системи: очікування overlay2, xfs ftype, SELinux/AppArmor.
  • Мережа: правила фаєрволу, DNS, маршрути, MTU.

4) Рuntime-стан (зазвичай не варто «відновлювати»)

Слой контейнерів і епhemeral runtime-файли можна відтворити. Якщо ви робите бекап усього Docker root каталогу
(/var/lib/docker) в надії воскресити контейнери байт у байт, ви підписуєтеся на тонкі злами.
Правильна ціль майже завжди — дані томів плюс конфіг деплойменту, а також чисте відтворення контейнерів.

Жарт №1: Якщо ваш план відновлення починається словами «Я думаю, дані на тому одному вузлі», вітаю — ви винайшли єдину точку несподіванки.

Факти й історичний контекст (щоб ви перестали повторювати помилки)

  • Факт 1: Рання епоха Docker з AUFS нормалізувала ідею, що контейнери одноразові; багато команд помилково зробили дані одноразовими також.
  • Факт 2: Перехід з AUFS на overlay2 був не лише про продуктивність — семантика відновлення й вимоги файлової системи змінилися (зокрема очікування XFS ftype=1).
  • Факт 3: Рух індустрії до «незмінної інфраструктури» зменшив потребу в відновленні хостів, але збільшив потребу відновлювати зовнішній стан (томи, об’єктні сховища, керовані БД).
  • Факт 4: Compose став типовим описом додатка для багатьох організацій, навіть коли операційна дисципліна (обертання секретів, фіксація версій, healthchecks) не поспішала за цим.
  • Факт 5: Багато інцидентів, які звинувачували «Docker», насправді — проблеми когерентності зберігання: неконсистентні файли, скопійовані під час активної роботи БД.
  • Факт 6: Ransomware змістив стратегію бекапів від «можемо відновити?» до «можемо відновити, не довіряючи тому, що атакувальник не зашифрував наші ключі?»
  • Факт 7: Реєстри образів стали критичною інфраструктурою; втрата приватного реєстру або його облікових даних може заблокувати відновлення, навіть якщо дані в безпеці.
  • Факт 8: Снапшоти файлової системи (LVM/ZFS) зробили швидкі бекапи простішими — але також надали надмірну впевненість, коли додатки не були готові до снапшотів.
  • Факт 9: Поява rootless-контейнерів змінила шляхи бекапу й модель прав; відновлення даних як root може тихо зламати rootless-рантайми пізніше.

Визначте масштаб репетиції: хост, застосунок або рівень даних

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

Масштаб A: Репетиція відновлення даних (найпоширеніша, найцінніша)

Ви відновлюєте дані томів/bind mounts і розгортаєте контейнери з відомих образів та конфігурації. Це правильний дефолт
для більшості продакшн-настроєнь Docker Compose.

Масштаб B: Репетиція відновлення застосунку (деплоймент + дані)

Ви відновлюєте точний стек застосунку: Compose файли, env/секрети, reverse proxy, сертифікати та дані. Це перевіряє
припущення «усе, що потрібно для роботи». Також виявляє хворобу «ми тримали ту конфігурацію на чийомусь ноуті».

Масштаб C: Відновлення хоста (рідко, але робіть хоча б щороку)

Ви вважаєте, що вузол загинув. Ви провізуєте свіжий хост і відновлюєте на нього. Тут ви дізнаєтеся про залежність від
старих ядер, відсутніх пакетів, кастомних iptables правил, дивних MTU-хаків та невідповідностей драйвера зберігання.

Плейбук швидкої діагностики (знайти вузьке місце швидко)

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

Перше: чи взагалі маєте ви доступ до потрібного?

  • Чи є у вас облікові дані репозиторію бекапів та ключі шифрування?
  • Чи може хост відновлення дістатися до об’єктного сховища / сервера бекапів / NAS?
  • Чи можна витягнути образи (або у вас є кеш для air-gapped середовища)?

Друге: чи бекап повний і внутрішньо консистентний?

  • Чи маєте ви всі очікувані шляхи томів/bind-mount для застосунку?
  • Чи збігаються контрольні суми? Чи можна перелічити й витягнути файли?
  • Для баз даних: чи маєте логічний бекап або тільки crash-consistent копію файлової системи?

Третє: куди йде час?

  • Пропускна здатність мережі (egress об’єктного сховища, обмеження VPN, тротлінг)?
  • Розпаковування й крипто (однопотокові інструменти відновлення)?
  • IOPS і «штурм» відновлення мільйонів дрібних файлів?

Четверте: чому застосунок не піднімається?

  • Права/власність/SELinux-лейбли на відновлених даних.
  • Дріфт конфігурації: env vars, секрети, змінені теги образів.
  • Несумісність схем: відновлення старих даних у нову версію застосунку.

Якщо пам’ятаєте лише одне: виміряйте швидкість передачі і перевірте ключі на ранньому етапі. Все інше — вторинне.

Побудуйте реалістичне середовище відновлення

Репетиція відновлення на тому ж хості, що й продукував бекапи, — це заспокійлива брехня. Вона має ті ж закешовані образи,
ті самі вже залогінені облікові дані й ті самі вручну підлаштовані правила фаєрволу. Мета — провалитися чесно.

Що означає «реалістично»

  • Свіжий хост: нова VM або bare metal, та сама сімейство ОС, ті ж мажорні версії.
  • Ті самі мережеві обмеження: той самий маршрут до сховища бекапів, той самий NAT/VPN, той самий DNS.
  • Немає прихованого стану: не використовуйте старий /var/lib/docker; не монтуйте продакшн-томи напряму.
  • Обмеження по часу: ви тестуєте RTO; перестаньте милуватися логами і почніть таймер.

Заздалегідь визначте критерії успіху

  • RPO верифіковано: ви можете вказати на найновіший успішний бекап і показати його мітку часу та вміст.
  • RTO виміряно: від “хост провізовано” до “сервіс відповідає коректно”.
  • Коректність підтверджена: не просто «контейнери запущені», а «дані правильні».

Практичні завдання: команди, виводи, рішення

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

Завдання 1: Інвентаризація запущених контейнерів та їхніх маунтів (джерельне середовище)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
NAMES               IMAGE                         STATUS
api                 registry.local/api:1.42.0     Up 3 days
postgres            postgres:15                   Up 3 days
nginx               nginx:1.25                    Up 3 days

Значення: Це мінімальний перелік «що існує». Цього замало, але це початок.
Рішення: Визначте, які контейнери мають стан (тут: postgres) і які безстанні.

cr0x@server:~$ docker inspect postgres --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume pgdata -> /var/lib/postgresql/data
bind /srv/postgres/conf -> /etc/postgresql

Значення: У вас є іменований том і bind mount. Дві політики бекапу, два режими відмови.
Рішення: План відновлення має захоплювати і pgdata, і /srv/postgres/conf.

Завдання 2: Перелік Docker томів і їхнє зіставлення з проектами

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     myapp_pgdata
local     myapp_redisdata
local     shared_uploads

Значення: Імена томів часто кодують імена проектів Compose. Це корисно під час відновлення.
Рішення: Визначте, які томи критичні, а які можна відновити повторно (наприклад, кеші).

Завдання 3: З’ясуйте, де томи живуть на диску (хост відновлення)

cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker

Значення: Директорія Docker root за замовчуванням. Томи будуть під цим шляхом, якщо не налаштовано інакше.
Рішення: Підтвердіть, що це співпадає з вашими очікуваннями бекапу; невідповідності призводять до «відновлення вдалося, дані відсутні».

Завдання 4: Перевірте файлову систему та вільне місце перед відновленням

cr0x@server:~$ df -hT /var/lib/docker /srv
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda2      ext4   200G   32G  158G  17% /
/dev/sdb1      xfs    800G  120G  680G  15% /srv

Значення: У вас є запас місця. Також зверніть увагу на типи файлових систем; деякі поведінки відрізняються для overlay і прав.
Рішення: Якщо простору замало, не «пробуйте все одно». Спочатку збільшіть або оберіть більший хост.

Завдання 5: Підтвердіть драйвер зберігання Docker і сумісність ядра

cr0x@server:~$ docker info --format 'Driver={{.Driver}}; BackingFS={{.BackingFilesystem}}'
Driver=overlay2; BackingFS=extfs

Значення: overlay2 на ext4 (Docker повідомляє «extfs»). Якщо оригінальний хост використовував інший драйвер, не припускайте портативності /var/lib/docker.
Рішення: Надавайте перевагу відновленню лише томів і конфігурації; перебудовуйте контейнери з образів.

Завдання 6: Переконайтеся, що артефакт бекапу існує і є свіжим

cr0x@server:~$ ls -lh /backups/myapp/
total 4.1G
-rw------- 1 root root 1.9G Jan  2 01:05 myapp-volumes-2026-01-02.tar.zst
-rw------- 1 root root 2.2G Jan  2 01:06 myapp-bindmounts-2026-01-02.tar.zst
-rw------- 1 root root  12K Jan  2 01:06 myapp-compose-2026-01-02.tgz

Значення: Окремі артефакти для томів, bind mounts і конфігурації деплойменту — це добре. Це робить часткові відновлення можливими.
Рішення: Якщо найновіший файл старший за ваш RPO, зупиніться й ескалюйте. Відновлення застарілих даних без повідомлення — шлях у кар’єру інцидентів.

Завдання 7: Перевірте цілісність архіву перед розпакуванням

cr0x@server:~$ zstd -t /backups/myapp/myapp-volumes-2026-01-02.tar.zst
/backups/myapp/myapp-volumes-2026-01-02.tar.zst: OK

Значення: Потік стиснення не пошкоджений.
Рішення: Якщо це завершується помилкою, не розпаковуйте «трохи». Знайдіть інший набір бекапів або відновіть pipeline бекапів.

Завдання 8: Сухий прогін — перелік файлів всередині бекапу (шукаємо відсутні шляхи)

cr0x@server:~$ tar -I zstd -tf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst | head
srv/postgres/conf/postgresql.conf
srv/postgres/conf/pg_hba.conf
srv/myapp/env/.env.production
srv/nginx/conf.d/app.conf

Значення: Ви бачите очікувані файли конфігів для bind-mount.
Рішення: Якщо ключові директорії відсутні, зупиніться й виправте визначення бекапу. Репетиції відновлення — не фокуси.

Завдання 9: Відновіть bind mounts спочатку в проміжну папку (щоб не перезаписувати)

cr0x@server:~$ mkdir -p /restore-staging
cr0x@server:~$ tar -I zstd -xpf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst -C /restore-staging
cr0x@server:~$ ls -la /restore-staging/srv/postgres/conf
total 24
drwxr-xr-x 2 root root 4096 Jan  2 01:06 .
drwxr-xr-x 3 root root 4096 Jan  2 01:06 ..
-rw-r--r-- 1 root root  980 Jan  2 01:06 pg_hba.conf
-rw-r--r-- 1 root root 3150 Jan  2 01:06 postgresql.conf

Значення: Файли відновлені збереженням прав (-p). Власність має значення пізніше.
Рішення: Порівняйте staging із цільовим макетом. Лише потім переміщуйте на потрібні місця.

Завдання 10: Відновіть дані іменованого тому за допомогою хелпер-контейнера

Для іменованих томів не копіюйте вручну всередину internals Docker. Використовуйте тимчасовий контейнер, який монтує том.

cr0x@server:~$ docker volume create myapp_pgdata
myapp_pgdata
cr0x@server:~$ docker run --rm -v myapp_pgdata:/data -v /backups/myapp:/backup alpine:3.20 sh -c "cd /data && tar -I zstd -xpf /backup/myapp-volumes-2026-01-02.tar.zst --strip-components=2 ./volumes/myapp_pgdata"
tar: removing leading './' from member names

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

Завдання 11: Санітарна перевірка вмісту та власності відновленого тому

cr0x@server:~$ docker run --rm -v myapp_pgdata:/data alpine:3.20 sh -c "ls -la /data | head"
total 128
drwx------    19 999      999           4096 Jan  2 01:04 .
drwxr-xr-x     1 root     root          4096 Jan  2 02:10 ..
-rw-------     1 999      999              3 Jan  2 01:04 PG_VERSION
drwx------     5 999      999           4096 Jan  2 01:04 base

Значення: Власність — 999:999, типовий для офіційного образу Postgres. Добре.
Рішення: Якщо власність неправильна (наприклад, root), виправте її зараз (chown), інакше Postgres може відмовитися стартувати.

Завдання 12: Відновіть конфіг деплойменту та закріпіть версії образів

cr0x@server:~$ mkdir -p /opt/myapp
cr0x@server:~$ tar -xpf /backups/myapp/myapp-compose-2026-01-02.tgz -C /opt/myapp
cr0x@server:~$ ls -la /opt/myapp
total 40
drwxr-xr-x 3 root root 4096 Jan  2 02:13 .
drwxr-xr-x 3 root root 4096 Jan  2 02:13 ..
-rw-r--r-- 1 root root 2241 Jan  2 01:06 docker-compose.yml
-rw------- 1 root root  412 Jan  2 01:06 .env.production

Значення: Конфіг присутній, включно з env файлом. Розглядайте його як чутливий.
Рішення: Переконайтеся, що образи зафіксовані тегами або дайджестами, яким ви довіряєте. Якщо Compose використовує latest, виправте це під час репетиції.

Завдання 13: Перевірте, чи можна витягти образи (або чи вони вже доступні)

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml pull
[+] Pulling 3/3
 ✔ postgres Pulled
 ✔ api      Pulled
 ✔ nginx    Pulled

Значення: Ваш шлях до реєстру, облікові дані та мережа функціонують.
Рішення: Якщо pull-операції не вдаються, план відновлення має включати зеркало реєстру, офлайн tar-образи або кроки відновлення облікових даних.

Завдання 14: Підніміть стек і слідкуйте за швидкими збоями

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml up -d
[+] Running 3/3
 ✔ Container myapp-postgres-1  Started
 ✔ Container myapp-api-1       Started
 ✔ Container myapp-nginx-1     Started

Значення: Контейнери запущені, але це не доказ коректності.
Рішення: Негайно перевірте логи й health endpoints. «Started» може означати «потихеньку зламано».

Завдання 15: Перевірте здоров’я та читайте перші помилки, а не останні

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml ps
NAME                IMAGE                         COMMAND                  SERVICE    STATUS          PORTS
myapp-api-1          registry.local/api:1.42.0     "gunicorn -c ..."        api        Up 20s (healthy) 0.0.0.0:8080->8080/tcp
myapp-nginx-1        nginx:1.25                    "/docker-entrypoint…"    nginx      Up 20s          0.0.0.0:80->80/tcp
myapp-postgres-1     postgres:15                   "docker-entrypoint…"     postgres   Up 20s (healthy) 5432/tcp

Значення: Health checks проходять. Це сильний сигнал, але не гарантія.
Рішення: Якщо health падає, перегляньте найперші релевантні логи (спочатку БД, потім застосунок, потім проксі).

cr0x@server:~$ docker logs --tail=50 myapp-postgres-1
2026-01-02 02:14:12.101 UTC [1] LOG:  database system is ready to accept connections

Значення: Postgres успішно піднявся. Якщо ви бачите «invalid checkpoint record» або «permission denied», відновлення некоректне.
Рішення: Для помилок БД визначте, чи потрібне логічне відновлення замість копії файлової системи.

Завдання 16: Доведіть коректність запитом на рівні застосунку

cr0x@server:~$ curl -fsS http://127.0.0.1:8080/health
{"status":"ok","db":"ok","version":"1.42.0"}

Значення: Ваш застосунок каже, що здоровий. Тепер перевірте дані, а не лише живучість.
Рішення: Виконайте відомий запит або бізнес-перевірку (наприклад, «конкретний запис клієнта існує»).

cr0x@server:~$ docker exec -i myapp-postgres-1 psql -U postgres -tAc "select now(), count(*) from users;"
2026-01-02 02:14:35.812396+00|1842

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

Завдання 17: Виміряйте пропускну здатність відновлення, щоб прогнозувати RTO

cr0x@server:~$ /usr/bin/time -f 'elapsed=%E cpu=%P' tar -I zstd -xpf /backups/myapp/myapp-volumes-2026-01-02.tar.zst -C /restore-staging-voltest
elapsed=0:02:41 cpu=380%

Значення: CPU — вузьке місце (380% означає ~4 ядра завантажені). Варто знати.
Рішення: Якщо відновлення зав’язло на CPU, ви можете зменшити RTO, додавши ядра, налаштувавши рівень стиснення або змінивши інструмент — до реальної аварії.

Завдання 18: Перевірте права файлів і контекст SELinux/AppArmor (за потреби)

cr0x@server:~$ getenforce
Enforcing

Значення: SELinux увімкнено. Bind mounts можуть потребувати лейблів, інакше контейнери отримають «permission denied» попри правильні UNIX-права.
Рішення: Якщо ви використовуєте SELinux, переконайтеся, що Compose використовує :z або :Z там, де потрібно, і перелейбуйте відновлені шляхи.

cr0x@server:~$ ls -lZ /srv/postgres/conf | head -n 3
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 pg_hba.conf
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 postgresql.conf

Значення: Тип SELinux за замовчуванням на файлах конфігів може не відповідати очікуванням контейнера.
Рішення: Якщо контейнери не можуть читати bind mounts під SELinux, потрібно перелейблити або налаштувати параметри маунту; не відключайте SELinux як «виправлення».

Завдання 19: Переконайтеся, що відновлення не замінило томи приховано

cr0x@server:~$ docker inspect myapp-postgres-1 --format '{{range .Mounts}}{{.Name}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
myapp_pgdata /var/lib/docker/volumes/myapp_pgdata/_data -> /var/lib/postgresql/data

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

Завдання 20: Зніміть доказ і часи (щоб репетиція покращувалася)

cr0x@server:~$ journalctl -u docker --since "today" | tail -n 5
Jan 02 02:13:55 server dockerd[1023]: API listen on /run/docker.sock
Jan 02 02:14:03 server dockerd[1023]: Loading containers: done.

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

Жарт №2: Репетиція відновлення — як чищення міжзубних проміжків: усі стверджують, що роблять це, а докази зазвичай кровоточать.

Три корпоративні міні-історії (як це ламається в реальному житті)

Міні-історія 1: Інцидент через неправильне припущення

Середня SaaS-компанія запускала Docker Compose на кількох потужних VM. Їхні бекапи були «простими»: нічний tar від
/srv плюс щотижневий снапшот диска VM. Припущення було таке, що все важливе живе в /srv.

Аварія почалася з банальної проблеми зі сховищем. VM не хотіла нормально завантажитися після інциденту хоста. Команда розгорнула
нову VM і відновила /srv з нічного бекапу. Compose піднявся. Nginx віддавав сторінки. API повертав 500.

Логи Postgres показали щойно ініційований порожній кластер бази даних. Ніхто не відновив БД — бо ніхто її не бекапив. DB використовувала
іменований Docker том, що сидів у Docker root під /var/lib/docker/volumes, поза областю бекапу. Щотижневий снапшот VM містив його,
але був занадто старий для неявного RPO компанії і керувався іншою командою.

Постмортем не був драматичним. Був гірше: очевидним. Вони сплутали «директорію даних нашого застосунку» з «де Docker зберігає стан».
Виправлення було теж не хитромудрим: проінвентаризувати маунти, явно бекапити іменовані томи і запускати квартальну репетицію відновлення
на чистому хості. І ще: перестати називати це «простими бекапами», якщо вони не включають вашу базу.

Міні-історія 2: Оптимізація, яка відкинула назад

Інша організація серйозно взялася за швидкість. Час відновлення вищий за допустимий, тож вони оптимізували.
Перейшли від логічних дампів БД до crash-consistent снапшотів файлової системи тома БД. Це було швидше й давало менші інкрементальні передавання.
Всі аплодували.

Через шість місяців знадобилося відновлення. Поганий деплой пошкодив стан застосунку, і вони відкотилися. Відновлення «працювало»
механічно: снапшот розпакували, контейнери стартували, healthchecks стали зеленими. Потім трафік підскочив, і БД почала кидати
помилки: тонкі пошкодження індексів, дивна поведінка планувальника запитів, потім crash loop.

Корінна причина була нудною, але смертельною: снапшот зробили під навантаженням, без координування контрольної точки або DB-native механізму бекапу.
Бекап тома був консистентним на рівні файлової системи, але не на рівні бази даних. Відновився швидко і зламався пізно — саме той тип помилки, що краде час.

Виправлення було компромісом: зберегти швидкі снапшоти для короткострокових «упс» відновлень, але також робити періодичні DB-native бекапи
(або використовувати підтримувані процедури бази) і валідовувати їх. Додали job, який стартує відновлену БД у сандбоксі й проганяє перевірки цілісності.
Оптимізації дозволені. Не верифіковані оптимізації — це просто ризик у формі продуктивності.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Компанія, близька до фінансів, тримала кілька клієнтських сервісів у Docker. Їхній SRE-лід не був романтиком. Кожного кварталу вони проводили
репетицію відновлення в ізольованому VPC, зі свіжим образом VM і копією репозиторію бекапів. Репетиція мала чекліст і секундомір.

Репетиція завжди включала ті самі нудні кроки: перевірка доступності ключів шифрування для on-call, верифікація маніфестів бекапів, відновлення томів
у staging, потім перемикання на місце, виконання кількох прикладів бізнес-перевірок. Нарешті — документування часу й оновлення руйнбука.
Ніхто не любив це. Ніхто не підносив це в слайдах.

Потім стався реальний інцидент: оператор випадково видалив продакшн-том і репліка швидко підхопила проблему. On-call слідував руйнбуку без імпровізацій.
Вони вже знали, що найдовший крок — розпаковування, і налаштували розмір хоста під це. Вони знали, які саме секрети потрібні і де вони живуть.
Вони вже вирішили питання з SELinux — в репетиції, а не під час аварії.

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

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

План репетиції відновлення (повторювано, а не «подивимось, що станеться»)

  1. Оголосіть масштаб і критерії успіху.

    • Які сервіси? Які набори даних? Які RPO/RTO ви верифікуєте?
    • Що означає «коректно» (запити, контрольні суми, дії в UI, кількість повідомлень)?
  2. Заморозьте інвентар.

    • Експортуйте Compose файли та посилання на env/секрети.
    • Перелічіть томи й bind mounts по контейнерах.
    • Запишіть посилання на образи (теги або дайджести).
  3. Провізуйте свіжий хост для відновлення.

    • Те саме сімейство ОС, схожі CPU/пам’ять, ті ж вибори файлової системи.
    • Той самий мережевий шлях до бекапів і реєстрів (або явно інший, якщо тестуєте DR-регіон).
  4. Завантажте артефакти бекапу і верифікуйте цілісність.

    • Контрольні суми, дешифрування, перелік вмісту, перевірка міток часу.
    • Підтвердіть, що у вас є ключі та паролі в моделі доступу, якої ви очікуєте під час інциденту.
  5. Відновлюйте спочатку в staging.

    • Bind mounts у /restore-staging.
    • Томи через допоміжні контейнери у щойно створені томи.
  6. Застосуйте права, лейбли й власність.

    • Томи БД мають відповідати очікуванням UID/GID контейнера.
    • SELinux/AppArmor: забезпечте правильні лейбли та параметри маунту.
  7. Підніміть стек з зафіксованими перевіреними образами.

    • Витягніть образи; якщо pull не вдається — використайте кеш/офлайн-образи.
    • Запускайте БД першою, потім застосунок, потім edge-проксі.
  8. Перевірте коректність.

    • Health endpoint + щонайменше один запит даних для кожного критичного сервісу.
    • Для черг/кешів: перевірте, чи потрібно їх відновлювати (зазвичай ні).
  9. Виміряйте часи і напишіть звіт репетиції.

    • Початок/кінець відновлення, пропускна здатність, вузькі місця, збої, виправлення.
    • Оновіть руйнбук і автоматизуйте крихкі кроки.

Що автоматизувати після першої чесної репетиції

  • Експорт інвентарю: маунти, томи, образи, Compose конфіги.
  • Генерацію маніфесту бекапу: очікувані шляхи та томи, розміри, мітки часу.
  • Перевірки цілісності: контрольні суми, тести архівів, періодичне відновлення в сандбокс.
  • Нормалізацію прав: відомі відповідності UID/GID для сервісів.
  • Збереження образів: тримайте потрібні образи для вашого RPO-вікна (або експортуйте tar).

Поширені помилки: симптом → корінна причина → виправлення

1) «Контейнери запущені, але застосунок порожній»

Симптом: Healthchecks проходять, але користувацькі дані відсутні або скинуті до дефолту.
Корінна причина: Відновлено в неправильний няв том або Compose створив новий порожній том через невідповідність імен проектів.
Виправлення: Перевірте маунти (docker inspect), переконайтеся, що імена томів співпадають, і явно вкажіть імена томів у Compose замість покладання на неявний scoping проекту.

2) «Permission denied» на відновлених bind mounts

Симптом: Контейнери падають з помилками доступу до файлів; файли на хості виглядають нормально.
Корінна причина: Неправильні SELinux-лейбли або rootless-контейнер очікує іншої власності, ніж дала відновлення.
Виправлення: Використовуйте опції маунту :z/:Z

3) Postgres/MySQL стартує, а потім поводиться дивно під навантаженням

Симптом: БД піднімається, але потім з'являються помилки, схожі на корупцію, або вона падає.
Корінна причина: Crash-consistent файловий бекап зроблений без координування з БД; несумісний стан WAL/контрольної точки.
Виправлення: Віддавайте перевагу нативним методам бекапу бази для надійних відновлень; якщо використовуєте снапшоти, координуйте з режимом бекапу БД і валідуйте у сандбоксі.

4) Відновлення «повільне без причини»

Симптом: Години відновлення, CPU завантажений, диски недовантажені.
Корінна причина: Однопотокове розпаковування/шифрування або занадто сильне стиснення; мільйони дрібних файлів підсилюють операції з метаданими.
Виправлення: Заміряйте розпаковування, подумайте про нижчий рівень стиснення чи паралельні інструменти, реорганізуйте бекапи (наприклад, архіви по томах), щоб зменшити метадані.

5) Не вдається витягти образи під час відновлення

Симптом: Аутентифікація до реєстру падає, DNS не працює або образи зникли.
Корінна причина: Облікові дані зберігалися тільки на старому хості; реєстр позбувся тегів, на які ви покладалися; залежність від публічного реєстру й ліміти.
Виправлення: Зберігайте облікові дані реєстру в відновлюваному менеджері секретів, фіксуйте образи дайджестами або незмінними тегами, і майте офлайн-кеш/експорт критичних образів.

6) Compose «працює в проді», але падає на хості відновлення

Симптом: Той самий Compose файл, різна поведінка: порти, DNS, мережі, MTU проблеми.
Корінна причина: Прихований дріфт конфігурації хоста: sysctls, iptables, модулі ядра, кастомний daemon.json або cloud-специфічна мережа.
Виправлення: Кодизуйте провізію хостів (IaC), експортуйте та версіонуйте налаштування демона і включайте «чистий хост» репетицію щороку.

7) Бекап присутній, але ключів немає

Симптом: Ви бачите файл бекапу, але не можете його дешифрувати чи відкрити під час інциденту.
Корінна причина: Ключі шифрування/паролі закриті за людиною, мертвим ноутом або зламаним SSO шляхом.
Виправлення: Практикуйте відновлення ключів під час репетицій, зберігайте break-glass доступ правильно і верифікуйте процедуру з роллю on-call з мінімально необхідними правами.

8) Ви відновили конфіг, але не «нудні» залежності

Симптом: Застосунок стартує, але не може відправити пошту, не підключається до платіжного провайдера або колбеки падають.
Корінна причина: Відсутні TLS сертифікати, правила фаєрволу, DNS записи, секрети webhook або дозволи на вихідні з'єднання.
Виправлення: Розглядайте зовнішні залежності як частину «стану деплойменту» і тестуйте їх у репетиції (або явно стабітьте й задокументуйте).

FAQ

1) Чи слід бекапити /var/lib/docker?

Зазвичай ні. Бекапте томи і будь-які bind-mounted директорії застосунку, плюс Compose конфіг і посилання на секрети.
Бекап усього Docker root каталогу крихкий між версіями, драйверами зберігання та відмінностями хостів.

2) Який найнадійніший спосіб бекапу бази даних у Docker?

Використовуйте підтримуваний метод бекапу самої бази (логічні дампи, base backups, WAL-архівування тощо) і валідуйте відновлення в сандбоксі.
Резервні копії на рівні файлової системи можуть працювати при правильній координації, але «якось раз було нормально» — це не метод.

3) Як часто проводити репетиції відновлення?

Квартально для критичних систем — розумна відправна точка. Щомісяця, якщо система часто змінюється або якщо RTO/RPO жорсткі.
Також робіть репетицію після великих змін: міграція сховища, оновлення Docker, оновлення БД або зміна інструментів бекапу.

4) Чи можна провести репетицію без дублювання продакшн-даних (через приватність)?

Так: використовуйте масковані набори даних, синтетичні фікстури або відновлюйте в зашифроване ізольоване середовище з суворим доступом.
Але вам потрібно відновити реалістичну структуру: права, розміри, кількість файлів, схему і поведінку на рантаймі.

5) Що найбільше роздуває час відновлення?

Дрібні файли та дерева з великою кількістю метаданих, особливо в поєднанні з шифруванням і стисненням. Можна мати багато пропускної здатності,
але бути заблокованим CPU або IOPS.

6) Чи компресувати бекапи?

Зазвичай так, але обирайте стиснення, що відповідає вашим обмеженням відновлення. Якщо ви під завантаженням CPU під час відновлення,
сильне стиснення шкодить RTO. Заміряйте розпаковування під час репетицій і налаштуйте.

7) Як зрозуміти, чи відновлено потрібне?

Не довіряйте статусу контейнера. Використовуйте перевірки на рівні застосунку: виконайте SQL-запит, перевірте кількість записів, підтвердіть конкретного клієнта
або прогоніть read-only бізнес-транзакцію. Автоматизуйте ці перевірки в репетиції.

8) Чи потрібно відновлювати Redis та інші кеші?

Зазвичай ні — кеші відтворювані, і їх відновлення може занести поганий стан назад. Але потрібно переконатися, що застосунок витримає порожній кеш
і що конфігурація кешу (паролі, TLS, maxmemory) бекапиться.

9) А секрети в змінних оточення?

Якщо ваше продакшн-залежить від env-файлу, цей файл — частина стану деплойменту і має бути відновлюваним. Краще: мігруйте секрети в
менеджер секретів або еквівалент Docker secrets і включіть процедуру доступу break-glass у репетицію.

10) Чи можна робити це з Docker Compose і бути «enterprise-grade»?

Так, якщо ви трактуєте Compose як артефакт з версіонуванням, зафіксованими образами, протестованими відновленнями і дисципліною управління станом.
«Enterprise-grade» — це поведінка, а не вибір інструмента.

Висновок: наступні кроки, які ви можете зробити цього тижня

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

Потім зробіть ці кроки за порядком:

  1. Інвентаризуйте маунти для кожного stateful контейнера і запишіть авторитетні шляхи та імена томів.
  2. Розділіть артефакти на дані (томи), bind mounts і конфіг деплойменту, щоб відновлювати селективно.
  3. Верифікуйте цілісність найновішого набору бекапів і доведіть, що маєте ключі для дешифрування під правами on-call.
  4. Відновіть у сандбокс і виконайте перевірки коректності на рівні застосунку, а не лише «контейнер запущений».
  5. Виміряйте RTO, знайдіть найповільніший крок і виправте саме його, перш ніж оптимізувати щось інше.

Резервні копії, які ви ніколи не відновлювали, — це не бекапи. Це стиснений оптимізм. Проведіть репетицію, запишіть, що зламалося, і зробіть це нудним.

ZFS: ECC проти non-ECC — математика ризику для реальних розгортань

Якщо ви довго використовуєте ZFS, рано чи пізно постає таке неприємне питання: «Чи справді мені потрібна ECC RAM, чи це просто міф від людей, які люблять дорогі материнські плати?»
Чесна відповідь нудна й гостра: це залежить від вашого бюджету ризику, цінності даних і того, як ваш пул ZFS фактично використовується — а не від відчуттів, форумних догм або одного страшного скриншота.

ZFS прекрасно виявляє корупцію. Воно не є чарівництвом у запобіганні корупції, що відбувається до обчислення контрольної суми, або корупції, яка трапляється не в той час і не в тому місці.
Цей матеріал — це математика, режими відмов і операційний план — щоб ви могли прийняти рішення, яке зможете обґрунтувати під час післяінцидентного розбору.

Що змінює ECC (і що не змінює)

ECC (Error-Correcting Code) пам’ять не робить систему «швидшою» і не є талісманом. Це контроль: вона виявляє й коригує певні класи помилок у ОЗП (зазвичай одиночні біти) і виявляє (але може не коригувати) деякі багатобітні помилки.
Вона зменшує ймовірність того, що транзієнтний збій пам’яті перетвориться на постійний смітник, записаний на диск.

Non‑ECC не є «гарантованою корупцією». Це просто некерований ризик. Більшість систем працюють довгими періодами без видимих проблем.
Потім одного дня під час scrub, resilver, сильного ARC-чурну, оновлень метаданих або в умовах обмеженої пам’яті ви отримаєте помилку контрольної суми, яку не зможете пояснити — або, ще гірше, ви її не отримаєте, бо було обчислено контрольну суму від неправильних даних.

Практичне формулювання:

  • ECC зменшує невизначеність. Вам усе одно потрібні відмовостійкість, scrubs, резервні копії, моніторинг і перевірені процедури відновлення.
  • ECC цінніша там, де ZFS зазнає найбільшого навантаження. Робочі навантаження з великою кількістю метаданих, dedup, високим ARC-чурном, спеціальні vdev та великі пула, які скрабляться днями.
  • ECC не виправляє поганого планування. Якщо ваша єдина копія — в одному пулі, ваша реальна проблема — «немає резервних копій», а не «немає ECC».

Одну перефразовану ідею слід прикріпити до кожного рішення щодо сховища: Надія — це не стратегія. — приписують Вінсу Ломбарді в операційному середовищі, але ставте її як прислів’я.

Факти та історичний контекст (корисні дані)

  1. Soft‑помилки — давня справа. «Космічні промені б’ють біти» звучить як наукова фантастика, але це вимірювали в продакшн‑флотах десятиліттями.
  2. Щільність DRAM зробила помилки більш релевантними. Коли комірки стали меншими, запас від шуму й витоку заряду зменшився; частота помилок стала помітнішою у великому масштабі.
  3. ECC стала стандартом у серверах тому, що час простою коштує дорого. Не тому, що сервери морально кращі, а тому що збої та падіння мають фінансові наслідки.
  4. ZFS популяризував наскрізні контрольні суми для звичайних адміністраторів. Контрольні суми даних і метаданих не є унікальними для ZFS, але ZFS зробив це оперативно доступним.
  5. Scrub змінив культуру. Традиційні RAID часто виявляли деградацію лише під час відбудови; ZFS нормалізував практику «періодично читати все і перевіряти».
  6. Copy‑on‑write змінює зону ураження. ZFS не перезаписує на місці, що зменшує деякі шаблони корупції, але породжує інші (особливо навколо оновлень метаданих).
  7. Dedup — урок смиренності. ZFS dedup може працювати, але це функція, що жере пам’ять і перетворює дрібні помилки на великі збої.
  8. «Потребительські NAS» доросли. Хобі‑лаби й SMB почали запускати багатодискові пули ZFS з корпоративними очікуваннями, часто на споживчій пам’яті й платах.

Де помилки пам’яті шкодять ZFS: модель відмов

1) Проблема з часуванням контрольної суми

ZFS захищає блоки за допомогою контрольних сум, що зберігаються окремо. Добре. Але існує часове вікно: контрольна сума обчислюється від даних в пам’яті.
Якщо дані були пошкоджені до обчислення контрольної суми, ZFS чесно обчислить контрольну суму від пошкоджених байтів і запише обидва. Це не «мовчазна корупція» всередині ZFS; це «валідно перевірена неправильна істина».

ECC допомагає, бо знижує шанс того, що байти, які живлять контрольну суму, будуть неправильними.
Non‑ECC означає, що ви ставитеся на те, що транзієнтні помилки не потраплятимуть у це вікно часто настільки, щоб мати значення.

2) Метадані — там, де день іде шкереберть

Пошкодження даних болісне. Пошкодження метаданих — існентіальне. Метадані ZFS включають вказівники блоків, spacemap, метадані алокацій, MOS‑структури, dnode і ще багато чого.
Поганий біт у метаданих може означати:

  • неможливість імпорту пулу
  • датасет, який не монтується
  • об’єкт, що вказує на неправильний блок
  • resilver, який поводиться «дивно», бо слідує пошкодженим вказівникам

ZFS стійкий, але не невразливий. Ваша відмовостійкість (mirror/RAIDZ) допоможе, якщо корупція знаходиться на диску і виявляється.
Якщо неправильні метадані записалися, відмовостійкість може відтворити помилку, бо це логічно консистентний запис.

3) ARC, чурн при викиданні і «ОЗП як множник відмов»

ARC — це кеш ZFS в пам’яті. Це функція продуктивності, але також місце, де бітова помилка може бути посилена:
неправильні кешовані дані можуть бути віддані, перезаписані або використані для побудови похідного стану.

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

4) Спеціальні vdev і прискорення метаданих малими блоками

Спеціальні vdev (зазвичай SSD‑дзеркала для метаданих і малих блоків) — це ракета для продуктивності й пастка для надійності.
Якщо ви втрачаєте цей vdev і не маєте відмовостійкості, ви можете втратити пул. Якщо ви пошкодите те, що туди пишеться, і корупція валідно перевірена контрольними сумами, ви можете втратити цілісність найважливіших структур.

5) Scrub, resilver і фази «високого читання»

Scrub і resilver читають багато. Вони також навантажують пайплайн: CPU, пам’ять, HBA, кабелі, диски.
Це час, коли латентні проблеми проявляються.
Якщо ви працюєте без ECC, ці операції — ваш лотерейний тираж, бо вони проганяють через ОЗП величезні обсяги даних.

Жарт №1: Якщо ваш розклад scrub — «коли згадаю», вітаю — ви винайшли біт‑рот Шредінгера.

Математика ризику для реальних розгортань

Більшість аргументів про ECC застрягають у абсолютних твердженнях: «Потрібно неодмінно» проти «Мені ніколи не траплялося».
Рішення у продакшні живуть у ймовірностях і витратах. Тож змоделюємо так, щоб ви могли мислити раціонально.

Основна формула: rate × exposure × consequence

Вам не потрібна точна швидкість підмінювання бітів від космічних променів для ваших DIMM, щоб робити корисні підрахунки. Вам потрібні:

  • Швидкість помилок (R): як часто відбуваються помилки в пам’яті (кориговані чи ні). Це сильно варіюється залежно від обладнання, віку, температури та якості DIMM.
  • Експозиція (E): скільки даних і метаданих проходить через пам’ять у «небезпечний» спосіб (записи, оновлення метаданих, вікна контролю сум, пайплайни scrub/resilver).
  • Наслідки (C): що коштує, коли щось іде не так (від «один файл неправильний» до «пул не імпортується»).

Ваш ризик — не просто «R». Ваш ризик — це R × E × C.

Ризик нерівномірно розподілений між робочими навантаженнями

Архів медіа, що переважно читається після завантаження, має інший профіль експозиції, ніж:

  • сховище віртуальних машин з постійним чурном
  • база даних з жорсткими затримками і синхронними записами
  • ціль резервного копіювання з великими послідовними потоками і частим очищенням
  • середовище з активним dedup, де метадані стають найгарячішими даними

Визначте вашу «одиницю втрат»

Перестаньте сперечатися абстрактно. Визначте, що для вас означає втрата:

  • Одиниця A: один пошкоджений файл, що відновлюється з резервної копії (неприємно)
  • Одиниця B: одна VM з корупцією файлової системи (болісно)
  • Одиниця C: відмова імпорту пулу, багатоденне відновлення та розбір з керівництвом (що впливає на кар’єру)

ECC здебільшого зменшує ймовірність подій типу B/C. Йдеться не про вашу колекцію MP3; йдеться про площу ураження.

Резервні копії змінюють наслідки, а не ймовірність

Міцні резервні копії зменшують C. ECC зменшує R.
Якщо є обидва, ви отримуєте мультиплікативну вигоду: менше інцидентів і дешевші інциденти.

Чому «ZFS контрольні суми роблять ECC непотрібним» — неправильне, але поширене скорочення

Контрольні суми ZFS захищають, коли:

  • диск повернув неправильні дані
  • кабель/HBA пошкодив біти в дорозі від диска
  • на диску відбувся секторний деградаційний ріст

Контрольні суми ZFS не гарантують захист, коли:

  • погані дані були контрольовано зведені і записані
  • вказівники метаданих були пошкоджені до обчислення контрольної суми
  • ваш застосунок записав сміття, а ZFS його охайно зберіг

ECC — це upstream‑контроль, що зменшує ймовірність того, що «погані дані стануть істинними».

Отже яка реальна рекомендація?

Якщо ваш пул містить бізнес‑дані, незамінні дані або дані, корупція яких важко виявляється на рівні застосунку, ECC — правильний дефолт.
Non‑ECC може бути обґрунтованим для:

  • одноразових кешів
  • вторинних реплік, де первинна цілісність захищена
  • домашніх лабораторій, де простої допустимі та резервні копії реальні (перевірені)
  • холодного медіа‑сховища, де завантаження контролюється і перевіряється

Якщо ваш план — «я помічу корупцію», ви припускаєте, що корупція буде гучною. Часто вона такою не буває.

Коли non‑ECC прийнятний (а коли це недбало)

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

Non‑ECC може бути ок, коли:

  • ваші дані репліковані в інше місце (і ви перевіряєте репліки)
  • ви можете видалити та відбудувати пул з джерела істини
  • хост ZFS не виконує роботу з великою кількістю метаданих (без dedup, без спеціальних vdev)
  • ви регулярно виконуєте scrub і моніторите тренди помилок

Недбало: пул — це джерело істини

Non‑ECC — погана ставка, коли:

  • у вас один пул з єдиною копією продукційних даних
  • ви використовуєте ZFS для зберігання VM з постійними записами та snapshot
  • ви ввімкнули dedup, бо хтось сказав, що «це економить місце»
  • ви працюєте біля меж пам’яті і ARC постійно під тиском
  • ви використовуєте спеціальні vdev без відмовостійкості або з споживчими SSD без захисту від втрати живлення

У таких сценаріях ECC — це дешево порівняно з першим інцидентом, коли доведеться пояснювати, чому дані «послідовні, але неправильні».

Три корпоративні історії з реального життя

Міні-історія 1: Інцидент через неправильне припущення

Середня компанія використовувала ZFS‑підтримуваний кластер VM для внутрішніх сервісів. Хости були переобладнані настільні машини: багато ядер, багато RAM, без ECC.
Інженер зі зберігання просив серверні плати, але закупівля почула «ZFS має контрольні суми» і переклала це як «ECC необов’язковий».

Все виглядало нормально, поки під час планового технічного вікна: оновлення ядра, перезавантаження, після чого автоматично запустився scrub.
Під час scrub один хост почав логувати помилки контрольних сум. Небагато. Досить, щоби викликати занепокоєння. Пул лишався онлайн, scrub закінчився, і команда записала це як «поганий диск».

Протягом наступних двох тижнів з’явилися спорадичні проблеми з застосунками: база SQLite почала повертати «malformed» помилки. Файлова система однієї VM потребувала ремонту після некоректного вимикання.
Команда ганялася за зайвими слідами: латентність зберігання, мережеві збої, підозрілий SSD.

Переломний момент настав, коли порівняли резервні копії: відновлення одного й того ж образу VM з двох різних snapshot дало різні контрольні суми для кількох блоків.
Це не «деградація диска», це «щось записувало неконсистентну істину у різний час».

Після болісного аналізу вони виявили закономірність: помилки контрольних сум з’являлися під час високої активності пам’яті. Логи хоста показували симптоми, схожі на MCE, на одній машині, але нічого визначного, бо платформа погано видавала телеметрію помилок пам’яті.
Заміна DIMM зменшила кількість помилок, але довіра була втрачена. Вони замінили платформи на ECC‑сумісні системи і додали щомісячні тести відновлення.

Неправильне припущення було не в тому, що «non‑ECC завжди псує дані». Неправильне припущення було в тому, що «контрольні суми роблять upstream коректність неважливою».
Контрольні суми виявляють обмани. Вони не заважають вам їх записувати.

Міні-історія 2: Оптимізація, що відкинулася назад

Інша команда використовувала ZFS для репозиторію резервних копій. Проблема з місцем була реальна, тому хтось запропонував dedup плюс стиснення. На папері ідея блискуча: резервні копії повторювані, dedup має працювати, і ZFS має це вбудовано.
Вони увімкнули dedup на великому датасеті і спостерігали за заощадженнями. Всі почувалися розумними.

Потім з’явилися скарги на продуктивність. Вікна інгесту зсунулися. Система почала свапити під навантаженням.
Команда відреагувала налаштуванням ARC і додаванням швидкого SSD для L2ARC, намагаючись «кешувати проблему». Вони також збільшили recordsize в гонитві за пропускною здатністю.

Те, чого вони не усвідомили: dedup вимагає величезної кількості метаданих у пам’яті. DDT (dedup table) ненажерливий. За напруження пам’яті все стає повільнішим, і система стає вразливішою до граничних випадків.
Вони працювали без ECC, бо «це лише бекапи», а платформа була спочатку оптимізована за вартістю.

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

Відкат зайняв тижні: вимкнули dedup для нових даних, мігрували критичні бекапи в новий пул і провели повну верифікацію відновлення найважливіших наборів.
Оптимізація не була злою; вона була невідповідною обладнанню та операційній зрілості.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Група фінансових послуг експлуатувала ZFS на парі серверів зі ECC RAM, дзеркалами спеціальних vdev і графіком, про який ніхто не сперечався: щотижневий scrub, щомісячні розширені SMART‑тести, щоквартальні вправи з відновлення.
Вся конструкція була майже образливо непомітною. Ніякого dedup. Ніяких екзотичних налаштувань. Просто дзеркала й дисципліна.

Одного кварталу під час вправи з відновлення вони помітили, що відновлення йде повільніше, ніж очікували, і приймаючий хост залогував кілька виправлених помилок пам’яті.
Нічого не впало. Даних не загублено. Але телеметрія була на місці, і тренування змусило команду звернути на це увагу, поки не було пожежі.

Вони проактивно замінили DIMM, потім провели ще одне відновлення й scrub. Чисто.
Через два тижні близнюк заміненого DIMM (та ж партія) почав повідомляти виправлені помилки на іншому сервері. Його теж замінили.

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

Швидкий план діагностики: знайдіть вузьке місце

Коли ZFS починає поводитися дивно — помилки контрольних сум, повільні scrubs, випадкові затримки — ви можете витратити дні, сперечаючись про ECC, ніби це теологія.
Цей план — для моменту, коли потрібні швидкі відповіді.

По‑перше: підтвердьте, з яким видом відмови маєте справу

  • Відмова цілісності: помилки контрольних сум, пошкоджені файли, зростання помилок пулу.
  • Відмова доступності/продуктивності: зависання I/O, scrub займає вічність, висока затримка, таймаути.
  • Тиск ресурсів: свап, OOM‑завершення, ARC‑чурн, насичення CPU.

По‑друге: ізолюйте «шлях диска» від «шляху пам’ять/CPU»

  • Якщо zpool status показує помилки контрольних сум на конкретному пристрої, підозрюйте спочатку диск/кабель/порт HBA/бекплейн.
  • Якщо помилки з’являються одночасно на кількох пристроях, підозрюйте HBA, бекплейн, ОЗП або CPU.
  • Якщо пул чистий, але застосунки бачать корупцію, підозрюйте баги застосунку, ОЗП або мережевий шар вище сховища.

По‑третє: вирішіть, чи можна тримати систему онлайн

  • Виправлені помилки пам’яті — це попередження. Зазвичай можна залишатися онлайн, але заплануйте вікно обслуговування.
  • Невиправлені помилки або зростання помилок контрольної суми: зупиніть записи, зробіть snapshot того, що можете, і плануйте контрольоване перемикання/відновлення.
  • Resilver/scrub на нестабільному обладнанні: ризиковано. Якщо можливо, виправте платформу спочатку.

Практичні завдання: команди, виводи та рішення

Це реальні завдання, які можна виконати на Linux з OpenZFS. Кожне включає, на що звернути увагу і яке рішення прийняти.
(Якщо ви на FreeBSD, команди інші, але операційна логіка та сама.)

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

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
status: One or more devices has experienced an unrecoverable error.
action: Determine if the device needs to be replaced, and clear the errors
  scan: scrub repaired 0B in 05:12:44 with 3 errors on Sun Dec  8 03:20:55 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz1-0                  ONLINE       0     0     0
            ata-WDC_WD80...-part1   ONLINE       0     0     3
            ata-WDC_WD80...-part1   ONLINE       0     0     0
            ata-WDC_WD80...-part1   ONLINE       0     0     0
            ata-WDC_WD80...-part1   ONLINE       0     0     0

errors: Permanent errors have been detected in the following files:

        /tank/vmstore/vm-112-disk-0.qcow2

Що це означає: Збільшення CKSUM на одному диску часто вказує на проблему з диском, кабелем, портом HBA або бекплейном. «Permanent errors» означає, що ZFS не зміг реконструювати деякі блоки.

Рішення: Якщо відмовостійкість не може відновити, відновіть постраждалий файл із резервної копії/снапшоту. Потім дослідіть шлях пристрою (SMART, кабелі). Не «очищайте і забувайте».

Завдання 2: Показати детальні властивості пулу, що впливають на цілісність і відновлення

cr0x@server:~$ zpool get ashift,autotrim,autoexpand,autoreplace,listsnapshots tank
NAME  PROPERTY       VALUE   SOURCE
tank  ashift         12      local
tank  autotrim       off     default
tank  autoexpand     off     default
tank  autoreplace    off     default
tank  listsnapshots  off     default

Що це означає: ashift впливає на write amplification і продуктивність. Це не виправить проблеми ECC, але неправильний ashift може зробити scrubs/resilvers довгими.

Рішення: Якщо ashift неправильний для ваших дисків, плануйте міграцію (не швидке переключення). Якщо scrubs займають дні, ваш вікно експозиції зростає — ще одна причина для цінності ECC.

Завдання 3: Підтвердити розклад scrub і результат останнього scrub

cr0x@server:~$ zpool status tank | sed -n '1,20p'
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 05:12:44 with 3 errors on Sun Dec  8 03:20:55 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0

Що це означає: У вас є недавній scrub і він знайшов помилки. Scrub — це ваша рання система попередження; ставтеся до нього відповідно.

Рішення: Якщо scrubs регулярно знаходять нові помилки контрольних сум, перестаньте думати, що це «випадково». Трендіть це й ескалюйте до апаратної діагностики.

Завдання 4: Перевірити журнали ZFS і повідомлення ядра навколо I/O

cr0x@server:~$ dmesg -T | egrep -i 'zfs|checksum|ata|sas|mce|edac' | tail -n 20
[Sun Dec  8 03:21:12 2025] ZFS: vdev I/O error, zpool=tank, vdev=/dev/sdb1, error=52
[Sun Dec  8 03:21:12 2025] ata3.00: status: { DRDY ERR }
[Sun Dec  8 03:21:12 2025] ata3.00: error: { UNC }
[Sun Dec  8 03:21:13 2025] mce: [Hardware Error]: CPU 0: Machine Check: 0 Bank 8: b200000000070005

Що це означає: Поєднання помилок I/O з повідомленнями MCE — червоний прапор. Не поспішайте звинувачувати диск, якщо CPU повідомляє machine checks.

Рішення: Якщо MCE/EDAC вказує на проблеми пам’яті, віддайте пріоритет стабільності ОЗП/платформи перед наступним scrub/resilver, який може записати нову «істину».

Завдання 5: Підтвердити, що ECC дійсно увімкнено і розпізнається

cr0x@server:~$ sudo dmidecode -t memory | egrep -i 'error correction|ecc|type:|manufacturer' | head -n 20
        Error Correction Type: Multi-bit ECC
        Type: DDR4
        Manufacturer: Micron Technology
        Error Correction Type: Multi-bit ECC
        Type: DDR4
        Manufacturer: Micron Technology

Що це означає: Платформа повідомляє про можливість ECC. Це не гарантує, що Linux отримує EDAC‑події, але це необхідна базова перевірка.

Рішення: Якщо звіт показує «None» або «Unknown», не обманюйте себе думкою, що у вас є ECC «тому що DIMM‑и ECC». Вам також потрібна плата/CPU, що його підтримують.

Завдання 6: Перевірити лічильники EDAC для виправлених/невиправлених помилок пам’яті

cr0x@server:~$ sudo edac-util -v
edac-util: EDAC drivers are loaded. 1 MC detected:
  mc0: 2 Uncorrected Errors with no DIMM info
  mc0: 41 Corrected Errors with no DIMM info

Що це означає: Виправлені помилки означають, що ECC працює. Невиправлені помилки означають, що система зазнала збоїв пам’яті, які ECC не змогла виправити.

Рішення: Будь‑які невиправлені помилки: заплануйте негайне обслуговування і заміну підозрілих DIMM/слотів/плати. Тренд виправлених помилок: трактуйте як сигнал передвідмови і плануйте заміну.

Завдання 7: Переглянути деталі помилок по DIMM (коли доступно)

cr0x@server:~$ sudo ras-mc-ctl --summary
Summary of memory errors:
Location: mc0/csrow0/channel0/dimm0
  Corrected: 37
  Uncorrected: 0
Location: mc0/csrow0/channel1/dimm0
  Corrected: 4
  Uncorrected: 0

Що це означає: Помилки локалізовані на DIMM/каналі, що саме по собі дає можливість для дій — заміни.

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

Завдання 8: Підтвердити, що dedup не увімкнено випадково

cr0x@server:~$ zfs get -r dedup tank
NAME                PROPERTY  VALUE  SOURCE
tank                dedup     off    default
tank/vmstore        dedup     off    default
tank/backups        dedup     off    default

Що це означає: Dedup вимкнено — добре. Якщо він увімкнений десь, навантаження на пам’ять і чутливість метаданих різко зростають.

Рішення: Якщо dedup увімкнено без жорсткого обґрунтування і плану розмірування, вимкніть його для нових записів (set dedup=off) і заплануйте міграцію старих даних при потребі.

Завдання 9: Перевірити розмір ARC і сигнали тиску пам’яті

cr0x@server:~$ arc_summary | egrep -i 'arc size|target size|memory|evict' | head -n 12
ARC size (current):                                   27.4 GiB
Target size (adaptive):                               30.1 GiB
Min size (hard limit):                                8.0 GiB
Max size (high water):                                32.0 GiB
Evict skips:                                          0
Demand data hits:                                     89.3%

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

Рішення: Якщо ARC‑чурн або свап наявні, зменшіть навантаження, додайте пам’ять або обмежте ARC. Не запускайте resilver на хості, який сам по собі свапить до дивних станів.

Завдання 10: Перевірити свапінг і тиск на звільнення пам’яті

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            64Gi        58Gi       1.2Gi       1.0Gi       4.8Gi       2.6Gi
Swap:           16Gi        12Gi       4.0Gi

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

Рішення: Знайдіть, що споживає пам’ять (VMs, dedup, навантаження з великою кількістю метаданих). Додайте пам’ять або зменшіть навантаження. Якщо не можете додати ECC, принаймні уникайте роботи у гарячому режимі зі свапом.

Завдання 11: Перевірити SMART‑здоров’я і UDMA CRC помилки (кабелі підказують)

cr0x@server:~$ sudo smartctl -a /dev/sdb | egrep -i 'reallocated|pending|offline_uncorrectable|udma_crc_error_count' 
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       0
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       0
199 UDMA_CRC_Error_Count    0x003e   200   199   000    Old_age   Always       -       12

Що це означає: UDMA CRC‑помилки зазвичай вказують на кабелі/бекплейн, а не на носій. Помилки контрольних сум ZFS, що корелюють з інкрементом CRC, часто означають «дані були пошкоджені в дорозі».

Рішення: Замініть кабелі, пере‑підключіть з’єднання, перевірте бекплейн/порт HBA. Потім запустіть scrub ще раз, щоб підтвердити стабільність.

Завдання 12: Визначити, чи помилки контрольних сум нові чи історичні

cr0x@server:~$ zpool status -v tank | tail -n 15
errors: Permanent errors have been detected in the following files:

        /tank/vmstore/vm-112-disk-0.qcow2

Що це означає: «Permanent errors» зберігаються, поки ви не відновите/перезапишете постраждалі блоки. Очищення лічильників не виправляє дані.

Рішення: Відновіть файл із відомого доброго snapshot/резервної копії або видаліть і згенеруйте заново. Тільки після усунення проблем zpool clear.

Завдання 13: Зіставити проблему на рівні блоку зі snapshot і спробувати самовідновлення

cr0x@server:~$ zfs list -t snapshot -o name,creation -S creation tank/vmstore | head
NAME                                CREATION
tank/vmstore@hourly-2025-12-08-0300  Sun Dec  8 03:00 2025
tank/vmstore@hourly-2025-12-08-0200  Sun Dec  8 02:00 2025
tank/vmstore@daily-2025-12-07        Sat Dec  7 23:55 2025

Що це означає: У вас є snapshot, з яких можна відкотитися або клонувати — це найшвидший шлях до відновлення цілісності.

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

Завдання 14: Примусити цілеспрямоване читання, щоб виявити латентні помилки

cr0x@server:~$ sudo dd if=/tank/vmstore/vm-112-disk-0.qcow2 of=/dev/null bs=16M status=progress
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 7 s, 307 MB/s
4294967296 bytes (4.3 GB, 4.0 GiB) copied, 14 s, 305 MB/s
...output...

Що це означає: Повний послідовний чит може викликати перевірку контрольних сум і показати, чи помилки повторюються. Це не заміна scrub, але швидкий інструмент триажу для конкретного об’єкта.

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

Завдання 15: Перевірити пропускну здатність scrub/resilver і визначити, чи ви обмежені CPU чи I/O

cr0x@server:~$ iostat -x 2 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    6.22   21.10    0.00   60.37

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
sdb              84.0  10432.0     0.0    0.0   28.4   124.2        3.0     64.0    2.1    2.40   98.0
sdc              82.0  10240.0     0.0    0.0   29.1   124.9        2.0     48.0    1.9    2.35   97.5

Що це означає: Високе %iowait і майже 100% завантаження диска вказують, що scrub обмежений дисками. Якби CPU був завантажений, а диски були вільні, ви були б обмежені CPU/перевіркою контрольних сум.

Рішення: Дискове обмеження: перевірте розклад vdev, ashift, стан дисків і кабелі. CPU‑обмеження: розгляньте швидший CPU, увімкнення апаратного прискорення контрольних сум (якщо є), або зменшення recordsize/метаданихного чурну.

Завдання 16: Підтвердити відмовостійкість спеціального vdev (якщо використовується)

cr0x@server:~$ zpool status tank | sed -n '1,80p'
  pool: tank
 state: ONLINE
config:

        NAME                       STATE     READ WRITE CKSUM
        tank                       ONLINE       0     0     0
          raidz2-0                 ONLINE       0     0     0
            sda1                   ONLINE       0     0     0
            sdb1                   ONLINE       0     0     0
            sdc1                   ONLINE       0     0     0
            sdd1                   ONLINE       0     0     0
          special                  ONLINE       0     0     0
            mirror-1               ONLINE       0     0     0
              nvme0n1p1            ONLINE       0     0     0
              nvme1n1p1            ONLINE       0     0     0

Що це означає: Спеціальний vdev — дзеркало. Це мінімальна прийнятна лінія безпеки, якщо ви розміщуєте там метадані.

Рішення: Якщо special — одиночний пристрій, виправте це перш ніж оптимізувати що-небудь інше. Одиночний special vdev — єдина точка відмови пулу.

Жарт №2: Запускати ZFS з dedup на non‑ECC — це як жонглювати бензопилами, бо це «економить кроки».

Поширені помилки: симптом → корінь → виправлення

1) «Випадкові» помилки контрольних сум на кількох дисках

  • Симптом: CKSUM інкрементується на більше ніж одному диску, іноді на різних дисках у різні дні.
  • Корінь: Проблема спільного шляху (HBA, бекплейн, живлення, кабелювання) або нестабільність пам’яті/CPU, що призводить до запису/перевірки неправильних даних.
  • Виправлення: Перевірте SMART CRC, переставте кабелі/порти, оновіть прошивку HBA, перевірте журнали MCE/EDAC, запустіть memtest у вікні обслуговування і зупиніть запис, доки не стабілізуєтеся.

2) «ZFS каже відновлено, але застосунок все ще зламаний»

  • Симптом: Scrub повідомляє про відновлення, але база/формат файлу все ще скаржиться.
  • Корінь: ZFS відновив пошкоджені блоки із відмовостійкості, але стан на рівні застосунку міг уже інтегрувати погані записи (особливо якщо корупція була до обчислення контрольної суми).
  • Виправлення: Відновіть із застосунково‑консистентних резервних копій або snapshot. Додайте контрольні суми на рівні застосунку, де можливо (бази даних часто мають свої перевірки).

3) Scrub чистий, але ви все одно не довіряєте пулу

  • Симптом: Жодних помилок ZFS, але були незрозумілі падіння, kernel panic або звіти про корупцію файлів.
  • Корінь: Нестабільність пам’яті, що впливає на обчислення та поведінку застосунків більше, ніж на читання з диска, або корупція, яка відбувається до того, як дані потрапляють до ZFS.
  • Виправлення: Перевірте EDAC/MCE, запустіть тести пам’яті, перевірте PSU і температури, валідуйте за допомогою наскрізних контрольних сум застосунку і розгляньте ECC, якщо це сховище як джерело істини.

4) «Ми очистили помилки і тепер все ок»

  • Симптом: Хтось запустив zpool clear і оголосив перемогу.
  • Корінь: Плутання лічильників з корупцією. Очищення скидає звітування, але не реальність.
  • Виправлення: Ідентифікуйте і усуньте пошкоджені файли (відновлення/перезапис). Очищайте лише після усунення проблем з даними і стабілізації обладнання.

5) Пул не імпортується після відключення живлення

  • Симптом: Імпорт не вдається або зависає після різкого відключення живлення.
  • Корінь: Проблеми з апаратним/прошивковим забезпеченням, погана пам’ять або нестабільний шлях зберігання, що виявляються під час інтенсивного відтворення та операцій з метаданими при завантаженні.
  • Виправлення: Перевірте ОЗП (логи ECC або memtest), прошивку HBA, забезпечте коректне відключення живлення (UPS) і документуйте та тестуйте процедури відновлення.

6) «Ми додали пам’ять і тепер з’явились помилки»

  • Симптом: Помилки з’явилися після апгрейду ОЗП.
  • Корінь: Змішані типи DIMM/таймінги, маргінальний DIMM, неправильні налаштування BIOS або плата, яка не може стабільно працювати з конфігурацією.
  • Виправлення: Використовуйте валідаційні конфігурації пам’яті, оновіть BIOS, знизьте частоту до стабільних налаштувань і стежте за лічильниками EDAC. Замініть підозрілі DIMM якомога раніше.

Контрольні списки / покроковий план

Чек‑лист рішення: чи має ця система ZFS використовувати ECC?

  1. Чи є цей пул джерелом істини? Якщо так, за замовчуванням — ECC.
  2. Чи важко виявити корупцію? VM‑образи, бази даних, фото, наукові дані: так. За замовчуванням — ECC.
  3. Чи використовуєте dedup, special vdev або багато snapshot? Якщо так, ECC категорично рекомендовано.
  4. Чи можете ви швидко відновити та чи тестували ви це? Якщо ні, ECC не врятує вас, але non‑ECC завдасть більше проблем.
  5. Чи є у вас телеметрія помилок пам’яті? Якщо ні — ви літаєте в темряві; надавайте перевагу платформам з ECC і видимістю EDAC.

Операційний чек‑лист: якщо ви мусите працювати без ECC

  1. Тримайте все просто: дзеркала/RAIDZ, без dedup, уникайте одиночних special vdev.
  2. Запускайте регулярні scrubs і налаштуйте оповіщення про нові помилки контрольних сум негайно.
  3. Тримайте запас пам’яті: уникайте свапу; обмежте ARC, якщо потрібно.
  4. Використовуйте контрольні суми на рівні застосунку де можливо (перевірки баз даних, хеші для архівів).
  5. Майте перевірені резервні копії: періодичні тести відновлення, а не «маємо десь бекапи».
  6. Майте план апаратних запасних частин: робочі кабелі, запасний HBA, запасний диск і задокументована процедура заміни.

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

  1. Заморозьте припущення: не оголошуйте «поганий диск» одразу.
  2. Збережіть вивід zpool status -v і системні логи навколо події.
  3. Перевірте SMART, особливо CRC і pending sectors.
  4. Перевірте MCE/EDAC лічильники. Якщо є виправлені помилки — трактуйте обладнання як деградоване.
  5. Визначте постраждалі файли; відновіть із snapshot/бекапу, якщо можливо.
  6. Виправте фізичний шар (кабель/порт/HBA) перед тим, як запускати scrub знову.
  7. Запустіть scrub і перевірте, що тренд помилок рівний.
  8. Якщо помилки повторюються на кількох пристроях, заплануйте обслуговування для ізоляції ОЗП/HBA/бекплейн.

Питання та відповіді

1) Чи вимагає ZFS ECC RAM?

ZFS не вимагає ECC для роботи. ECC — це контроль надійності. Якщо пул містить важливі дані, ECC — правильний дефолт.

2) Якщо ZFS має контрольні суми, чому корупція пам’яті ще важлива?

Контрольні суми виявляють корупцію після обчислення контрольної суми. Якщо пошкоджені дані були контрольовано і записані, ZFS пізніше валідуватиме їх як «коректні», бо вони відповідають своїй контрольній сумі.

3) Чи підходить non‑ECC для домашнього NAS?

Іноді. Якщо у вас є реальні резервні копії і ви можете терпіти періодичне відновлення, non‑ECC може бути прийнятним компромісом.
Якщо ви зберігаєте незамінні фото і ваша «резервна копія» — ще один диск в тій самій коробці, ви не інженеруєте, а грально ризикуєте.

4) Що гірше: відсутність ECC чи відсутність розкладу scrub?

Відсутність розкладу scrub зазвичай гірша в короткій перспективі, бо латентні проблеми диска ви відкриєте під час відбудови — коли найменш бажано мати сюрпризи.
Відсутність ECC підвищує шанс, що деякі сюрпризи стануть дивнішими і важчими для діагностики.

5) Чи робить дзеркало/RAIDZ ECC менш важливою?

Відмовостійкість допомагає, коли корупція відбувається на диску і виявляється. ECC допомагає запобігти поганим записам і захищає операції в пам’яті.
Вони адресують різні режими відмов; вони доповнюють одне одного, а не замінюють.

6) Чи можу я «перевірити» non‑ECC систему, запустивши memtest один раз?

Memtest корисний, але це тест у певний момент часу. Деякі відмови залежать від температури або навантаження і проявляються лише через місяці.
Якщо ви серйозно ставитеся до цілісності, надавайте перевагу ECC плюс моніторинг, щоб бачити виправлені помилки до того, як вони стануть інцидентом.

7) Які функції ZFS роблять ECC більш важливим?

Dedup, special vdev, інтенсивне snapshotting/cloning, робочі навантаження з великою кількістю метаданих і системи, що працюють біля меж пам’яті.
Вони збільшують обсяг критичного стану в пам’яті і вартість помилок.

8) Якщо я бачу виправлені ECC помилки, панікувати?

Ні. Виправлені помилки означають, що ECC виконала свою роботу. Але не ігноруйте їх. Зростаючий тренд — сигнал для обслуговування: замініть DIMM, перевірте охолодження і BIOS‑налаштування.

9) Чи достатньо ECC, щоб гарантувати цілісність?

Ні. Вам все одно потрібні відмовостійкість, scrubs, резервні копії та валідація. ECC зменшує один клас upstream‑ризиків; воно не робить систему невразливою і не робить резервні копії зайвими.

10) Яке найдешевше підвищення надійності, якщо я не можу отримати ECC?

Операційна дисципліна: scrubs, моніторинг SMART, тести відновлення і уникнення свапу. Також спростіть пул (дзеркала) і уникайте ризикованих функцій (dedup, одиночний special vdev).

Наступні кроки, які можна зробити цього тижня

  1. Визначте вашу одиницю втрат. Якщо втрата пулу — це подія для кар’єри, купуйте обладнання з підтримкою ECC або перемістіть навантаження.
  2. Увімкніть і моніторьте правильні сигнали. Слідкуйте за zpool status, результатами scrub, SMART CRC/pending sectors і лічильниками EDAC/MCE.
  3. Заплануйте scrubs і тестові відновлення. Scrub знаходить проблеми; тести відновлення доводять, що ви можете їх пережити.
  4. Аудитуйте функції ZFS. Якщо dedup увімкнено «бо економить місце», вимкніть для нових записів і спроектуйте правильно перед повторним включенням.
  5. Якщо залишаєтесь на non‑ECC, зменшіть експозицію. Тримайте запас пам’яті, уникайте свапу і зберігайте консервативну топологію пулу.

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

3D-стекінг і майбутнє чиплетів: куди рухаються CPU

О 02:17 дзвонить ваш on-call телефон. Латентність зросла, CPU «лише» на 55%, а хтось у чаті каже: «Певно, мережа». Ви дивитесь на графіки й відчуваєте знайомий жах: система повільна, але ні ваша стара ментальна модель, ні звичні підозри це не пояснюють.

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

Чому процесори змінилися: фізика, гроші і кінець «просто зменшіть техпроцес»

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

Коли ви чуєте «чиплети» та «3D-стекінг», не перекладайте це як «хитра інженерія». Перекладайте як: старі економічні й фізичні припущення зламалися, тож пакування стало новою архітектурою. Інновації переміщуються зсередини кристалу у взаємодію між кристалами.

Факти й історичний контекст (те, що дійсно допомагає міркувати)

  • Факт 1: Dennard scaling (стабільна густина потужності при зменшенні транзисторів) фактично зупинилася в середині 2000-х, що змусило частотний ріст заякоритись і підштовхнуло до мультикорних дизайнів.
  • Факт 2: Затримка міжз’єднань уже кілька років є першорядним вузьким місцем; внутрішні дроти не стають пропорційно швидшими з кожним вузлом, тож «більшою пластинкою» означає більше часу на переміщення бітів.
  • Факт 3: Ліміти ретиклу обмежують, наскільки великим може бути одне експонування літографії; дуже великі кристали стають кошмаром за виходом придатних виробів, якщо їх не розбивати або не зшивати.
  • Факт 4: Індустрія давно використовує мульти-чіп модулі (згадайте: ранні пакети з двома кристалами, серверні модулі), але сьогоднішні чиплети значно стандартизованіші й критичніші за продуктивність.
  • Факт 5: High Bandwidth Memory (HBM) стала практичною завдяки стекуванню DRAM-чіпів і з’єднанню їх TSV; це показало, що вертикальна інтеграція може перевершити традиційну пропускну здатність DIMM.
  • Факт 6: 3D-стекування кешу в мейнстримних CPU показало дуже конкретний урок: додавання SRAM вертикально може підвищити продуктивність без збільшення найбільш гарячого логічного кристала.
  • Факт 7: Гетерогенні ядра (концепти big/little) існували в мобільних пристроях роками; тепер вони поширені в серверах, бо пропускна здатність і теплові обмеження, а не пікова частота, визначають пропускну спроможність.
  • Факт 8: Складні пакувальні технології (2.5D інтерпозери, силіконові мости, fan-out) тепер є конкурентною відмінністю, а не лише деталлю бекенду виробництва.

Операційний висновок: наступні 10–15% приросту продуктивності менш ймовірно прийдуть від нових інструкцій, і більш ймовірно — від кращої локальності, розумніших ієрархій пам’яті та більш щільних зв’язків між кристалами. Якщо ваше навантаження чутливе до варіації латентності, треба ставитися до пакування і топології так само, як до маршрутизації в мережі.

Чиплети, міжз’єднання і чому «сокет» більше не означає те, що ви думаєте

Чиплет CPU — це пакет, що містить кілька кристалів, кожен із яких спеціалізується на чомусь: ядра, кеш, контролери пам’яті, IO, акселератори, іноді навіть процесори безпеки. Сам пакет — це продукт. «CPU» більше не є одною плитою; це невелика розподілена система під тепловим розподільником.

Чиплети існують з трьох прямих причин:

  1. Вихід придатних виробів (yield): менші кристали мають кращий вихід; дефекти не вбивають весь величезний кристал.
  2. Комбінація технологічних вузлів: швидка логіка на передовому вузлі, IO на дешевшому, зрілому вузлі.
  3. Продуктова гнучкість: повторно використовувати відпрацьований IO-кристал у кількох SKU; варіювати кількість ядер і кеш-тайлів без повної переробки.

Міжз’єднання — тепер архітектура

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

Сучасні пакети використовують власні fabric-рішення, і галузь рухається в бік інтероперабельних стандартів die-to-die, наприклад UCIe. Ключова думка не в абревіатурі. Важливо, що зв’язки між кристалами обслуговуються як високошвидкісний IO: серіалізовані, синхронізовані, керовані по живленню, треновані, іноді з повторними спробами. Отже, стан лінку, лічильники помилок і стани живлення можуть впливати на продуктивність у способи, що здаються «випадковими», якщо їх не вимірювати.

Жарт №1: Чиплети — як мікросервіси: усім подобається гнучкість, поки не треба дебажити затримки через межі, які ви спеціально створили.

NUMA не був новим. Ви просто перестали його поважати.

Чиплетні CPU перетворюють кожен сервер на більш нюансовану NUMA машину. Іноді «NUMA-вузли» відповідають контролерам пам’яті; іноді — комплексам ядер; іноді — обом. У будь-якому випадку локальність важлива: яке ядро звертається до якої пам’яті, який тайл останнього рівня кешу ближчий, і як часто ви перетинаєте міжз’єднання.

Якщо ваша стратегія продуктивності все ще починається і закінчується «додайте ядра» і «запиніть потоки», ви натрапите на нову стіну: переповнення міжз’єднання і конкуренція в ієрархії пам’яті. Пакет CPU тепер має внутрішні трафік-патерни, і ваше навантаження може створювати «гарячі точки».

3D-стекінг: вертикальна пропускна здатність, вертикальні проблеми

3D-стекінг — це використання кількох кристалів, складених вертикально з щільними з’єднаннями (часто TSV, мікро-бампси або гібридне бондування). Його застосовують для кешу, DRAM (HBM) і все частіше для логіки-на-логиці.

Навіщо стекувати?

  • Пропускна здатність: вертикальні з’єднання можуть бути значно щільнішими, ніж маршрутизація по краю пакета.
  • Латентність: ближча фізична відстань може знизити час доступу для певних структур (особливо кешу).
  • Ефективність площі: можна додати ємність, не збільшуючи 2D-підпис найгарячішого логічного кристала.

Але нічого не дається просто так. 3D-стекінг вводить неприємний операційний трикутник: термальність, вихід придатних виробів (yield) і надійність.

Стекований кеш: чому це працює

Стекований SRAM над обчислювальним кристалом дає великий кеш останнього рівня, не роблячи обчислювальний кристал гігантом. Це може бути величезною перевагою для навантажень зі робочими наборами трохи більшими за традиційні розміри кешу: багато ігор, деякі EDA-потоки, певні in-memory БД, key-value сховища з “гарячими” ключами та аналітичні конвеєри з повторними сканами.

З операційної точки зору стекований кеш змінює дві речі:

  1. Продуктивність стає більш бімодальною. Якщо ваше навантаження вміщується в кеш — ви герой. Якщо ні — ви повертаєтесь до DRAM і виграш зникає.
  2. Тепловий резерв стає цінним. Додатковий кремній над обчислювальним кристалом впливає на тепловий потік; поведінка turbo і стійкі частоти може змінитися так, що це виявиться як варіація латентності.

HBM: чит-код пропускної здатності з цінником

HBM стекує DRAM-чіпи і розміщує їх близько до обчислювального кристала (часто через інтерпозер). Це дає величезну пропускну здатність у порівнянні з традиційними DIMM, але є обмеження по ємності на стек і висока вартість. Це також змінює режими відмов і спостережуваність: помилки пам’яті можуть проявлятися інакше, і планування ємності стає іншим видом спорту.

3D і 2.5D пакування також нав’язують нове правило проєктування: ваше програмне забезпечення має розуміти рівні. HBM проти DDR, близька пам’ять проти далекої, кеш-на-пакеті проти кеш-на-кристалі. «Просто аллоцювати пам’ять» стає рішенням щодо продуктивності.

Жарт №2: Стекувати кристали — чудово, поки ви не згадаєте, що тепло теж стекується, і на відміну від списку завдань його не можна відкласти.

Справжній ворог: байти, а не FLOPS

Більшість продакшн-систем не обмежені сирою арифметичною пропускною здатністю. Вони обмежені переміщенням даних: від пам’яті до кешу, від кешу до ядра, від ядра до NIC, від сховища до пам’яті і назад. Чиплети і 3D-стекінг — це визнання індустрією того, що пам’ять і міжз’єднання — це головна дія.

Тут інстинкти SRE допомагають. Коли пакет CPU стає фабрикою, вузькі місця виглядають як:

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

Що змінюється з чиплетами і стекуванням

Локальність пам’яті більше не опціональна. На великому монолітному кристалі «віддалений» доступ усе ще може бути досить швидким. У чиплетах віддалений доступ може проходити через hops fabric і конкурувати з іншим трафіком. На SKU зі стекованим кешом «локальний» кеш може бути більший, але штраф за промах може бути більш помітним через змінену частоту/теплову поведінку.

Пропускна здатність не була однорідною. Деякі кристали мають ближчий доступ до певних контролерів пам’яті. Деякі ядра тісніше діляться певними кеш-тайлами. Топологія може винагороджувати хороше планування і карати наївне розміщення.

Варіація латентності стає нормою. Стани управління живленням, відсікання годин fabric і алгоритми бусту можуть змінювати внутрішні латентності. Ваш p99 помітить це раніше, ніж середні величини.

Теплові режими і енергоспоживання: пакет — нове поле битви

На папері ви купуєте CPU з TDP і частотою boost і вважаєте, що це кінець історії. Насправді сучасні CPU — це системи з управлінням енергією, які постійно домовляються про частоти на основі температури, струму й характеристик навантаження. Чиплети й 3D-стеки ускладнюють ці домовленості.

Гарячі точки і теплові градієнти

З чиплетами у вас немає одного однорідного теплового профілю. Є гарячі точки, де ядра щільні, окремі IO-кристали, які працюють прохолодніше, і іноді стековані кристали, що перешкоджають відведенню тепла від обчислювального кристала знизу. У довготривалих продакшн-навантаженнях стійкі частоти важливіші за пікові бусти.

Дві операційні наслідки:

  • Бенчмарки частіше брешуть. Короткі бенчмарки ловлять boost; продакшн ловить steady-state і ліміти потужності.
  • Охолодження — це продуктивність. Маргінальне охолодження або проблеми з повітряним потоком не лише спричинять тротлінг; вони спричинять варіацію, яку важче діагностувати.

Надійність: більше з’єднань — більше місць для проблем

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

Корисний операційний афоризм, переказ ідеї відомого голосу в надійності: Складні системи виходять з ладу складними способами; зменшуйте невідомі і вимірюйте потрібні речі. (парафраз ідеї, натхненної підходами інженерії надійності)

Переклад: не припускайте однорідності між хостами і не припускайте, що два сокети поводяться однаково лише через те, що SKU співпадає.

Що це означає для SRE: продуктивність, надійність і шумні сусіди

Вам не потрібно ставати інженером пакування. Але потрібно припинити сприймати «CPU» як один скалярний ресурс. У світі чиплетів і стеків ви керуєте:

  • Топологічними обчисленнями (ядра не однакової відстані від пам’яті і кешу)
  • Пропускною здатністю міжз’єднання (внутрішній fabric може насичуватися)
  • Тепловим резервом (стійкі частоти, тротлінг і p99)
  • Політикою енергоспоживання (ліміти, turbo і взаємодія з планувальником)

Спостережуваність повинна розширитися

Традиційний моніторинг хостів — CPU%, load average, пам’ять у використанні — дедалі частіше не пояснюватиме вузькі місця. Вам потрібен мінімум базового розуміння:

  • NUMA локальність (чи вирівняні потоки й пам’ять?)
  • Поведіна кешу (LLC misses, тиск на пропускну здатність)
  • Частота й тротлінг (чи обмежені ви потужністю?)
  • Розміщення планувальника (чи Kubernetes або systemd не перемістили ваше навантаження по вузлах?)

І так, це дратує. Але це менше дратує, ніж квартал апгрейдів CPU з очікуванням прискорення і отримання уповільнення.

Швидка діагностика: знаходьте вузьке місце за хвилини

Це потік тріажу, який я використовую, коли сервіс гальмує на новій платформі з чиплетами/стеками або гіршає після масштабування. Мета — не ідеальний корінь проблеми. Мета — зробити правильне наступне рішення швидко.

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

  1. Перевірте частоту CPU і тротлінг: якщо частоти низькі під навантаженням — ви обмежені потужністю/термально.
  2. Перевірте пропускну здатність пам’яті і тиск промахів кешу: якщо LLC misses і пропускна здатність високі — ви обмежені пам’яттю.
  3. Перевірте NUMA локальність: якщо віддалених звернень багато — ви, ймовірно, обмежені топологією/планувальником.

Друге: підтвердіть топологію і розміщення

  1. Перевірте NUMA-вузли і відповідність CPU → вузлам.
  2. Перевірте афінітет процесу до CPU і політику пам’яті.
  3. Перевірте, чи навантаження не стрибає між вузлами (міграції планувальника).

Третє: ізолюйте одну змінну і повторіть

  1. Зафіксуйте навантаження на одному NUMA-вузлі; порівняйте p95/p99.
  2. Примусьте локальне виділення пам’яті; порівняйте пропускну здатність.
  3. Застосуйте консервативну політику енергоспоживання; порівняйте варіацію.

Якщо ви не можете відтворити істотних змін, контролюючи розміщення і стани живлення, проблема ймовірно на вищому шарі (блокування, GC, IO), і не варто звинувачувати пакет CPU. Сучасні CPU складні, але не магічні.

Практичні завдання з командами: що запускати, що означає, що вирішувати

Ось реальні завдання, які можна виконати на Linux-хостах, щоб зрозуміти поведінку, пов’язану з чиплетами/3D-стекінгом. Команди навмисно нудні. Нудні інструменти тримають вас чесними.

Завдання 1: Швидко відобразіть NUMA-топологію

cr0x@server:~$ lscpu | egrep 'Model name|Socket|Thread|Core|NUMA|CPU\(s\)'
CPU(s):                               128
Model name:                           AMD EPYC 9xx4
Thread(s) per core:                   2
Core(s) per socket:                   64
Socket(s):                            1
NUMA node(s):                         8

Що означає вивід: У вас 8 NUMA-вузлів на одному сокеті. Це топологія, притаманна чиплетам: кілька доменів пам’яті і hops між ними в одному пакеті.

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

Завдання 2: Дивіться, які CPU належать яким NUMA-вузлам

cr0x@server:~$ numactl --hardware
available: 8 nodes (0-7)
node 0 cpus: 0-15
node 0 size: 64000 MB
node 0 free: 61234 MB
node 1 cpus: 16-31
node 1 size: 64000 MB
node 1 free: 60110 MB
node 2 cpus: 32-47
node 2 size: 64000 MB
node 2 free: 59872 MB
node 3 cpus: 48-63
node 3 size: 64000 MB
node 3 free: 62155 MB
node 4 cpus: 64-79
node 4 size: 64000 MB
node 4 free: 60990 MB
node 5 cpus: 80-95
node 5 size: 64000 MB
node 5 free: 61801 MB
node 6 cpus: 96-111
node 6 size: 64000 MB
node 6 free: 61644 MB
node 7 cpus: 112-127
node 7 size: 64000 MB
node 7 free: 62002 MB

Що означає вивід: Кожен NUMA-вузол володіє діапазоном CPU і частиною пам’яті. Якщо ваш процес працює на CPU вузла 0, але аллоцює пам’ять з вузла 6, він платитиме “фабричну” плату за кожен віддалений доступ.

Рішення: Для сервісів з чутливою латентністю вирівняйте CPU pinning і політику пам’яті. Для завдань на пропускну здатність можливо краще інтерлеювати пам’ять для більшої сумарної пропускної здатності.

Завдання 3: Перевірте, чи ядро записує проблеми NUMA-локальності

cr0x@server:~$ numastat -p 1 3
Per-node process memory usage (in MBs) for PID 1 (systemd)
Node 0 Node 1 Node 2 Node 3 Node 4 Node 5 Node 6 Node 7 Total
----- ----- ----- ----- ----- ----- ----- ----- -----
Numa_Hit      12     10      9      8      9     10      8      9    75
Numa_Miss      1      0      0      0      0      0      0      0     1
Numa_Foreign   0      0      0      0      0      0      0      0     0
Interleave_Hit 0      0      0      0      0      0      0      0     0
Local_Node    12     10      9      8      9     10      8      9    75
Other_Node     1      0      0      0      0      0      0      0     1

Що означає вивід: Для PID 1 усе в порядку. Для вашого реального сервісу, якщо Other_Node великий, ви платите за віддалені доступи.

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

Завдання 4: Перевірте поведінку частоти CPU під навантаженням

cr0x@server:~$ sudo turbostat --Summary --quiet --show CPU,Avg_MHz,Busy%,Bzy_MHz,PkgTmp,PkgWatt --interval 5
CPU  Avg_MHz  Busy%  Bzy_MHz  PkgTmp  PkgWatt
-    2850     62.10  4588     86      310.12

Що означає вивід: Завантажені ядра працюють високо (Bzy_MHz), температура пакета висока, і потужність суттєва. Якщо Bzy_MHz падає з часом, а Busy% залишається високим — швидше за все ви обмежені потужністю/теплом.

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

Завдання 5: Підтвердьте, що політика живлення (governor) не саботує вас

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

Що означає вивід: Governor встановлено в performance. Якщо там powersave на хості, чутливому до латентності, ви фактично просите джиттер.

Рішення: Встановіть відповідну політику для ролі кластера. Пакет для пакетних задач може економити енергію; OLTP-кластер не повинен вдавати вигляд ноутбука.

Завдання 6: Виміряйте міграції планувальника (тихий NUMA-вбивця)

cr0x@server:~$ pidstat -w -p $(pgrep -n myservice) 1 5
Linux 6.5.0 (server)  01/12/2026  _x86_64_  (128 CPU)

01:10:01 PM   UID       PID   cswch/s nvcswch/s  Command
01:10:02 PM  1001     43210   120.00     15.00  myservice
01:10:03 PM  1001     43210   135.00     20.00  myservice
01:10:04 PM  1001     43210   128.00     18.00  myservice

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

Рішення: Розгляньте прив’язку CPU для найгарячіших потоків або налаштуйте час виконання (GC-потоки, кількість воркерів) щоб зменшити оборот.

Завдання 7: Перевірте тиск пропускної здатності пам’яті за допомогою pcm-memory (якщо встановлено)

cr0x@server:~$ sudo pcm-memory 1 -csv
Time,Ch0Read,Ch0Write,Ch1Read,Ch1Write,SystemRead,SystemWrite
1.00,12.3,5.1,11.8,4.9,198.4,82.1
2.00,12.5,5.0,12.1,4.8,201.0,80.9

Що означає вивід: Системна швидкість читання/запису висока. Якщо вона близька до меж платформи під час інциденту — ви обмежені пам’яттю, а не CPU.

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

Завдання 8: Спостерігайте сигнали промахів кешу і стагнацій за допомогою perf

cr0x@server:~$ sudo perf stat -p $(pgrep -n myservice) -e cycles,instructions,cache-misses,branches,branch-misses -a -- sleep 10
 Performance counter stats for 'system wide':

    38,112,001,220      cycles
    52,880,441,900      instructions              #    1.39  insn per cycle
       902,110,332      cache-misses
     9,221,001,004      branches
       112,210,991      branch-misses

      10.002113349 seconds time elapsed

Що означає вивід: Багато промахів кешу. IPC непоганий, але промахи можуть домінувати над часом виконання залежно від навантаження. На чиплетних CPU промахи можуть транслюватися у трафік fabric і віддалені звернення пам’яті.

Рішення: Якщо промахи кешу корелюють зі спайками латентності, пріоритезуйте локальність: прив’яжіть потоки, зменшіть спільний стан і протестуйте SKU зі стекованим кешем, коли робочий набір трохи перевищує LLC.

Завдання 9: Перевірте помилки пам’яті та шторми скоректованих помилок

cr0x@server:~$ sudo ras-mc-ctl --summary
Memory controller events summary:
  Corrected errors: 24
  Uncorrected errors: 0
  No DIMM labels were found

Що означає вивід: Є скоректовані помилки. Зростаючий рівень може спричинити деградацію продуктивності та непередбачувану поведінку; на просунутих платформах пакування ви хочете помічати це рано.

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

Завдання 10: Підтвердьте стан лінків/PCIe (IO-кристал теж частина історії)

cr0x@server:~$ sudo lspci -vv | sed -n '/Ethernet controller/,+25p' | egrep 'LnkSta:|LnkCap:'
LnkCap: Port #0, Speed 16GT/s, Width x16
LnkSta: Speed 16GT/s (ok), Width x16 (ok)

Що означає вивід: Лінк працює на очікуваній швидкості/ширині. Якщо ви бачите downtrained лінки — IO-продуктивність падає і CPU цикли витрачаються на обробку переривань/пакетів.

Рішення: Downtrained лінки вимагають перевірки: riser-и, BIOS-настройки, прошивка та фізичне встановлення. Не «оптимізуйте» ПО навколо зламаного обладнання.

Завдання 11: Підтвердіть розподіл переривань (щоб уникнути pileup на одному ядрі)

cr0x@server:~$ cat /proc/interrupts | egrep 'eth0|mlx|ens' | head
  55:   10223342          0          0          0   IR-PCI-MSI 524288-edge      ens3f0-TxRx-0
  56:          0    9981221          0          0   IR-PCI-MSI 524289-edge      ens3f0-TxRx-1
  57:          0          0    9875522          0   IR-PCI-MSI 524290-edge      ens3f0-TxRx-2

Що означає вивід: Переривання розподілені по CPU. Якщо всі переривання лягають на одне CPU в одному NUMA-вузлі, а ваше навантаження працює в іншому — ви отримаєте крос-вузловий трафік і джиттер.

Рішення: Прив’яжіть IRQ близько до NUMA-вузла NIC і до сервісних потоків, які обробляють пакети. Локальність стосується й IO.

Завдання 12: Перевірте політику пам’яті і явний локальний тест

cr0x@server:~$ numactl --cpunodebind=2 --membind=2 ./bench --duration 30
throughput=118223 ops/s
p99_latency_ms=3.4

Що означає вивід: Ви примусили і CPU, і пам’ять бути на вузлі 2. Порівняйте це з непінованими результатами. Великий дельта вказує на штрафи NUMA/fabric.

Рішення: Якщо пінінг суттєво покращує p99 — впровадьте політику розміщення (systemd CPUAffinity, Kubernetes topology manager або прив’язка на рівні сервісу), замість полювання на мікрооптимізації.

Завдання 13: Перевірте hugepages і індикатори тиску TLB

cr0x@server:~$ grep -E 'HugePages_Total|HugePages_Free|Hugepagesize' /proc/meminfo
HugePages_Total:    4096
HugePages_Free:     3900
Hugepagesize:       2048 kB

Що означає вивід: Hugepages доступні. У пам’яте-інтенсивних навантаженнях hugepages можуть зменшити промахи TLB, що важливіше, коли латентність пам’яті вже вища через віддалені звернення.

Рішення: Якщо профілювання показує тиск на TLB — увімкніть hugepages і валідуйте вплив. Не застосовуйте механіку наосліп — вимірюйте.

Завдання 14: Виявлення тротлінгу і причин лімітів потужності (приклад Intel через RAPL)

cr0x@server:~$ dmesg | egrep -i 'thrott|powercap|rapl' | tail -n 5
[ 8123.221901] intel_rapl: power limit changed to 210W
[ 8123.222110] CPU0: Package power limit exceeded, capping frequency

Що означає вивід: Система має power-capping. Ваш бенчмарк міг виконатись до втручання ліміту; продакшн виконується вже під цим лімітом.

Рішення: Узгодьте BIOS/прошивку і налаштування потужності з інтенцією навантаження. Якщо ви обмежуєтеся задля бюджету енергії датацентру — відкоригуйте SLO і налаштуйте конкурентність.

Три корпоративні міні-історії з ери чиплетів

Міні-історія 1: Інцидент через неправильне припущення

Середня SaaS-компанія мігрувала latency-чутливий API-тир на нові сервери. Така сама кількість ядер як раніше, вищі рекламовані boost-частоти і великий показник L3-кешу, який виглядав як безкоштовні гроші. Роллаут був консервативним: 5% canary, метрики спочатку виглядали нормально, потім 25%, потім 50%.

Приблизно на половині флоту p99 латентність почала стрибати. Не зростати плавно — стрибати. Графіки мали пилкоподібний шаблон, і люди сперечалися про патерни трафіку і GC. Завантаження CPU залишалося помірним. Мережа виглядала чистою. Сховище мовчало. Канал інцидентів наповнився найгіршим реченням в операціях: «Нічого не виглядає неправильно».

Неправильне припущення: вони поводилися з CPU як із однорідним ресурсом і думали, що якщо середній CPU% нормальний — CPU не є вузьким місцем. Насправді навантаження розподілялось по NUMA-вузлах і часто аллоцювало пам’ять віддалено через поведінку рантайму та свободу планувальника контейнерів. Віддалені звернення не були катастрофічними; вони були варіабельними, що руйнувало хвостову латентність.

Вони довели це, зафіксувавши сервіс в одному NUMA-вузлі і примусивши локальне виділення пам’яті в тесті. p99 стабілізувався миттєво, і пилкоподібний малюнок зник. Виправлення було не гламурним: топологічно-усвідомлене планування, CPU pinning для найгарячіших подів і свідома політика пам’яті. Вони також перестали перепаковувати latency-чутливі й пакетні поди на одному сокеті. «Більша завантаженість» не була ціллю; ціль була — передбачувана латентність.

Міні-історія 2: Оптимізація, що відігралася

Фінтех-команда запускала ризиковий движок, який багаторазово сканував великий in-memory набір. Вони купили SKU зі стекованим кешем, бо бенчмарк вендора показав великий приріст. Ранні тести були багатообіцяючими. Пропускна здатність зросла. Усі святкували. Потім вони зробили те, що роблять компанії: «оптимізували».

Команда агресивно підняла паралелізм, думаючи, що додатковий кеш дозволить масштабуватись. Вони також включили агресивніший turbo-профіль у BIOS, щоб ганяти короткочасні швидкісні підйоми. У стейджингу робота закінчувалась швидше — більшість часу.

У продакшні оптимізація відплатилася двома способами. По-перше, додаткові потоки збільшили трафік між чиплетами, бо робоча структура була спільною і не була коректно розділена. Міжз’єднання почало перевантажуватись. По-друге, turbo-політика швидко підвищила температуру, викликавши термальне тротлінгування посеред виконання. Система не просто сповільнилась; вона стала непередбачуваною. Деякі запуски закінчувалися швидко; деякі потрапляли на тротлінг і тягнулися.

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

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Велика корпоративна платформа стандартизувала «чекліст при підключенні апаратури» для нових поколінь CPU. У нього входили базові налаштування BIOS/прошивки, версії мікрокоду, перевірка NUMA-топології і набір perf/latency smoke-тестів, зафіксованих на конкретних вузлах.

Коли прийшла партія нових серверів, smoke-тести показали тонку регресію: пропускна здатність пам’яті була нижчою, ніж очікувалась на одному NUMA-вузлі, і p99 латентність при синтетичному змішаному навантаженні була гіршою. Нічого явно не падало. Більшість команд оголосили б це «в межах варіації» і пішли далі.

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

Вони виправили базу, перевстановили хости, повторно запустили ті самі зафіксовані тести і отримали очікувані результати. Ніяких геройств. Жодних нічних інцидентів. Просто операційна дисципліна: вимірюйте, стандартизуйте і відмовляйтеся приймати мовчазну варіацію у світі, де пакети — це малі розподілені системи.

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

1) Симптом: спайки p99 після масштабування на більше ядер

Корінна причина: Контенція між чиплетами та віддалені звернення пам’яті зростають, коли потоки розповсюджуються по NUMA-вузлах; спільні структури даних підсилюють трафік.

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

2) Симптом: завантаження CPU помірне, але пропускна здатність низька

Корінна причина: Стави пам’яті (LLC misses, латентність DRAM), задирки fabric або часті міграції приховані за «не завантажений» CPU.

Виправлення: Використовуйте perf stat і інструменти пропускної здатності пам’яті; перевірте numastat; піньте й локалізуйте; зменшуйте аллокаторний шум і копіювання.

3) Симптом: нові сервери швидші в бенчмарках, але гірші в продакшні

Корінна причина: Бенчмарки ловлять boost-частоти і гарячі кеш-стани; продакшн працює в стійких лімітах потужності і змішаних навантаженнях.

Виправлення: Тестуйте стійкі прогони, включайте p99-метрики і валідуйте під реалістичною конкурентністю та тепловими умовами.

4) Симптом: один хост у пулі постійно дивний

Корінна причина: Downtrained PCIe-лінк, деградований канал пам’яті, шторм скоректованих помилок або дрейф BIOS, що впливає на енергію/топологію.

Виправлення: Перевірте lspci -vv, підсумки RAS, версії мікрокоду/BIOS; ізолюйте та виправте замість того, щоб налаштовувати під це ПО.

5) Симптом: джиттер латентності після увімкнення «енергозберігаючих» функцій

Корінна причина: Агресивні C-state, відсікання годин fabric, частотне масштабування або пакетні ліміти потужності викликають варіабельну поведінку пробудження/буста.

Виправлення: Використовуйте performance governor для латентнісних режимів, налаштуйте BIOS power states і перевірте turbostat під реальним навантаженням.

6) Симптом: падіння pps мережі після оновлення апаратури

Корінна причина: IRQ і потоки на різних NUMA-вузлах; IO-кристал і локальність NIC мають значення, і крос-вузловий трафік додає латентності.

Виправлення: Вирівняйте афінітет IRQ і потоки застосунку з NUMA-вузлом NIC; підтвердіть ширину/швидкість лінку; уникайте надмірної консолідації.

7) Симптом: «Ми додали стекований кеш, але не побачили виграш»

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

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

8) Симптом: після контейнеризації продуктивність погіршилася на чиплетних CPU

Корінна причина: Планувальник контейнерів перемістив потоки по CPU/NUMA-вузлах; cgroup CPU-квоти ввели сплески; локальність сторінки кешу погіршилась.

Виправлення: Використовуйте CPU manager/topology manager, встановіть явні requests/limits відповідно і пініть пам’ять-інтенсивні поди до NUMA-вузлів.

Чеклісти / покроковий план для нових платформ

Покроковий план: введення нової платформи з чиплетами/стеками в продакшн

  1. Базова топологія: зафіксуйте lscpu і numactl --hardware для SKU; збережіть разом з артефактами збірки.
  2. Стандартизувати прошивку: налаштування BIOS, мікрокод і політики енергії мають бути однаковими по пулу.
  3. Виберіть стандартну енергетичну стратегію по тиру: латентні кластери отримують performance policy; пакетні кластери можуть навмисно бути power-capped.
  4. Запустіть зафіксовані smoke-тести: виміряйте пропускну здатність і p99 із CPU+пам’яттю, прив’язаними до вузла; потім запустіть непіновані; порівняйте дельти.
  5. Перевірте запас пропускної здатності пам’яті: якщо ваше навантаження обмежене пам’яттю — планування ємності означає планування пропускної здатності.
  6. Перевірте локальність IO: перевірте стан PCIe лінків і розподіл IRQ; переконайтесь, що афінітет NIC відповідає розміщенню CPU.
  7. Вирішіть політику розміщення: або прийміть NUMA (прив’язка і локалізація), або явно інтерлейвуйте для пропускної здатності. Не робіть «випадкового гібрида».
  8. Розгорніть з детекцією варіації: слідкуйте не лише за медіанами, а й за розкидом по хостах; алертьте на «один хост дивний» рано.
  9. Документуйте режими відмов: підписи тротлінгу, пороги скоректованих помилок і як ізолювати хост.
  10. Повторно тестуйте після оновлень ядра: зміни планувальника можуть допомогти або зашкодити топологічній обробці; періодично валідуйте.

Чекліст: як вирішувати між стекованим кешем і більшою пропускною здатністю пам’яті

  • Якщо ваш робочий набір трохи більший за LLC і ви бачите багато LLC misses: стекований кеш може дати великий виграш.
  • Якщо пропускна здатність пам’яті близька до максимуму і превалюють стаги: стекований кеш може вас не врятувати; пріоритет — пропускна здатність (HBM платформи, більше каналів) або зменшення трафіку.
  • Якщо хвостова латентність важлива: віддавайте перевагу рішенням, що зменшують варіацію (локальність, стабільна політика потужності), а не чисто піковим показникам.

Чекліст: чого уникати при впровадженні чиплет-орієнтованих CPU

  • Не припускайте «один сокет = однорідність». Вимірюйте NUMA-поведінку.
  • Не приймайте дрейф BIOS у автоскейлінг-групі.
  • Не налаштовуйте додатки, не перевіривши спочатку поведінку потужності і тротлінг.
  • Не змішуйте латентні і пакетні навантаження на одному сокеті без суворої ізоляції.

FAQ

1) Чи завжди чиплети швидші за монолітні кристали?

Ні. Чиплети насамперед — економічна і продуктова стратегія, з вигодами по продуктивності тоді, коли міжз’єднання і топологія управляються добре. Погана локальність може знищити виграш.

2) Чи зробить 3D-стекінг CPU гарячішими?

Часто так, на практиці. Стеки можуть ускладнювати відвід тепла і створювати гарячі точки. Вендори проектують навколо цього, але стійкі навантаження можуть відчувати ранні тротлінг або більшу варіацію.

3) Чи обов’язкове NUMA-тюнінг тепер?

Для latency-чутливих сервісів на чиплетних CPU це майже обов’язково. Для «емоційно паралельних» пакетних задач часто можна обійтися без нього — поки раптом не можна.

4) Які навантаження найбільше виграють від стекованого кешу?

Навантаження з робочим набором, який більший за звичайний кеш, але менший за DRAM-патріотичні стрімінги: hot key-value, деякі аналітичні задачі, певні симуляції і читин-важливі in-memory структури даних.

5) Який операційний ризик більш просунутого пакування?

Більше компонентів і лінків можуть означати більше тонких деградацій: шторм скоректованих помилок, downtrained лінки або варіація платформи. Ваш моніторинг і практики карантину стають важливішими.

6) Чи означає чиплети, що «більше ядер» перестануть допомагати?

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

7) Як HBM змінює планування ємності?

HBM підштовхує до багаторівневої моделі: дуже висока пропускна здатність, але обмежена ємність. Плануйте, що має залишитись в HBM, що може пролитися в DDR і як поводиться ваш аллокатор/рантайм.

8) Чи зробить UCIe пакети CPU модульними як збірка ПК?

З часом вони стануть більш модульними, ніж сьогодні — але не чекайте plug-and-play. Цілісність сигналу, доставка живлення, терміка і валідація усе ще складні, і «стандарт» не усуне фізику.

9) Яка найпростіша «достатня» зміна, щоб зменшити хвостову латентність на чиплетних CPU?

Прив’яжіть найгарячіші потоки до NUMA-вузла і тримайте їхньої пам’ять локально. Потім перевірте за допомогою зафіксованого A/B тесту. Якщо це допомагає — інвестуйте в топологічно-усвідомлене планування.

10) Чи купувати мені SKU зі стекованим кешем для всього?

Ні. Купуйте їх для навантажень, які демонструють чутливість до кешу під час профілювання. Інакше ви платите за кремній, який переважно прикрашає ваш procurement-список.

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

3D-стекінг і чиплети — це не тренд; це форма дороги попереду. CPU стає пакетно-рівневою розподіленою системою з тепловими й топологічними обмеженнями. Ваш софт і операції мають поводитися відповідно.

Що зробити наступного тижня (не в наступному кварталі)

  1. Виберіть один сервіс з SLO по латентності і запустіть pinned vs unpinned NUMA тест (numactl), щоб виміряти чутливість.
  2. Додайте дві панелі на хості: частота CPU/тротлінг (на основі turbostat) і віддалені NUMA-доступи (numastat/PMU, якщо є).
  3. Стандартизуйтe базові налаштування BIOS/мікрокоду для кожного пулу апаратури; алертьте на дрейф.
  4. Напишіть односторінковий runbook, використовуючи Швидку діагностику вище, щоб on-call не звинувачував мережу за звичкою.
  5. Визначте філософію розміщення: локальність-перш за все для латентних тирів; інтерлив/пропускна здатність для throughput-ти грів — і примусьте її виконуватися.

Якщо ви нічого іншого не зробите, зробіть це: припиніть сприймати CPU% як істину. На чиплетах і стекованих дизайнах CPU% — це настрій. Виміряйте локальність, виміряйте пропускну здатність і виміряйте тротлінг. Тоді ви зможете сперечатися впевнено — а це єдина розумна суперечка, на яку може дозволити собі операційний відділ.

Proxmox «неможливо виділити пам’ять»: балонінг, перевиділення і як це налаштувати

Ви натискаєте Start на ВМ, а Proxmox відповідає цифровим еквівалентом зітхання:
«неможливо виділити пам’ять». Або ще гірше — ВМ запускається, а хост починає вбивати випадкові процеси,
ніби стресований театральний суфлер у залі з одним виходом.

Помилки пам’яті в Proxmox не містять містики. Це проблеми звітності: що хост вважає, що у нього є,
що ВМ заявляють, що вони можуть використати, що вони фактично торкаються, і що ядро готове
обіцяти в цей момент. Виправте облік — і більшість драми зникне.

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

Якщо ви на виклику і кластер кричить, вам не потрібна філософська лекція. Потрібен короткий цикл:
підтвердити режим відмови, визначити обмежувач, зробити одну безпечну зміну, повторити.

По-перше: це виснаження пам’яті хоста чи обмеження на ВМ?

  • Якщо ВМ не вдається запустити з повідомленням «неможливо виділити пам’ять», підозрюйте
    ліміти коміту хоста, обмеження cgroup, hugepages або фрагментацію — це часто помітно одразу в dmesg / журналах.
  • Якщо ВМ запускається, а потім її вбивають, зазвичай це OOM killer у гостьовій ОС (всередині ВМ) або
    OOM killer хоста (вбиває QEMU), залежно від того, в яких логах видно труп.

По-друге: перевірте реальний запас хоста, а не гарні графіки

  • Вільна пам’ять хоста і swap: free -h
  • Тиск пам’яті і затримки при звільненні: vmstat 1
  • Докази OOM: journalctl -k та dmesg -T
  • Розмір ZFS ARC (якщо використовуєте ZFS): arcstat / /proc/spl/kstat/zfs/arcstats

По-третє: перевірте політику та виділення зі сторони Proxmox

  • Конфіг ВМ: ціль балона vs max, hugepages, NUMA тощо:
    qm config <vmid>
  • Політика перевиділення на вузлі: pvesh get /nodes/<node>/status та
    /etc/pve/datacenter.cfg
  • Якщо це контейнер (LXC), перевірте обмеження пам’яті cgroup і ліміт swap:
    pct config <ctid>

По-четверте: виберіть найменш шкідливе негайне пом’якшення

  • Зупиніть одну некритичну ВМ, щоб звільнити RAM і знизити тиск прямо зараз.
  • Якщо ZFS «їсть» сервер: обмежте ARC (постійно) або, в крайньому разі, перезавантажте.
  • Якщо ви перевиділяєте: зменшіть max-пам’ять ВМ (не лише ціль балона).
  • Якщо swap відсутній і ви напружені: додайте swap (на хості), щоб уникнути миттєвого OOM, поки ви виправляєте розміри.

Жарт №1: Перевиділення пам’яті — як корпоративний бюджет: все працює, поки всі не спробують одночасно відшкодувати обід.

Що насправді означає «неможливо виділити пам’ять» в Proxmox

Proxmox — це шар управління. Реальний аллокатор — Linux, а для ВМ зазвичай QEMU/KVM. Коли ви бачите
«неможливо виділити пам’ять», відбувається одне з наступного:

  • QEMU не може зарезервувати запитану ВМ оперативну пам’ять під час старту. Це може завершитись невдачею,
    навіть якщо free виглядає нормально, бо Linux дбає про правила коміту і фрагментацію.
  • Ядро відмовляє в алокації через логіку overcommit/CommitLimit. Linux відстежує, скільки пам’яті
    процеси пообіцяли потенційно використати (віртуальна пам’ять), і може відмовляти новим обіцянкам.
  • Запитані hugepages відсутні. Hugepages вирізані заздалегідь. Якщо їх немає, алокація провалюється
    миттєво й голосно.
  • Обмеження cgroup блокують алокацію. Частіше трапляється з контейнерами, але може впливати, якщо залучені systemd slices або кастомні cgroups.
  • Пам’ять є, але не в формі, яку ви просите. Фрагментація може перешкодити великим суміжним виділенням, особливо для hugepages або певних DMA-вимог.

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

Два числа, що мають значення: target гостьової пам’яті і guest max

В опціях ВМ Proxmox балонінг дає вам:

  • Memory (max): стеля ВМ. QEMU виконує облік для неї.
  • Balloon (min/target): ціль під час виконання, яку можна зменшити під тиском.

Якщо ви встановлюєте max на 64 ГіБ «на всяк випадок» і ціль балона на 8 ГіБ «бо зазвичай простаєте», ви сказали хосту:
«Будь ласка, будь готовий фінансувати мій 64 ГіБ стиль життя». Хост, будучи дорослим, може сказати «ні».

Цікавинки та трохи історії (щоб більше не повторювати помилок)

  1. Поведінка Linux щодо overcommit стара і свідома: вона існує, бо багато алокацій ніколи не використовуються повністю,
    і суворий облік витрачав би RAM на порожні обіцянки.
  2. OOM killer з’явився задовго до сучасних віртуалізаційних стеків; це був прагматичний відповідь Linux на «хтось брешe про пам’ять» задовго до того, як маркетинг хмар перетворив брехню на фічу.
  3. Балонінг став поширеним з ранніми гіпервізорами, бо прості гості скуповували кеш і робили консолідацію гіршою, ніж вона мала бути.
  4. KSM (Kernel Samepage Merging) призначений для дедуплікації ідентичних сторінок пам’яті між ВМ — корисно, коли багато ВМ з однаковим образом ОС.
  5. Transparent Huge Pages (THP) були введені для підвищення продуктивності через більші сторінки, але вони можуть створювати спади латентності під тиском пам’яті через роботу з компактування.
  6. ZFS ARC — це не «просто кеш». Він конкурує з анонімною пам’яттю. Якщо ви не обмежите його, він із задоволенням візьме RAM,
    поки ядро не змусить віддати — іноді запізно.
  7. cgroups змінили гру: замість того, щоб весь хост був однією дружною сім’єю, тепер обмеження пам’яті можуть зробити так, що одна ВМ або контейнер впаде, навіть коли хост виглядає нормально.
  8. Swap колись був обов’язковою порадою; потім люди ним зловживали; потім його відкидали; потім сучасні SSD зробили «невеликий контрольований swap» знову розумним у багатьох випадках.

Операційна цитата, що лишається болісно актуальною (парафраз): Вернер Фогельс казав, що суть надійності — очікувати відмов і проектувати під них, а не прикидатися, що їх не буде.

Балонінг: що він робить, чого не робить і чому він вас вводить в оману

Що таке балонінг насправді

Балонінг використовує драйвер всередині гостя (зазвичай virtio-balloon). Хост просить гостя «надути» балон,
тобто: виділити пам’ять всередині гостя й зафіксувати її так, щоб гість не міг її використовувати. Ця пам’ять стає
придатною для звільнення з точки зору хоста, бо гість добровільно її віддав.

Це хитро. Але є обмеження фізики та поведінки гостя:

  • Якщо гість під реальним тиском пам’яті, він не зможе багато віддати без swap або OOM всередині себе.
  • Якщо в гості немає драйвера балона, балонінг — це по суті пантоміма.
  • Балонінг реактивний. Якщо хост уже в біді, може бути запізно.

Важливий нюанс балонінгу в Proxmox

Конфігурація балонінгу в Proxmox часто дає хибне відчуття безпеки. Люди ставлять низькі цілі балона і високі max-пам’яті,
думаючи, що «використовують лише ціль». Але облік QEMU і логіка коміту ядра часто повинні враховувати максимум.

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

Коли балонінг вартий застосування

  • Кластери для розробки/тестування, де гості простаивают, а стрибки рідкісні й терпимі.
  • Флоти VDI-подібних ВМ з багатьма схожими образами, часто разом із KSM.
  • Звичайні серверні флоти, де ви можете забезпечити реалістичні max-значення, а не фантазії.

Коли балонінг — пастка

  • Бази даних з жорсткою затримкою та великими buffer pool — тиск в гості переводиться в IO-тиск.
  • Системи з вимкненим swap всередині гостей (балонінг може викликати OOM у гості).
  • Хости, що вже напружені по пам’яті, де час реакції балона занадто повільний.

Перевиділення: коли це розумно, а коли — ризиковано

Три різні «перевиділення», які плутають люди

На практиці ви балансуєте три шари:

  1. Перевиділення у планувальнику/обліку Proxmox: чи вважає Proxmox, що можна запустити ще одну ВМ
    на основі налаштованої RAM, цілей балона і пам’яті вузла.
  2. Поведінка перевиділення в Linux: vm.overcommit_memory і CommitLimit.
  3. Фактичне фізичне перевиділення: чи перевищує сума активно використовуваної гостями пам’яті RAM хоста
    (і чи є у вас swap, компресія чи план дій).

Коротко про облік коміту в Linux в одній абзаці для операцій

Linux вирішує, чи дозволити алокацію, виходячи з того, скільки пам’яті може бути використано, якщо процеси її торкнуться.
Це число відстежується як Committed_AS. Дозволений поріг — CommitLimit,
приблизно RAM + swap мінус деякі резерви, змінений налаштуваннями overcommit. Якщо Committed_AS наближається
до CommitLimit, ядро починає відмовляти в алокаціях — привіт, «неможливо виділити пам’ять».

Думка щодо практики

  • Продакшен: тримайте перевиділення помірним і забезпечуйте реалістичні max ВМ. Якщо ви не можете вказати
    співвідношення перевиділення і план евакуації, ви не перевиділяєте — ви граєте в азартні ігри.
  • Лабораторія: перевиділяйте агресивно, якщо готові до випадкових OOM. Просто маркуйте це чесно і не прикидайтеся, що це продакшен.
  • Змішані навантаження: або відокремте «шумних» користувачів пам’яті (БД, аналітика) на окремі вузли,
    або ж жорстко їх обмежуйте. «Співіснування» — так називають те, що люди говорять перед інцидентом.

ZFS ARC, page cache і пам’ять хоста, про яку ви забули

Proxmox часто працює на ZFS через зручність снапшотів і send/receive. Але ZFS не соромиться: він використовує RAM
для ARC (Adaptive Replacement Cache). Це добре, поки не стає проблемою.

ARC проти «вільної пам’яті»

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

Що робити

  • На хостах з ZFS і багатьма ВМ встановіть розумний максимум ARC (zfs_arc_max). Не дозволяйте ARC «боротися» з вашими гостями.
  • Розглядайте пам’ять хоста як спільну інфраструктуру. Хост потребує пам’яті для:
    ядра, slab, мережі, метаданих ZFS, оверхеду QEMU та ваших агентів моніторингу, які клянуться бути легкими.

Swap: не гріх, але й не план життя

Відсутність swap означає, що ви забрали амортизатори. У віртуалізації це може бути фатально, бо раптовий сплеск тиску
перетворюється на миттєві OOM-вбивства замість повільного, діагностованого погіршення.

Але swap також може стати перетягуванням продуктивності. Мета — контрольований swap: достатньо, щоб пережити сплески,
але не стільки, щоб приховати хронічне перевиділення.

Рекомендації щодо swap на хості (практично, не догма)

  • Якщо ви працюєте на ZFS і маєте багато ВМ: додайте swap. Навіть помірна кількість може запобігти вбиванню QEMU під час коротких сплесків.
  • Якщо ваше сховище повільне: тримайте swap меншим і віддавайте пріоритет правильному розміруванню RAM. Свап на повільних HDD — це не «стабільність», а «тривале страждання».
  • Якщо ви використовуєте SSD/NVMe: swap значно більш терпимий, але все одно не безкоштовний. Моніторте швидкість swap-in/out, а не тільки використання swap.

Жарт №2: Swap — це як зустріч, яка могла бути листом — іноді рятує день, але якщо ви там живете, ваша кар’єра закінчена.

Практичні завдання: команди, виводи та рішення (12+)

Ось перевірки, які я реально запускаю, коли вузол Proxmox починає кидати помилки алокації пам’яті. Кожне завдання містить:
команду, приклад виводу, що це означає і яке рішення воно визначає.

Завдання 1: Перевірити RAM і swap хоста на око

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            62Gi        54Gi       1.2Gi       2.3Gi       6.9Gi       2.8Gi
Swap:            8Gi       1.6Gi       6.4Gi

Значення: «available» — ваш ближній запас перед тим, як звільнення стане некрасивим. 2.8 ГіБ на хості 62 ГіБ
з віртуалізацією — тісно, але не миттєво катастрофічно.

Рішення: Якщо available < 1–2 ГіБ і ВМ не стартують, зупиніть некритичні ВМ. Якщо swap = 0, додайте swap як стабілізатор, поки виправляєте розміри.

Завдання 2: Визначити, чи ядро відхиляє алокації через CommitLimit

cr0x@server:~$ grep -E 'CommitLimit|Committed_AS' /proc/meminfo
CommitLimit:    71303168 kB
Committed_AS:   70598240 kB

Значення: Ви близькі до стелі коміту. Ядро може відмовляти новим резервуванням пам’яті навіть якщо є кеш, який можна звільнити.

Рішення: Зменшити max-пам’ять ВМ, додати swap (збільшує CommitLimit) або перемістити навантаження.
Зміна цілей балона не допоможе, якщо проблема в max.

Завдання 3: Підтвердити політику overcommit

cr0x@server:~$ sysctl vm.overcommit_memory vm.overcommit_ratio
vm.overcommit_memory = 0
vm.overcommit_ratio = 50

Значення: Режим 0 — евристичне перевиділення. Ratio важливий здебільшого для режиму 2. Все одно, поведінка коміту в дії.

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

Завдання 4: Шукати докази роботи OOM killer на хості

cr0x@server:~$ journalctl -k -b | tail -n 30
Dec 26 10:14:03 pve1 kernel: Out of memory: Killed process 21433 (qemu-system-x86) total-vm:28751400kB, anon-rss:23110248kB, file-rss:0kB, shmem-rss:0kB
Dec 26 10:14:03 pve1 kernel: oom_reaper: reaped process 21433 (qemu-system-x86), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Значення: Хост вбив QEMU. Та ВМ не «поїхала», її виконали.

Рішення: Розглядайте як виснаження пам’яті хоста/перевиділення. Зменшіть консолідацію, обмежте ARC, додайте swap
і не покладайтеся на балонінг як ремінь безпеки.

Завдання 5: Перевірити тиск пам’яті та поведінку звільнення в реальному часі

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
 6  1 1677720 312000  8200 5120000  40  120   180   260  900 1800 18 12 55 15  0
 5  2 1677800 280000  8100 5010000  10  200   140   320  920 1700 15 10 50 25  0
 7  3 1677850 260000  8000 4920000  80  500   220   600 1100 2200 20 15 35 30  0

Значення: Ненульові si/so вказують на свапінг. Високий wa — IO wait.
Якщо b росте, а id падає, хост трясеться (thrashing).

Рішення: Якщо свапінг тривалий і IO wait підіймається, зупиніть ВМ або перемістіть навантаження.
Ви не зможете «відтюнити» себе з бурі thrash у реальному часі.

Завдання 6: Знайти найбільших споживачів пам’яті на хості (RSS, не VIRT-мрії)

cr0x@server:~$ ps -eo pid,comm,rss,vsz --sort=-rss | head -n 10
 21433 qemu-system-x86 23110248 28751400
 19877 qemu-system-x86 16188012 21045740
  1652 pveproxy          312400  824000
  1321 pvedaemon         210880  693000
  1799 zfs               180200  0
  1544 pvestatd          122000  610000

Значення: RSS — це реальна резидентна пам’ять. QEMU-процеси домінують, як і очікувалось.

Рішення: Якщо одна ВМ розженена, обмежте її max-пам’ять або розслідуйте всередині гостя.
Якщо «багато середніх» ВМ навантажують — це математика консолідації, а не один негідник.

Завдання 7: Подивитись конфіг ВМ (балон vs max)

cr0x@server:~$ qm config 104 | egrep 'memory|balloon|numa|hugepages'
memory: 32768
balloon: 8192
numa: 1
hugepages: 2

Значення: Max — 32 ГіБ, ціль балона 8 ГіБ. Hugepages увімкнено (2 = 2MB hugepages).

Рішення: Якщо вузол не вдається виділити пам’ять, max цієї ВМ у 32 ГіБ може бути надто щедрим.
Якщо hugepages увімкнено, підтвердіть їх наявність (Завдання 8) або вимкніть для гнучкості.

Завдання 8: Перевірити наявність hugepages (класична причина помилок старту)

cr0x@server:~$ grep -i huge /proc/meminfo
AnonHugePages:   1048576 kB
HugePages_Total:    8192
HugePages_Free:      120
HugePages_Rsvd:       50
Hugepagesize:       2048 kB

Значення: Вільних лише 120 hugepages (~240 МіБ). Якщо ви спробуєте стартувати ВМ, що потребує багато hugepages, вона впаде.

Рішення: Або забезпечте достатньо hugepages при завантаженні, або припиніть використовувати hugepages для цього класу ВМ.
Hugepages — інструмент продуктивності, а не налаштування за замовчуванням.

Завдання 9: Перевірити поведінку THP (може викликати латентність при тиску)

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

Значення: THP увімкнено завжди.

Рішення: Для вузлів, чутливих до латентності, розгляньте madvise або never.
Не змінюйте це під час інциденту, якщо ви не впевнені; плануйте в maintenance-вікні і вимірюйте.

Завдання 10: Якщо використовуєте ZFS — швидко перевірити розмір ARC

cr0x@server:~$ awk '/^size/ {print}' /proc/spl/kstat/zfs/arcstats
size                            4    34359738368

Значення: ARC ≈ 32 ГіБ. На хості 64 ГіБ з багатьма ВМ це може бути забагато.

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

Завдання 11: Підтвердити статус KSM (допомагає при багатьох схожих ВМ, але коштує CPU)

cr0x@server:~$ systemctl is-active ksmtuned
inactive

Значення: Сервіс налаштування KSM не працює. На деяких Proxmox-настройках KSM налаштовано інакше;
це швидкий індикатор.

Рішення: Якщо ви запускаєте десятки схожих Linux-ВМ, увімкнення KSM може зменшити використання пам’яті.
Якщо CPU вже гарячий, KSM може зашкодити. Тестуйте на одному вузлі спочатку.

Завдання 12: Перевірити інформацію про пам’ять вузла в Proxmox (що Proxmox думає)

cr0x@server:~$ pvesh get /nodes/pve1/status | egrep '"memory"|"swap"|"loadavg"'
"loadavg": [
  "2.61",
  "2.45",
  "2.31"
],
"memory": {
  "free": 1288490188,
  "total": 66571993088,
  "used": 651834
},
"swap": {
  "free": 6871947673,
  "total": 8589934592,
  "used": 1717986919
}

Значення: API Proxmox дає вигляд, який може відрізнятися від ваших очікувань (одиниці, кешування, час).
Не вважайте його абсолютною істинною; перевіряйте free і meminfo.

Рішення: Використовуйте це для автоматики та дашбордів, але при налагодженні відмов алокації довіряйте доказам ядра і логам QEMU перш за все.

Завдання 13: Проаналізувати помилку старту ВМ у логах завдань

cr0x@server:~$ journalctl -u pvedaemon -b | tail -n 20
Dec 26 10:18:11 pve1 pvedaemon[1321]: start VM 104: UPID:pve1:0000A3F9:00B2B6D1:676D5A13:qmstart:104:root@pam:
Dec 26 10:18:12 pve1 pvedaemon[1321]: VM 104 qmp command failed - unable to execute QMP command 'cont': Cannot allocate memory
Dec 26 10:18:12 pve1 pvedaemon[1321]: start failed: command '/usr/bin/kvm -id 104 ...' failed: exit code 1

Значення: Помилка відбувається на стадії старту/cont QEMU, не всередині гостя.

Рішення: Сфокусуйтеся на лімітах коміту хоста, hugepages і фрагментації — не на тонкому тюнінгу гостя.

Завдання 14: Перевірити конфіг контейнера (LXC) та ліміт swap

cr0x@server:~$ pct config 210 | egrep 'memory|swap|features'
memory: 4096
swap: 512
features: nesting=1,keyctl=1

Значення: Контейнер має 4 ГіБ RAM і 512 МіБ swap. Якщо він стрибне вище, алокації всередині контейнера проваляться.

Рішення: Для контейнерів «неможливо виділити пам’ять» часто означає досягнення ліміту cgroup.
Збільшуйте memory/swap або виправляйте поведінку додатка. Вільна RAM хоста не врятує LXC з жорсткою межею.

Завдання 15: Перевірити сигнали ризику фрагментації (швидко і грубо)

cr0x@server:~$ cat /proc/buddyinfo | head
Node 0, zone      DMA      1      1      1      1      0      0      0      0      0      0      0
Node 0, zone    DMA32   1024    512    220     12      0      0      0      0      0      0      0
Node 0, zone   Normal   2048   1880    940    110      2      0      0      0      0      0      0

Значення: Buddy allocator показує, скільки вільних блоків є в різних порядках. Якщо вищі порядки майже нуль,
великі суміжні виділення (включно з потребами hugepage) можуть провалюватись навіть при «достатній загальній вільній пам’яті».

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

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

Інцидент: хибне припущення («балонінг означає, що max не резервується»)

Середня компанія тримала внутрішній Proxmox-кластер для бізнесових додатків і кількох важких пакетних задач.
Команда мала звичку: ставити високу max-пам’ять ВМ «щоб ніхто не писав тикет», а ціль балона — низьку,
щоб «підтримувати ефективність».

Це працювало — поки вони не оновили декілька ВМ і не запустили квартальний звіт. Нові процеси породилися, карти пам’яті
розширилися, і кілька ВМ перезапустилися для патчів. Раптом: «неможливо виділити пам’ять» при старті ВМ.
Дашборд усе ще показував «вільно», бо кеш здався відновлюваним.

Корінь проблеми — не витік. Це був облік. Committed_AS хоста повільно підбирався до CommitLimit.
Кожна ВМ з щедрим max додавала до суми «пообіцяної» пам’яті, навіть якщо «зазвичай» сиділа на низькому рівні. Коли декілька
рестартів відбулися одночасно, QEMU спробував зарезервувати те, що йому казали, що може знадобитись. Ядро відмовило.
Помилка була правдивою; їхня ментальна модель — ні.

Виправлення було нудним: вони зменшили max-пам’ять ВМ до обґрунтованих значень, зберегли балонінг для еластичності
і додали swap на хости, де його не було. Найголовніше — вони перестали ставитись до «max» як до бажання.
Наступний квартальний прогін ще підскакував, але перестав ламати рестарти.

Оптимізація, що відпалила (hugepages усюди)

Інша організація гналася за затримкою. Інженер з продуктивності увімкнув hugepages для цілого класу ВМ, бо блог казав,
що це покращить TLB-поведінку. І справді може. Вони також залишили Transparent Huge Pages у положенні «always»,
бо більше hugepages звучало як більше продуктивності. Так оптимізм перетворився на конфігурацію.

Тижнями все виглядало нормально. Потім один вузол почав падати при старті ВМ після рутинних міграцій. Та сама ВМ на інших вузлах стартувала.
На цьому вузлі: «неможливо виділити пам’ять». Вільної пам’яті не було катастрофічно мало, але free hugepages майже не було.
Buddyinfo показав фрагментацію: пам’ять була, але не в потрібних шматках.

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

План відновлення: вимкнути hugepages для загальних ВМ, резервувати hugepages лише для невеликого набору критичних випадків із передбачуваним розміром,
і встановити THP в madvise. Загальна продуктивність покращилася, бо система припинила битися сама з собою.

Скучно, але вірно: резерв хоста і ліміти врятували ситуацію

Третя команда тримала Proxmox для змішаних навантажень: веб-додатки, кілька Windows-ВМ і пара сховищних appliance.
У них була нудна правило: кожен вузол тримає фіксований «резерв хоста» RAM, який ніколи не виділяється гостям офіційно.
Вони також обмежили ZFS ARC з першого дня.

Це не було красиво. Це означало, що вони могли запускати менше ВМ на вузол, ніж хотіли фанатики таблиць.
Але під час інциденту, коли шумна ВМ раптово почала споживати пам’ять (невірно налаштований Java-сервіс), хост мав достатньо запасу,
щоб тримати процеси QEMU живими і уникнути OOM хоста.

Гість все одно постраждав (як і має бути), але зона ураження залишилася всередині тієї ВМ. Кластер не почав вбивати сторонні навантаження.
Вони злили вузол, виправили конфіг гістьової, і відновили роботу. Ніяких північних перезавантажень, ніяких каскадних відмов, ніяких «чому впала наш фаєрвол-ВМ?»

Практика, що врятувала їх, не була секретним тюнінгом ядра. Це було бюджетування і відмова витрачати аварійний фонд.

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

ВМ не стартує: «Неможливо виділити пам’ять» відразу

  • Симптом: Старт падає миттєво; QEMU виходить з помилкою алокації.
  • Причина: Досягнутий CommitLimit хоста, відсутні hugepages або фрагментація пам’яті для запитаного виділення.
  • Виправлення: Зменшити max-пам’ять ВМ; додати swap на хості; вимкнути hugepages для цієї ВМ; забезпечити hugepages при завантаженні, якщо вони потрібні.

ВМ стартує, а потім раптово вимикається або скидається

  • Симптом: ВМ ніби «крушиться», в логах немає чистого завершення.
  • Причина: OOM killer хоста вбив QEMU, часто після стрибка пам’яті або важкого reclaim.
  • Виправлення: Знайти OOM-логи; зменшити перевиділення хоста; резервувати пам’ять хоста; обмежити ZFS ARC; переконатися, що swap існує і моніторити його активність.

Гості спочатку повільні, потім хост повільнішає, потім всі починають філософствувати

  • Симптом: IO wait піднімається; швидкості swap-in/out зростають; латентність ВМ підскакує.
  • Причина: Thrashing: недостатньо RAM для робочих наборів, і свап/reclaim домінує.
  • Виправлення: Зупинити або мігрувати ВМ; зменшити ліміти пам’яті; додати RAM; переглянути план консолідації. Жоден sysctl вас тут не врятує.

Балонінг увімкнено, але пам’ять ніколи не «повертається»

  • Симптом: Хост залишається заповненим; гості не звільняють пам’ять, як очікувалось.
  • Причина: Драйвер балона не встановлено/не працює, гість не може звільнити пам’ять, або «max» все ще зобов’язує хост.
  • Виправлення: Встановити драйвер virtio-balloon; перевірити всередині гостя; встановити реалістичний max; використовувати балонінг для еластичності, а не як заміну розміру.

Все було гаразд, поки не збільшилися ZFS snapshots і реплікація

  • Симптом: Тиск пам’яті хоста зростає під час інтенсивної роботи зі сховищем; старти ВМ падають.
  • Причина: Зростання ARC, тиск метаданих, нарощування slab і IO-керована пам’ять.
  • Виправлення: Обмежити ARC; моніторити slab; тримати запас; уникати роботи вузла на 95% «використано» і називати це ефективністю.

Контейнери показують «неможливо виділити пам’ять», хоча хост має запас

  • Симптом: Додатки в LXC не можуть виділити пам’ять; хост виглядає нормально.
  • Причина: Досягнутий ліміт пам’яті cgroup (memory/swap контейнера).
  • Виправлення: Підвищити ліміти контейнера; налаштувати додаток; дозволити swap контейнера, якщо ви очікуєте сплесків.

Контрольні списки / покроковий план

Покроково: виправити вузол, який кидає помилки алокації

  1. Підтвердити OOM хоста проти помилки при старті.
    Перевірте journalctl -k на OOM-вбивства і логи pvedaemon для контексту помилки старту.
  2. Виміряти тиск коміту.
    Якщо Committed_AS близький до CommitLimit, ви в території «обіцянки перевищують реальність».
  3. Перелічити ВМ з великим max-пам’яттю.
    Зменшіть max-пам’ять для винних. Не обмежуйтеся зміною лише цілей балона.
  4. Перевірити hugepages і налаштування THP.
    Якщо hugepages увімкнено для ВМ, переконайтеся в достатній препризначеності або вимкніть для загальних навантажень.
  5. Перевірити ZFS ARC, якщо це стосується.
    Якщо ARC великий і ви хост для ВМ перш за все, обмежте його.
  6. Переконатися, що swap існує і він адекватний.
    Додайте swap, якщо його нема; моніторте si/so. Swap — для сплесків, а не для щоденної оренди.
  7. Резервувати пам’ять хоста.
    Тримайте фіксований буфер для ядра + ZFS + оверхед QEMU. Ваше майбутнє «я» дякуватиме мовчки.
  8. Повторно тестуйте запуск ВМ у контрольованій послідовності.
    Не запускайте все одразу після налаштувань. Стартуйте критичні служби першими.

Постійне налаштування: обмеження ZFS ARC (приклад)

Якщо вузол — це хост ВМ і ZFS — лише засіб, встановіть максимум ARC. Один поширений метод:
створити конфіг modprobe і оновити initramfs, щоб застосувати при завантаженні.

cr0x@server:~$ echo "options zfs zfs_arc_max=17179869184" | sudo tee /etc/modprobe.d/zfs.conf
options zfs zfs_arc_max=17179869184
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.12-4-pve

Значення: ARC обмежено до 16 ГіБ (значення в байтах). Ви сказали ZFS не їсти всю машину.

Рішення: Виберіть обмеження, що залишає достатньо RAM для гостей плюс резерв хоста. Підтвердіть після перезавантаження, читаючи arcstats.

Постійне налаштування: додати swap на хості (приклад файлу)

cr0x@server:~$ sudo fallocate -l 8G /swapfile
cr0x@server:~$ sudo chmod 600 /swapfile
cr0x@server:~$ sudo mkswap /swapfile
Setting up swapspace version 1, size = 8 GiB (8589930496 bytes)
no label, UUID=0a3b1e4c-2f1e-4f65-a3da-b8c6e3f3a8d7
cr0x@server:~$ sudo swapon /swapfile
cr0x@server:~$ swapon --show
NAME      TYPE SIZE USED PRIO
/swapfile file   8G   0B   -2

Значення: Swap активний. CommitLimit зростає, і у вас є буфер проти раптових сплесків алокацій.

Рішення: Якщо використання swap стає постійним разом з високими si/so, це не «працює як задумано».
Це сигнал зменшити консолідацію або додати RAM.

Політика: резервувати RAM для хоста (проста і дієва практика)

  • Резервуйте принаймні 10–20% RAM хоста для хоста на змішаних вузлах.
    Більше — якщо ви запускаєте ZFS, Ceph, потужні мережі або багато малих ВМ.
  • Тримайте «цільову суму max ВМ», яку можете відстояти. Якщо сума max ВМ перевищує заданий коефіцієнт від RAM хоста,
    робіть це свідомо й лише там, де поведінка навантаження це підтримує.

Контрольний список для балонінгу (використовуйте його правильно)

  • Увімкніть балонінг лише якщо гість має підтримку virtio-balloon.
  • Встановіть max-пам’ять близько до реальності; ціль балона може бути нижчою для простою.
  • Моніторте guest swap і гостеві OOM події після увімкнення балонінгу.
  • Не застосовуйте балонінг до баз даних, якщо ви не приймаєте IO-стрибки і непередбачувану латентність.
  • Поширені питання

    1) Чому Proxmox каже «неможливо виділити пам’ять», коли free показує ГБ вільних?

    Тому що free показує знімок фізичної пам’яті, тоді як облік коміту ядра і правила фрагментації можуть відмовити новій алокації.
    Також «free» ігнорує, чи доступна пам’ять у потрібній формі (наприклад, hugepages).

    2) Чи зменшує балонінг те, що хост повинен резервувати?

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

    3) Чи варто ставити vm.overcommit_memory=1, щоб зупинити відмови алокацій?

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

    4) Скільки swap має бути на хості Proxmox?

    Достатньо, щоб пережити сплески і підвищити CommitLimit, але не настільки, щоб приховати хронічне перевиділення.
    Зазвичай: кілька ГБ до низьких десятків ГБ залежно від RAM хоста і волатильності навантаження. Міряйте активність swap; якщо вона постійна, ви недорозміровані.

    5) Чи є ZFS ARC причиною, чому мій вузол «залипає без пам’яті»?

    Іноді. ARC може рости і конкурувати з ВМ. Якщо старти ВМ провалюються або хост OOM, коли ARC великий,
    обмежте ARC. Якщо ARC невеликий, дивіться інші причини (комітліміт, hugepages, розгінні ВМ).

    6) Чи варто увімкнути KSM на Proxmox?

    Якщо ви запускаєте багато схожих ВМ (той самий ОС, схожі сторінки пам’яті), KSM може зекономити RAM.
    Це коштує CPU і може додати латентності. Увімкніть свідомо, вимірюйте вплив на CPU і не вважайте це безкоштовною пам’яттю.

    7) Чому контейнери б’ються «неможливо виділити пам’ять», якщо хост в порядку?

    LXC керується cgroups. Контейнер може досягти ліміту всередині свого простору, навіть якщо хост має запас.
    Підніміть ліміти pct memory/swap або виправте навантаження контейнера.

    8) Чи варті hugepages того?

    Для певних високопродуктивних, чутливих до латентності навантажень — так. Для загальної консолідації — часто ні.
    Hugepages підвищують передбачуваність TLB, але знижують гнучкість і можуть створювати проблеми запуску, якщо їх не забезпечити.

    9) У чому різниця між гостевим OOM і OOM хоста?

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

    10) Чи можна «постійно вирішити» це без додавання RAM?

    Часто так: встановіть реалістичні max-пам’яті ВМ, резервуйте RAM для хоста, обмежте ARC за потреби і уникайте фантастичних співвідношень перевиділення.
    Якщо робочі набори реально перевищують фізичну RAM, постійне рішення — більше RAM або менше навантажень на вузол.

    Наступні кроки (розумні)

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

    1. Припиніть ставитись до max-пам’яті ВМ як до пропозиції. Зробіть її контрактом.
    2. Використовуйте балонінг для еластичності, а не як заперечення. Ціль — низька, стеля — реалістична.
    3. Дайте хосту аварійний фонд. Резервуйте RAM; додайте swap; тримайте ZFS ARC у своїй смузі.
    4. Віддавайте перевагу передбачуваним вузлам над героїчним тюнінгом. Розділяйте навантаження, коли їх режими відмов різняться.
    5. Опрацюйте це як процес. Додайте алерти на близькість до CommitLimit, швидкість swap-in/out, OOM-логи і розмір ARC.

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

    PostgreSQL проти SQLite на VPS: найшвидший вибір без жалю

    Ви працюєте на VPS. Потрібна «база даних». Не проєкт на вихідні й не стадо яків. Щось, що не розбудить вас о 03:00 через застряглий файл або тому, що ваш застосунок раптово отримав реальний трафік і ваш «простий» вибір перетворився на болісну міграцію.

    Найшвидший спосіб обрати між PostgreSQL і SQLite — припинити сперечатися про фічі й поставити одне жорстке питання: де межа вашої конкурентності та відмовостійкості? Якщо вона всередині одного процесу — SQLite як скальпель. Якщо вона розкидана між багатьма процесами, користувачами, джобами й з’єднаннями — PostgreSQL ваш нудний, перевірений часом гайковий ключ.

    Рішення за хвилину

    Якщо ви прочитаєте тільки цей розділ — все одно зробите пристойний вибір.

    Обирайте SQLite, якщо всі ці твердження вірні

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

    Обирайте PostgreSQL, якщо хоча б одне з цих тверджень вірне

    • У вас є декілька записувачів, кілька інстансів застосунку, cron-завдань, воркерів, аналітичні запити, адмін-інструменти… все, що поводиться як невелике натовп.
    • Вам потрібна стійка конкурентність, щоб не перетворювати застосунок на координатор блокувань.
    • Ви дбаєте про ізоляцію, гарантії довговічності та відновлення при реальних відмовах.
    • Вам потрібні онлайн-зміни схеми, багаті індекси та плани запитів, що масштабується за межі «милого» застосунку.
    • Ви передбачаєте зростання і вважаєте за краще масштабуватися через додавання CPU/RAM зараз і реплік пізніше, а не робити ризикову міграцію під тиском.

    Правило великого пальця: якщо БД має посередничати між людським нетерпінням (веб-трафік) і машинним нетерпінням (джоби), PostgreSQL — дорослий у кімнаті.

    Жарт #1: SQLite як велосипед: швидкий, елегантний і ідеальний, поки ви не спробуєте перевезти ним диван.

    Ментальна модель, що рятує від жалю

    Більшість дебатів «Postgres vs SQLite» вмирають, бо люди порівнюють SQL-синтаксис або чеклісти фіч. Вибір справді про операційну форму: хто звертається до бази, як часто і що відбувається, коли щось іде не так.

    SQLite: бібліотека з файлом, а не сервер

    SQLite працює в процесі. Немає серверного демонa, що приймає з’єднання. Ваш застосунок лінкує бібліотеку; «база даних» — це файл (плюс опціональні файли журналювання/WAL). Це означає:

    • Латентність може бути чудовою, бо немає мережевого хопу. Виклики — це виклики функцій.
    • Конкурентність обмежена блокуванням файлу. Читання — ок. Записи вимагають координації; WAL покращує це, але не робить усе довільно паралельним.
    • Довговічність залежить від семантики файлової системи, опцій монтування і ваших налаштувань synchronous. Це не «небезпечно», це «ви несете відповідальність за гострі краї».
    • Резервні копії — це копії файлу, що можуть бути дивовижно простими — поки ви не зробите їх у невдалий момент без використання backup API SQLite.

    PostgreSQL: сервер з процесами, пам’яттю та своїми поглядами

    PostgreSQL працює як сервер бази даних зі своїми процесами, кешами, write-ahead log (WAL), фоновим vacuum та чіткими транзакційними семантиками. Це означає:

    • Висока конкурентність з MVCC (multi-version concurrency control): читачі не блокують записувачів так, як це відбувається при файлових блоках.
    • Довговічність і відновлення після крашу — основа системи. Потрібно налаштувати й тестувати, але система створена для поганих днів.
    • Є операційне навантаження: оновлення, бекупи, моніторинг, vacuum і управління з’єднаннями.
    • Шляхи масштабування ясніші: реплікація, read replicas, партиціювання, пулери з’єднань та зріла екосистема інструментів.

    Питання межі

    Запитайте: «Чи є база даних межою спільного сервісу?» Якщо так — PostgreSQL. Якщо ні — SQLite може бути легітимною production-базою. Не недооцінюйте, як часто «ні» тихо перетворюється на «так», коли додаєте воркер, потім другий інстанс застосунку, потім адмін-дашборд з важкими запитами.

    Цікаві факти та трохи історії

    Трохи контексту допомагає, бо архітектурні вибори не були довільними. Це шрами від реального використання.

    1. SQLite з’явився в 2000 році як вбудована база, щоб уникнути оверхеду клієнт/серверних СУБД для конкретного проєкту; згодом став дефолтним «малим SQL» двигуном у світі.
    2. PostgreSQL має корені в 1980-х (проєкт POSTGRES в UC Berkeley), і його ДНК це показує: розширюваність, коректність і академічна одержимість транзакційною поведінкою.
    3. SQLite, ймовірно, найбільш розгорнута СУБД, бо вона постачається в телефонах, браузерах, ОС і безлічі додатків як бібліотека.
    4. PostgreSQL популяризував багату розширюваність через користувацькі типи, оператори та розширення; тому він став дефолтною платформою «SQL плюс» у багатьох сучасних стеків.
    5. WAL режим SQLite був доданий пізніше, щоб зменшити блокування записувачів і покращити конкурентність; це змінило уявлення про те, для чого підходить SQLite в production.
    6. MVCC у PostgreSQL означає, що старі версії рядків залишаються до очищення vacuum; це і фіча продуктивності, і операційний клопіт.
    7. SQLite відомий суворою портативністю файлу бази між архітектурами і версіями, але все одно залежить від поведінки файлової системи щодо довговічності.
    8. WAL у PostgreSQL теж називають WAL (той самий акронім, інша реалізація), і він лежить в основі реплікації та відновлення до точки у часі.
    9. Повідомлення «database is locked» у SQLite — не баг; це очевидний наслідок моделі блокувань. Баг — ваша думка, що він поводиться як серверна СУБД.

    Реалії VPS: диски, пам’ять і сусіди

    VPS — це не ноутбук і не керована база. Це невеликий шматок великої машини з розділеним IO і іноді непередбачуваними сусідами. Ваш вибір бази має це враховувати.

    Дисковий IO — перша неправда в ваших бенчмарках

    На VPS ваш «SSD» може бути швидким, а може бути «швидким, коли сусіди сплять». І SQLite, і PostgreSQL піклуються про поведінку fsync, але відчувають її по-різному:

    • SQLite пише в один файл бази (плюс журнал/WAL). Випадкові записи можуть бути болючими, якщо навантаження інтенсивне.
    • PostgreSQL пише в кілька файлів: дані й WAL-сегменти. WAL-записи більш послідовні і можуть бути лагідніші до реальних дисків, але при цьому з’являються фонові процеси і контрольні точки.

    Пам’ять — це не просто «кеш»; це політика

    SQLite сильно покладається на page cache ОС. Це нормально — Linux добре кешує. PostgreSQL має власні shared_buffers плюс кеш ОС. Якщо ви неправильно налаштуєте це на маленькому VPS, отримаєте подвійний кеш і позбавите систему ресурсів.

    Модель процесів важлива при малій RAM

    SQLite живе в процесі вашого застосунку. PostgreSQL використовує кілька процесів і має витрати пам’яті на з’єднання. На VPS з 1 GB RAM купа простих з’єднань може стати помилкою продуктивності, а не дрібницею. Якщо ви запускаєте Postgres на малому залізі, ви навчитесь любити connection pooling.

    Операційний радіус ураження

    Радіус ураження SQLite часто — «цей файл». Радіус ураження PostgreSQL — «цей кластер», але з кращими інструментами для ізоляції та відновлення. SQLite можна відновити, скопіювавши файл — якщо ви не скопіювали його в неправильний момент. PostgreSQL можна відновити, відігнавши WAL — якщо ви не тестували backup-и. Оберіть свій яд, а потім пом’якште його наслідки.

    Практичні завдання: команди, виводи, рішення (12+)

    Нижче завдання, які ви можете виконати на VPS сьогодні. Кожне дає сигнал, а не відчуття. Мета — вирішувати на основі доказів: можливостей IO, потреб у конкурентності та ризиків відмови.

    Завдання 1: Перевірте CPU та тиск пам’яті (чи вам взагалі дозволено запускати Postgres?)

    cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)'
    CPU(s):                               2
    Model name:                           Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz
    
    cr0x@server:~$ free -h
                   total        used        free      shared  buff/cache   available
    Mem:           1.0Gi       220Mi       180Mi        12Mi       620Mi       690Mi
    Swap:          1.0Gi         0B       1.0Gi
    

    Що це означає: На 1 GB RAM Postgres можливий, але потрібно дисципліновано підходити (пулінг з’єднань, тюнінг пам’яті). SQLite буде відчуватися беззусильним.

    Рішення: Якщо ви не можете дозволити собі кількасот МБ для Postgres плюс запас для застосунку, оберіть SQLite або апгрейд VPS.

    Завдання 2: Визначте тип сховища і опції монтування (довговічність тут)

    cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
    /dev/vda1 ext4 rw,relatime,errors=remount-ro
    

    Що це означає: ext4 з relatime — нормальна конфігурація. Якщо бачите дивні опції, наприклад data=writeback або екзотичні мережеві FS, слід ставитися з підозрою до заяв про довговічність SQLite і тюнити Postgres відповідно.

    Рішення: Якщо ви на мережевому або дивному сховищі, Postgres з протестованою поведінкою WAL+fsync зазвичай безпечніший, ніж «копіювати файл бази».

    Завдання 3: Швидка перевірка латентності диска (майбутній квиток «бд повільна»)

    cr0x@server:~$ iostat -xz 1 3
    Linux 6.2.0 (server) 	12/30/2025 	_x86_64_	(2 CPU)
    
    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
               3.10    0.00    1.20    0.40    0.10   95.20
    
    Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
    vda              5.00    8.00   80.0   210.0   2.10   0.40   0.52
    

    Що це означає: await в одиницях низьких цифр — прийнятно. Якщо бачите спайки 20–100ms, і SQLite, і Postgres постраждають, але в SQLite це виявиться як затримки в потоках застосунку.

    Рішення: Високий IO wait підштовхує до використання Postgres з тюнінгом контрольних точок і, можливо, переходу на краще сховище; також це аргумент зменшити write amplification у будь-якому випадку.

    Завдання 4: Виміряйте вартість синхронізації файлової системи (за це платять і SQLite, і Postgres)

    cr0x@server:~$ sudo dd if=/dev/zero of=/var/tmp/fsync.test bs=4k count=25000 conv=fdatasync status=progress
    102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.4 MB/s
    25000+0 records in
    25000+0 records out
    102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.3 MB/s
    

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

    Рішення: Якщо примусовий sync дорогий, SQLite потребує WAL + розумних synchronous налаштувань; Postgres потребує тюнінгу checkpoint-ів і не варто надто агресивно виставляти synchronous_commit для не критичних записів.

    Завдання 5: Перевірте ліміти відкритих файлів (Postgres це більше зачепить)

    cr0x@server:~$ ulimit -n
    1024
    

    Що це означає: 1024 — це тісно для Postgres під навантаженням з багатьма з’єднаннями й файлами. SQLite це зачепить менше, але ваш застосунок може.

    Рішення: Якщо обираєте Postgres — підніміть ліміти через systemd або limits.conf; якщо не можете, зберігайте мало з’єднань і використовуйте пулер.

    Завдання 6: Подивіться на кількість живих з’єднань (якщо вже натовп — SQLite стане гострим)

    cr0x@server:~$ sudo ss -tanp | awk '$4 ~ /:5432$/ {c++} END {print c+0}'
    0
    

    Що це означає: Зараз Postgres відсутній, але важлива сама картина: скільки одночасних клієнтів буде.

    Рішення: Якщо ви очікуєте десятки/сотні одночасних з’єднань — Postgres з пулером виграє. SQLite не має «з’єднань» в тому сенсі; там є «потоки й процеси, що борються за файл».

    Завдання 7: Створіть SQLite базу з WAL і перевірте pragmas (зробіть її менш крихкою)

    cr0x@server:~$ sqlite3 /var/lib/myapp/app.db 'PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA wal_autocheckpoint=1000;'
    wal
    

    Що це означає: WAL режим увімкнено; synchronous NORMAL — поширений компроміс (достатньо довговічності для багатьох застосунків, менше IO-болю ніж FULL).

    Рішення: Якщо ви обираєте SQLite — будьте явні щодо pragmas. За замовчуванням це не «production-політика», а «загальні бібліотечні налаштування».

    Завдання 8: Симулюйте конкурентні записи в SQLite (впізнайте стіну блокувань раніше)

    cr0x@server:~$ for i in $(seq 1 20); do (sqlite3 /var/lib/myapp/app.db "BEGIN IMMEDIATE; CREATE TABLE IF NOT EXISTS t(x); INSERT INTO t VALUES($i); COMMIT;" >/dev/null 2>&1 &); done; wait; echo done
    done
    

    Що це означає: Це грубий тест. Якщо при повторі з більшим контентіоном ви почнете бачити «database is locked» в stderr — це ваша сирена тривоги.

    Рішення: Якщо реальне навантаження схоже на це (багато записів) — перестаньте романтизувати SQLite і використайте Postgres.

    Завдання 9: Встановіть Postgres і перевірте стан сервісу

    cr0x@server:~$ sudo apt-get update -qq
    ...output...
    
    cr0x@server:~$ sudo apt-get install -y postgresql
    ...output...
    
    cr0x@server:~$ sudo systemctl status postgresql --no-pager
    ● postgresql.service - PostgreSQL RDBMS
         Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
         Active: active (exited)
    

    Що це означає: На Debian/Ubuntu обгортка сервісу може показувати «active (exited)», тоді як кластерні юніти працюють. Не панікуйте; перевірте кластер.

    Рішення: Якщо ви не можете тримати сервіс здоровим на VPS (права, диск повний, тиск пам’яті) — SQLite може бути здоровішим вибором, поки не стабілізуєте хост.

    Завдання 10: Перевірте готовність кластера Postgres

    cr0x@server:~$ pg_lsclusters
    Ver Cluster Port Status Owner    Data directory              Log file
    16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
    

    Що це означає: Він онлайн. Є директорія даних і шлях до логу — дві речі, які ви навчитесь поважати.

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

    Завдання 11: Перевірте налаштування довговічності та контрольних точок Postgres (не летіть навмання)

    cr0x@server:~$ sudo -u postgres psql -c "SHOW synchronous_commit; SHOW fsync; SHOW full_page_writes; SHOW checkpoint_timeout; SHOW max_wal_size;"
     synchronous_commit
    -------------------
     on
    (1 row)
    
     fsync
    -------
     on
    (1 row)
    
     full_page_writes
    ------------------
     on
    (1 row)
    
     checkpoint_timeout
    --------------------
     5min
    (1 row)
    
     max_wal_size
    --------------
     1GB
    (1 row)
    

    Що це означає: За замовчуванням налаштування консервативні. Вони орієнтовані на безпеку на загальному апаратному забезпеченні, а не на ваш конкретний VPS.

    Рішення: Якщо вам потрібна висока пропускна здатність записів — тюньте checkpoint-и й max_wal_size. Якщо потрібна максимальна безпека — тримайте консервативні значення і інвестуйте в резервні копії та тестування.

    Завдання 12: Виявіть тиск vacuum (операційний «податок» Postgres)

    cr0x@server:~$ sudo -u postgres psql -c "SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
     relname | n_dead_tup
    ---------+------------
    (0 rows)
    

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

    Рішення: Якщо ви обираєте Postgres — потрібно моніторити vacuum/bloat. Якщо ви не можете зобов’язатися до цього, простота SQLite починає виглядати привабливо — за умови, що конкурентність підходить.

    Завдання 13: Виявіть повільні запити в Postgres (вирішіть, чи потрібні індекси або інша БД)

    cr0x@server:~$ sudo -u postgres psql -c "SHOW shared_preload_libraries;"
     shared_preload_libraries
    --------------------------
    (1 row)
    

    Що це означає: Якщо pg_stat_statements не увімкнено, ви втрачаєте ключову лінзу у поведінці запитів.

    Рішення: Якщо ви працюєте далі за межі іграшкового трафіку — увімкніть статистику запитів і сприймайте її як продукційну телеметрію. У SQLite є опції, але в Postgres такий аналіз став рутинним.

    Завдання 14: Оцініть розмір БД і зростання (файл SQLite vs кластер Postgres)

    cr0x@server:~$ du -sh /var/lib/myapp/app.db
    48M	/var/lib/myapp/app.db
    
    cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_size_pretty(pg_database_size(current_database()));"
     pg_size_pretty
    ----------------
     7289 kB
    (1 row)
    

    Що це означає: SQLite — це один файл; Postgres — дерево директорій плюс WAL. Шляхи зростання відрізняються: файл SQLite росте і може не зменшуватися; Postgres може мати блоут, якщо не виконувати vacuum.

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

    Завдання 15: Протестуйте workflow резервного копіювання/відновлення (це вирішує, чи спатимете ви)

    cr0x@server:~$ sqlite3 /var/lib/myapp/app.db ".backup '/var/backups/app.db.bak'"
    
    cr0x@server:~$ ls -lh /var/backups/app.db.bak
    -rw-r--r-- 1 root root 48M Dec 30 03:12 /var/backups/app.db.bak
    
    cr0x@server:~$ sudo -u postgres pg_dump -Fc -f /var/backups/pg.dump postgres
    
    cr0x@server:~$ ls -lh /var/backups/pg.dump
    -rw-r--r-- 1 postgres postgres 36K Dec 30 03:13 /var/backups/pg.dump
    

    Що це означає: Обидві можна бекапити. Ключ — консистентність і тестування відновлення. SQLite потребує правильного методу бекапу; Postgres потребує відпрацювання прав доступу та ресторів.

    Рішення: Якщо ви не можете і не будете тестувати відновлення — не обирайте жодну систему, бо ви не обираєте базу даних, а обираєте майбутній інцидент.

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

    Це послідовність при «щось повільно». Мета — ізолювати вузьке місце за хвилини, а не сперечатися в Slack годинами.

    Перше: це CPU, пам’ять чи диск?

    cr0x@server:~$ uptime
     03:20:11 up 12 days,  2:41,  1 user,  load average: 0.22, 0.40, 0.35
    
    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
     1  0      0 184320  28000 635000    0    0    10    25  120  180  3  1 95  1  0
     0  0      0 183900  28000 635200    0    0     0     0  110  170  2  1 97  0  0
    

    Інтерпретація: Високий wa означає очікування диска; високий si/so — свапінг; високий r при низькому idle — тиск CPU.

    Дія: Якщо хост свопить — спочатку вирішіть пам’ять (зменшіть з’єднання, тюньте Postgres, додайте RAM). Якщо IO wait високий — дивіться на checkpoint-и, вартість fsync і моделі записів.

    Друге: база заблокована чи заблокована процесами?

    SQLite: шукайте помилки блокувань в логах застосунку; перевірте довгі транзакції.

    Postgres: перевірте блокуючі лока.

    cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, wait_event_type, wait_event, state, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY pid;"
     pid  | wait_event_type | wait_event | state  | query
    ------+-----------------+------------+--------+-------
    (0 rows)
    

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

    Третє: це проблема запиту чи проблема ємності?

    Для Postgres ідентифікуйте повільні запити і робіть EXPLAIN. Для SQLite перегляньте шаблони доступу й індекси та розгляньте винесення важких запитів поза гарячий шлях.

    cr0x@server:~$ sudo -u postgres psql -c "EXPLAIN (ANALYZE, BUFFERS) SELECT 1;"
                                          QUERY PLAN
    --------------------------------------------------------------------------------------
     Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.003..0.004 rows=1 loops=1)
     Planning Time: 0.020 ms
     Execution Time: 0.010 ms
    (3 rows)
    

    Інтерпретація: У реальній роботі шукайте послідовні скани по великих таблицях, величезні буферні хіти або час, витрачений на очікування IO.

    Дія: Якщо запити повільні через відсутні індекси — виправляйте схему. Якщо повільно через повільний диск — змінюйте сховище або зменшуйте churn записів. Якщо повільно через конкурентність — виправляйте пулінг або обирайте правильну БД.

    Поширені помилки (симптом → корінь → виправлення)

    Це не моральні провали. Це передбачувані наслідки ставитися до бази як до чорної скриньки.

    1) Поява «database is locked» спорадично (SQLite)

    Симптоми: Помилки застосунку під навантаженням, спайки під час фонових завдань, запити падають і потім проходять при повторі.

    Корінь: Декілька записувачів або довгі транзакції, що утримують write-лок. WAL допомагає, але один записувач усе одно потребує часу.

    Виправлення: Увімкніть WAL; тримайте транзакції короткими; серіалізуйте записи через чергу; додайте busy_timeout; або переходьте на Postgres, якщо потрібні конкурентні записи.

    2) SQLite здається швидким, поки ви не розгорнете кілька інстансів

    Симптоми: Працює в dev, ненадійно в prod; продуктивність падає лише після горизонтального масштабування.

    Корінь: Блокування файлу між процесами стає точкою конкуренції. Також: спільні файлові системи — пастка.

    Виправлення: Не кладіть SQLite на NFS. Якщо вам потрібно більше ніж один процес-писач — використайте Postgres.

    3) Postgres «повільний», але CPU простоює

    Симптоми: Висока латентність, низький CPU, періодичні паузи.

    Корінь: IO wait під час checkpoint-ів або fsync-важкий робочий набір; max_wal_size замале; погане сховище.

    Виправлення: Збільшіть max_wal_size; тюньте checkpoint-и; перемістіть WAL на швидший диск, якщо можливо; обережно зменшіть synchronous налаштування для не критичних шляхів.

    4) Postgres падає через багато з’єднань на малому VPS

    Симптоми: Спайки пам’яті, OOM kills, «too many clients», випадкові тайм-аути.

    Корінь: Модель «одне з’єднання на запит»; витрати пам’яті на з’єднання; відсутній пулінг.

    Виправлення: Використайте PgBouncer; зменшіть max_connections; виставте розумний розмір пулу; виправте застосунок, щоб повторно використовувати з’єднання.

    5) Резервні копії є, але відновлення не працює

    Симптоми: Тест відновлення провалюється; права зіпсовані; бракує ролей; файл резервної копії SQLite пошкоджено або неконсистентний.

    Корінь: Резервні копії зроблені неправильно (копія файлу SQLite під час запису) або не тестуються (дампи Postgres без глобалів/ролей).

    Виправлення: Для SQLite використовуйте .backup або backup API; для Postgres проводьте drills з відновлення, включаючи ролі та схему; автоматизуйте перевірку.

    6) Таблиці Postgres блоять і запити деградують за тижні

    Симптоми: Використання диску росте швидше за дані; індекси роздуваються; запити повільні; vacuum працює постійно.

    Корінь: MVCC мертві кортежі накопичуються; autovacuum не встигає; агресивні UPDATE/DELETE патерни.

    Виправлення: Тюньте autovacuum для таблиць; уникайте hot updates де можливо; розгляньте партиціювання або періодичне технічне обслуговування.

    7) Файл SQLite роздувається і ніколи не зменшується

    Симптоми: Використання диску росте, навіть після видалень; VPS починає бракувати місця.

    Корінь: SQLite переиспользує сторінки, але не завжди повертає простір файловій системі; фрагментація; великі видалення.

    Виправлення: Періодичний VACUUM (дорогий); продумайте стратегію збереження; розгляньте розбиття великих таблиць або перехід на Postgres при інтенсивному churn.

    8) «Ми вибрали Postgres бо це enterprise» і тепер ops тоне

    Симптоми: Ніхто не відповідає за оновлення, vacuum, бекапи; БД стала «пет», а не «кеттл».

    Корінь: Вибір Postgres без виділення операційної зрілості.

    Виправлення: Або інвестуйте в базові операції (моніторинг, drills відновлення, план оновлень), або тримайте простіше зі SQLite, поки дійсно не потрібен серверний DB.

    Три корпоративні міні-історії

    Міні-історія 1: Інцидент, спричинений неправильною припущенням (файл SQLite на «спільному сховищі»)

    Компанія була середнього розміру, продукт був здоровий, і хтось придумав блискучу ідею: запустити два інстанси застосунку за балансувальником «для стійкості». База була SQLite, лежала на тому, що провайдер VPS рекламував як «спільне сховище», змонтоване в обох інстансах. Здавалося елегантно. Один файл. Два інстанси. Що може піти не так?

    Декілька днів працювало. Потім перший підйом трафіку — нічого драматичного, просто маркетингова розсилка. Запити почали накопичуватися. Латентність зросла. Дехто отримував помилки; дехто читав застарілі дані; кілька користувачів бачили дивні часткові оновлення, що зникали при оновленні сторінки.

    На виклику інженер знайшов переривчасті «database is locked» у логах, але не систематично. Гірше — були випадкові повідомлення «disk I/O error», що виглядали як апаратні проблеми. Але це не було апаратне. Це була файлова система і менеджер блокувань, які сперечались, хто володіє правдою між двома вузлами.

    Неправильне припущення було тонким: «якщо сховище спільне, то блокування файлу теж спільні». На багатьох спільних файлових системах advisory locks не працюють як локальні ext4-локи, особливо при відмовах або затримках. SQLite не «зламався»; середовище порушило припущення, на яких воно базується, щоб забезпечити ACID.

    Виправлення було нудним: спочатку перевели на Postgres на одному вузлі, потім додали репліку. Також прибрали спільний монт і стали трактувати межі сховища як межі відмов. Звіт про інцидент не звинувачував SQLite; він вказував на архітектуру, яка робила вигляд, що файл може бути розподіленою системою.

    Міні-історія 2: Оптимізація, що відбилась боком (Postgres затюнено для швидкості, заплатила тривогою про втрату даних)

    Інша організація мала Postgres на малому VPS. Записи були інтенсивними: події, логи, лічильники. Команда хотіла зменшити латентність і прочитала блог про відключення деяких механізмів довговічності. Вони змінили налаштування, щоб зменшити fsync-навантаження і зробити коміти швидшими. Усі вітали. Графіки пішли вниз і праворуч.

    Через два тижні VPS хост мав незапланований ребут. Нічого драматичного — просто «обслуговування вузла», про яке довідались після факту. Postgres перезапустився, але шматок найсвіжіших записів зник. Не катастрофа, але достатньо, щоб викликати питання від клієнтів і внутрішній тривожний дзвінок.

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

    Оптимізація відбилась боком, бо оптимізувала не те: стабільну латентність за рахунок передбачуваної довговічності. Є обґрунтовані причини послабити довговічність для еpheremal аналітики або кешів. Але вони використовували це для стану, що впливає на клієнтів.

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

    Міні-історія 3: Нудна, але правильна практика, що врятувала день (drills резервного відновлення та автоматика)

    Це менш драматично, і в цьому суть. Команда, що працювала SaaS на одному VPS, використовувала Postgres. Вони не були витончені. У них не було платформи. Але вони робили одне ритуально: щотижневі drills відновлення на тестовій VM з чеклістом.

    У них був скрипт, який тягнув останній бекап, відновлював його, запускав невеликий набір sanity-запитів і підтверджував, що застосунок може запуститись проти нього. Також зберігався мінімальний runbook про те, як підняти відновлену БД, якщо первинна помре. Ніхто не любив це робити. Це як чистити зуби.

    Потім розробник випадково виконав руйнівну міграцію в продакшн. Не навмисно. Просто помилковий environment variable і інструмент міграцій, що охоче послухався. Таблиці були видалені. On-call приглушив алерти, тихо поклявся і почав drill відновлення, який вони багато разів практикували.

    У них все ще була погана година, але не поганий тиждень. Вони відновили, коректно прогнали міграції й відіграли вузьке вікно бізнес-подій з логів. CEO ніколи не дізнався, що таке «WAL», і це найвищий комплімент, який можна отримати від операцій.

    Цитата (парафраз): «Ви не піднімаєтесь до виклику; ви відкатуєтесь до підготовки.» — парафраз ідеї, що часто цитують у надійності/ops колах

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

    Чекліст A: Якщо ви схиляєтесь до SQLite (зробіть його production-ready)

    1. Підтвердіть реальність single-writer: перерахуйте всі шляхи, що виконують записи (веб-запити, воркери, cron, адмін-скрипти). Якщо більше одного актора одночасно — плануйте серіалізацію або міграцію.
    2. Використайте WAL режим: встановіть PRAGMA journal_mode=WAL.
    3. Поставте розумне synchronous: зазвичай NORMAL — хороший компроміс для VPS; використовуйте FULL, якщо не можете терпіти втрату нещодавніх записів при краші.
    4. Встановіть busy_timeout: нехай застосунок трохи зачекає, а не впаде одразу при контенції блокувань.
    5. Резервуйте правильно: використовуйте механізм backup SQLite, а не «cp файлу під час піку записів».
    6. Плануйте зростання файлу: моніторте розмір БД і вільне місце; плануйте періодичний VACUUM тільки якщо потрібно.
    7. Не кладіть SQLite на NFS/спільні томи: тільки локальний диск, якщо не хочете розбирати блокування через затримки.

    Чекліст B: Якщо ви схиляєтесь до PostgreSQL (зробіть його нудним, стабільним і дешевим)

    1. Правильно розмірюйте з’єднання: тримайте max_connections розумним; використовуйте пулер для веб-застосунків.
    2. Налаштуйте пам’ять свідомо: тюньте shared_buffers консервативно на малій RAM; залиште запас для ОС-кешу та вашого застосунку.
    3. Увімкніть видимість запитів: вмикайте статистику запитів, щоб бачити, що повільне, раніше ніж користувачі почнуть скаржитись.
    4. Моніторте vacuum: слідкуйте за мертвими кортежами та активністю autovacuum; блоут — повільна течія.
    5. Резервні копії і тести відновлення: автоматизуйте обидва процеси. Бекап без відновлення — бажання.
    6. План оновлень: вирішіть, як ви будете обробляти мажорні та мінорні оновлення до того, як опинитесь під тиском.
    7. Керування диском: моніторьте простір для даних і WAL; уникайте роботи на 90% заповненні VPS.

    Покроково: шлях без жалю (15 хвилин)

    1. Запустіть Завдання 1–4, щоб зрозуміти реалії RAM і IO.
    2. Перелічіть ваших писачів. Якщо більше одного конкурентного писача існує зараз або скоро — обирайте Postgres.
    3. Якщо SQLite все ще можливий — виконайте Завдання 7–8. Якщо під час іграшкового тесту з’являється контенція блокувань — обирайте Postgres.
    4. Якщо обираєте Postgres — запустіть Завдання 9–12 і підтвердіть, що можете тримати його здоровим на цьому VPS.
    5. Запустіть Завдання 15 і проведіть хоча б одне відновлення. Обирайте систему, шлях відновлення якої ви зможете реально виконати під стресом.

    Жарт #2: Найшвидша база — це та, яку ви не загубили о 03:00, саме тому бекапи дають найкращий ROI з усіх фіч, які ви ніколи не покажете на демо.

    Запитання й відповіді (FAQ)

    1) Чи витримає SQLite production-трафік?

    Так, якщо «production-трафік» означає переважно читання, невелику кількість записів і контрольовану модель конкурентності. Його використовують у багатьох реальних системах. Він просто не хоче бути вашим мульти-тенантним координатором записів.

    2) Чи робить WAL режим SQLite «таким самим, як Postgres»?

    Ні. WAL зменшує блокування читач/запис і покращує конкурентність, але все одно у вас один файл бази з семантикою блокування і менше інструментів для конкуренції. Postgres спроєктований як сервіс для спільного використання.

    3) Чи є Postgres overkill для малого VPS?

    Іноді. Якщо VPS дуже крихітний і навантаження просте, Postgres може додати зайвих рухомих частин. Але якщо у вас декілька писачів або є траєкторія зростання, «overkill» швидко перетворюється на «дякую, що не змушували мене мігрувати в стресі».

    4) Який найбільший прихований витратний фактор Postgres на VPS?

    Управління з’єднаннями та пам’яттю. Без пулінгу і розумних лімітів Postgres може спалювати RAM на простаках і падати так, ніби це «випадкова нестабільність». Це не випадковість; це математика.

    5) Який найбільший прихований витратний фактор SQLite на VPS?

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

    6) Якщо я почну з SQLite, наскільки болісна міграція на Postgres?

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

    7) Чи варто використовувати SQLite для кешу, а Postgres як джерело істини?

    Може працювати, але не будьте випадково розподіленою системою. Якщо потрібен кеш — розгляньте in-memory кеши або Postgres-native стратегії. Якщо використовуєте SQLite як локальний кеш — тримайте його як витратний і відновлювальний.

    8) Щодо довговічності: чи небезпечний SQLite?

    SQLite може бути довговічним при правильній конфігурації і використанні на файловій системі, що дотримує його очікувань. Ризик не в «SQLite небезпечний», а в «SQLite робить легкою можливість бути небезпечним, не помічаючи цього». Postgres централізує поведінку довговічності в сервері, створеному для крашів.

    9) Чи потрібна реплікація на VPS?

    Не завжди. Для багатьох VPS-налаштувань перший виграш — надійні бекапи й drills відновлення. Реплікація корисна, коли у вас вимоги до uptime, що перевищують «відновити в межах X хвилин», і ви можете дозволити собі складність.

    10) Як вирішити, чи у мого застосунку «декілька писачів»?

    Якщо записи можуть виконуватись одночасно з більш ніж одного OS-процесу або контейнера (веб-воркери, job-воркери, заплановані таски, адмін-скрипти) — у вас кілька писачів. Якщо ви розгортаєте кілька інстансів застосунку — точно так.

    Наступні кроки, які можна зробити сьогодні

    Оберіть шлях і зробіть його операційно реальним. Бази не виходять з ладу через неправильний бренд; вони виходять з ладу, бо ви не підібрали систему під навантаження і не практикували відновлення.

    Якщо ви обираєте SQLite

    • Увімкніть WAL і явне synchronous налаштування.
    • Додайте busy timeout і тримайте транзакції короткими.
    • Реалізуйте бекапи через backup-механізм SQLite і проведіть тест відновлення.
    • Запишіть жорстке правило: «без спільних файлових систем, без мульти-писачів».

    Якщо ви обираєте PostgreSQL

    • Налаштуйте розумний connection pooling і ліміти негайно.
    • Увімкніть видимість запитів і слідкуйте за повільними запитами й блокуваннями.
    • Автоматизуйте бекапи і періодично виконуйте drills відновлення.
    • Моніторьте використання диску і стан vacuum до того, як це стане проблемою.

    Версія без жалю — це не про вибір «найкращої» бази. Це про вибір бази, моделі відмов якої ви можете передбачити, спостерігати і відновлювати з людським ритмом на VPS.

    Pentium 4 / NetBurst: найгучніша помилка ери ГГц

    Якщо ви керували продакшн-системами на початку 2000-х, напевно пам’ятаєте відчуття: купили «більше ГГц»,
    графіки не покращилися, а пейджер — так. Затримки лишилися грубими, пропускна здатність не зросла, а вентилятори
    навчилися кричати по-новому.

    NetBurst (мікроархітектура Pentium 4) — це приклад того, що стається, коли маркетинг і проєктування апаратної
    частини надто тісно потисли руки. Не те щоб інженери були безпорадні. Проблема в тому, що обмеження були жорсткими,
    ставка — вузькою, а реальний світ відмовився співпрацювати.

    Теза: ГГц були проксі, а не продуктом

    NetBurst створювали під частоту. Не під «якісну частоту», не під «ефективну частоту», а під «поставте число на коробку
    і нехай світ сперечається пізніше». Intel роками привчав покупців інтерпретувати тактову частоту як продуктивність.
    Ринок винагороджував таке спрощення. А потім прийшли рахунки: надто глибокі конвеєри інструкцій, через які помилки передбачення
    обходилися дорого, підсистема пам’яті, що не поспішала за ядром, і щільність потужності, яка перетворила дизайн шафи в хобі для
    фанатів ОВК.

    Це не була одна банальна помилка проєкту. Це був набір компромісів, які всі спиралися на одне припущення:
    частота буде рости, а програмне забезпечення йтиме в ногу. Коли хоча б одне припущення провалилося — розгалужений код,
    пам’яттєво важкі навантаження, реальні обмеження дата-центру — підхід провалився.

    Якщо перекласти це на SRE-мову: NetBurst оптимізували під пік у ідеальних мікробенчмарках і карали хвостову латентність
    під реальним сумішевим навантаженням. Так ви можете доставити багато розчарування.

    Один раз я бачив, як слайд закупівель виставляв «3.0 GHz» ніби це SLA пропускної здатності.
    Це як оцінювати мережу, рахуючи літери в «Ethernet».

    Внутрішня будова NetBurst: конвеєр, який з’їв ваш IPC

    Глибокі конвеєри: добре для частоти, погано для помилок

    Класична історія NetBurst — «дуже глибокий конвеєр». Практична історія — «велика штрафна санкція за невірне передбачення».
    Глибший конвеєр допомагає досягти вищих тактів, бо кожна стадія робить менше роботи. Мінус у тому, що ви збільшили відстань
    між «ми здогадалися про гілку» і «з’ясували, що помилилися». Коли помилка трапляється, ви скидаєте багато роботи, що була в дорозі,
    і починаєте заново.

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

    Trace cache: хитро, складно і чутливо до навантаження

    Trace cache у NetBurst зберігав декодовані мікрооперації (uops), а не сирі x86-інструкції. Це було розумно: декодування x86 —
    нетривіальна справа, і кеш uop може зменшити навантаження на фронт-енд. Але це також зробило продуктивність залежною від потоку коду
    і вирівнювання. Якщо ваш стрім інструкцій не «грає ввічливо» — багато гілок, дивне вирівнювання, погана локальність — trace cache
    переставав бути благом і ставав ще одним місцем для промаху.

    Ідея не була хибною; вона була ранньою й крихкою. Сьогодні uop-кеші успішні тому, що решта системи стала краще їх підгодовувати,
    а компроміси потужність/продуктивність менеджаться з більшим тактом.

    FSB і спільний northbridge: пункт пропуску пропускної здатності

    Системи Pentium 4 залежали від front-side bus (FSB) до окремого контролера пам’яті (northbridge). Це означає, що ваше CPU-ядро швидке,
    а пам’ять — «деінде», і кожен запит — подорож через спільну шину. Під навантаженням ця шина перетворюється на задачу планування.
    Додайте кілька CPU — і це стає груповим проєктом.

    Порівняйте це з пізнішими дизайнами з інтегрованими контролерами пам’яті (AMD зробив це раніше на x86; Intel пізніше).
    Коли пам’ять ближче й має більше виділених шляхів, ви зменшуєте конкуренцію й знижуєте латентність. В продакшні латентність — валюта.
    NetBurst витрачав її як турист.

    SSE2/SSE3 ера: сильний у потоковій математиці, нерівний в інших задачах

    NetBurst непогано справлявся з деякими векторизованими, потоковими навантаженнями — кодом, що міг передбачувано обробляти масиви
    і уникати розгалуженої логіки. Ось чому бенчмарки могли виглядати нормально, якщо їх будували під тип навантаження, який машина любила.
    Але реальні сервіси не ввічливі. Вони парсять, розгалужуються, виділяють пам’ять, блокуються й чекають на I/O.

    NetBurst був еквівалентом двигуна, налаштованого під певний трек. Посадіть його в міський рух — і ви дізнаєтеся, що таке «крива крутного моменту».

    Чому реальні навантаження болять: кеші, гілки, пам’ять і очікування

    IPC — те, що ви відчуваєте; ГГц — те, чим хвалитеся

    Інструкцій за такт (IPC) — грубий, але корисний проксі для «скільки роботи виконується за тик». NetBurst часто мав нижчий IPC,
    ніж contemporaries у багатьох загальноцільових навантаженнях. Тому чіп працював на вищій частоті, щоб компенсувати.
    Це може працювати — поки не перестає, тому що:

    • Розгалужений код викликає помилки передбачення, які дорожчі в глибоких конвеєрах.
    • Промахи кешу зупиняють виконання, а швидке ядро просто раніше доходить до паузи.
    • FSB/латентність пам’яті стає жорсткою стіною, яку частотою не переломиш.
    • Енергетика/терміки примушують троттлити, тож обіцяні ГГц — скоріше бажання.

    Помилки передбачення гілок: податок латентності, який ви платите постійно

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

    Стіна пам’яті: коли ядро випереджає систему

    NetBurst міг швидко виконувати, якщо його підгодовували, але багато навантажень обмежені пам’яттю. Промах кешу — сотні тактів очікування.
    Це число — не моральний провал; це фізика плюс топологія. Практичний ефект — CPU з вищими ГГц може виглядати гірше,
    якщо він частіше натрапляє на затори пам’яті або не вміє їх приховувати.

    З точки зору оператора це проявляється так: високе завантаження CPU, посередня пропускна здатність і система, яка здається «застряглою»
    без очевидної насиченості I/O. Вона не застрягла. Вона чекає на пам’ять і б’ється сама з собою.

    Спекулятивне виконання: корисне, але підсилює вартість помилок

    Спекуляція — це спосіб, яким сучасні CPU отримують продуктивність: вгадати шлях, виконати його, викинути якщо помилилися.
    У глибокому конвеєрі неправильний шлях дорогий. Ставка NetBurst була в тому, що кращі такти компенсують це. Іноді так і було.
    Часто — ні.

    Одна з найпростіших операційних інструкцій із епохи NetBurst: не приймайте «CPU завантажено на 95%» за «CPU виконує 95% корисної роботи».
    Потрібні лічильники, а не відчуття.

    Терміки та енергоспоживання: коли процесор торгується з фізикою

    Щільність потужності стала функцією продукту (випадково)

    NetBurst грівся. Особливо пізні моделі на базі Prescott, які стали легендарними через споживання і нагрівання. Тепло — це не лише рахунок за електрику;
    це ризик надійності, шум вентиляторів і змінність продуктивності.

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

    Троттлінг за температурою: невидимий гальмо

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

    Жарт №1: Prescott не був обігрівачем, але сидіти поруч зі стійкою в холодну пору стало трохи приємніше.

    Надійність і експлуатація: гарячі системи старіють швидше

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

    Парафраз відомої ідеї, часто приписуваної В. Едвардсу Демінгу, чітко підходить для опсів: «Ви не можете керувати тим, чого не вимірюєте».
    З NetBurst вам доводилося вимірювати терміки, бо CPU це робив точно.

    Hyper-Threading: хороший трюк, що виявив погані припущення

    Hyper-Threading (SMT) з’явився на деяких моделях Pentium 4 і був реально корисний за відповідних умов:
    він міг заповнювати «прогалини» в конвеєрі, запускаючи інший потік, коли один застряг. Це звучить як безкоштовна продуктивність,
    і іноді так воно й було.

    Коли допомагало

    • Змішані навантаження, де один потік чекає на промахи кешу, а інший може використати виконавчі блоки.
    • Сервіси з інтенсивним I/O, де потік часто блокується, і накладні витрати планувальника керовані.
    • Деякі ролі серверів, орієнтовані на пропускну здатність, з незалежними запитами і обмеженою конкуренцією за блокування.

    Коли шкодило

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

    Hyper-Threading на NetBurst — це мікрокосм правила: SMT робить добрі дизайни кращими, а крихкі — дивнішими.
    Воно може підвищити пропускну здатність і водночас зробити латентність потворною. Якщо ваше SLO — p99, не «увімкніть і моліться».
    Тестуйте з production-подібною конкуренцією і контролюйте хвіст.

    Історичні факти, які мають значення (і кілька, що ще болять)

    1. NetBurst дебютував з Willamette (Pentium 4, 2000), віддаючи пріоритет частоті над IPC.
    2. Northwood покращив ефективність і такти, і став «менш болючим» Pentium 4 для багатьох покупців.
    3. Prescott (2004) перейшов на тонший техпроцес, додав функції й став відомим через нагрів і енергоспоживання.
    4. «Гонка ГГц» настільки вплинула на рішення про покупку, що «вища частота» часто перемагає кращу архітектуру в комерційних бесідах.
    5. Доступ до пам’яті через FSB означав, що CPU конкурував за пропускну здатність по спільній шині до northbridge.
    6. Trace cache зберігав декодовані мікрооперації, намагаючись зменшити накладні витрати декоду і постачати довгий конвеєр ефективно.
    7. Hyper-Threading з’явився на вибраних моделях і міг підвищити пропускну здатність, використовуючи проста клітинка виконання.
    8. Pentium M (з родоводу P6) часто перевершував Pentium 4 при значно нижчих тактах, особливо в реальних задачах.
    9. Intel зрештою змінив курс від NetBurst; лінійка Core (на іншому фундаменті) замінила стратегію замість нескінченного її розвитку.

    Три корпоративні міні-історії з передової

    Міні-історія 1: інцидент через хибне припущення («ГГц = ємність»)

    Середня компанія успадкувала парк застарілих веб-серверів і планувала швидке оновлення. Критерії вибору були надто прості: беріть
    найбільш високочастотні Pentium 4 в межах бюджету. Нотатка закупівель буквально прирівнювала «+20% частоти» до «+20% запитів в секунду».
    Ніхто не був злий умисно; вони були зайняті.

    Розгортання пройшло гладко, поки трафік не досяг звичного піка. Завантаження CPU виглядало нормально — високо, але стабільно.
    Мережа була під контролем. Диски не кричали. Але p95 латентність піднялася, потім p99 пішов вертикально. Команда on-call зробила те, що й роблять команди:
    перезапустили сервіси, переклали трафік, звинуватили балансувальник і дивилися на графіки, поки графіки не стали дивитися назад.

    Справжня проблема була в поведінці пам’яті. Навантаження змінилося з роками: більше персоналізації, більше логіки шаблонів, більше динамічної маршрутизації.
    Це означало більше «pointer-chasing» і розгалужень. Нові сервери мали вищі частоти, але схожу латентність пам’яті і спільну топологію FSB, що ставала гіршою під конкуренцією.
    Вони швидше доходили до тих самих пам’яттєвих пауз, а Hyper-Threading додав конкуренцію в найгірший момент.

    Виправлення було не в «тонкій настройці Linux». Потрібно було перевизначити базову ємність, використовуючи тести, схожі на продакшн:
    реальна конкуренція, фази з теплим/холодним кешем і хвостова латентність як ключова метрика. Компанія змінила склад парку:
    менше «швидко-частотних» коробок, більше збалансованих вузлів з кращими підсистемами пам’яті. Вони також перестали використовувати ГГц
    як основне число ємності. Дива трапляються, коли перестаєш собі брехати.

    Міні-історія 2: оптимізація, що відкотилася («увімкнемо HT і отримаємо безкоштовну продуктивність»)

    Інша команда запускала Java-сервіс з багатьма короткоживучими запитами. Вони увімкнули Hyper-Threading на всьому парку
    і подвоїли потоки робітників, очікуючи лінійного зростання пропускної здатності. На синтетичних тестах це виглядало чудово. Потім прийшли інциденти:
    спорадичні стрибки латентності, GC-паузи, що синхронізувалися із сплесками трафіку, і новий тип «повільно, але нічого не на межі».

    Система не була голодною за CPU; вона була голодною за кешем і блокуваннями. Два логічні CPU ділили виконувальні ресурси і, що важливіше,
    кеш і шляхи пропускної здатності пам’яті. Патерни алокацій і синхронізації JVM створювали «скакання» кеш-ліній, а додаткова конкуренція
    підсилювала контенцію в гарячих точках, які раніше виглядали нешкідливо.

    Вони намагалися виправити це, збільшуючи heap, потім прив’язуючи потоки, потім крутячи різні системні ручки. Деякі речі допомогли, більшість — ні.
    Справжній виграш прийшов від кроку назад: ставитися до Hyper-Threading як до інструмента пропускної здатності з витратами на латентність.
    Виміряйте витрати.

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

    Міні-історія 3: нудна, але правильна практика, що врятувала день («термальний запас — це ємність»)

    Команда фінансових сервісів запускала обчислювально-важкі нічні завдання на кластері, який включав вузли епохи Prescott Pentium 4.
    Ніхто не любив ті коробки, але завдання були стабільні, і кластер «був достатнім». Тиха суперсила команди — вони розглядали середовище як частину ємності:
    моніторинг температури впускного повітря, перевірки стану вентиляторів і оповіщення про індикатори троттлінгу.

    Одного літа охолоджувальний блок деградував на вихідних. Не повний аут — просто поганий режим. В понеділок тривалість завдань повільно піднялася.
    Більшість команд звинуватили би планувальник або базу даних. Ця команда помітила тонку кореляцію: вузли в одному ряду показували трохи вищі
    температури і трохи нижчу ефективну частоту CPU.

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

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

    Практичні завдання: 12+ команд для діагностики «швидкий CPU, повільна система»

    Це виконувано на типовому сервері Linux. Мета не «довести, що NetBurst поганий» у 2026 році.
    Ви вчитеся розпізнавати ті самі режими відмов: зупинки в конвеєрі, стіна пам’яті, артефакти планування, термальне троттлінг
    і оманлива завантаженість.

    Завдання 1: Визначте CPU і чи є HT

    cr0x@server:~$ lscpu
    Architecture:            x86_64
    CPU op-mode(s):          32-bit, 64-bit
    CPU(s):                  2
    Thread(s) per core:      2
    Core(s) per socket:      1
    Socket(s):               1
    Model name:              Intel(R) Pentium(R) 4 CPU 3.00GHz
    Flags:                   fpu vme de pse tsc ... ht ... sse2

    Що це значить: «Thread(s) per core: 2» вказує на Hyper-Threading. Model name дає родину CPU.

    Рішення: Якщо HT є, тестуйте з HT увімкненим/вимкненим для latency-чутливих сервісів; не вважайте, що це виграш.

    Завдання 2: Перевірте поточну частоту і драйвер масштабування

    cr0x@server:~$ grep -E 'model name|cpu MHz' /proc/cpuinfo | head
    model name	: Intel(R) Pentium(R) 4 CPU 3.00GHz
    cpu MHz		: 2793.000

    Що це значить: CPU не на номінальній частоті. Може бути енергозбереження або троттлінг.

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

    Завдання 3: Підтвердіть governor частоти CPU

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

    Що це значить: «ondemand» може знижувати частоту до росту навантаження; на старіших платформах він може повільно реагувати.

    Рішення: Для вузлів з низькою латентністю розгляньте «performance» і повторіть тест; для батчів «ondemand» може бути прийнятним.

    Завдання 4: Пошук термальних зон і температур

    cr0x@server:~$ for z in /sys/class/thermal/thermal_zone*/temp; do echo "$z: $(cat $z)"; done
    /sys/class/thermal/thermal_zone0/temp: 78000
    /sys/class/thermal/thermal_zone1/temp: 65000

    Що це значить: Температури в міліградусах Цельсія. 78000 = 78°C.

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

    Завдання 5: Виявлення індикаторів троттлінгу в kernel логах

    cr0x@server:~$ dmesg | grep -i -E 'throttl|thermal|critical|overheat' | tail
    CPU0: Thermal monitoring enabled (TM1)
    CPU0: Temperature above threshold, cpu clock throttled
    CPU0: Temperature/speed normal

    Що це значить: CPU знизив швидкість через нагрів. Ваша «загадкова» пропускна здатність може бути простою фізикою.

    Рішення: Усуньте проблеми з повітряним потоком/охолодженням, зменшіть навантаження або зменшіть конкуренцію. Не підганяйте ПО під термічний дефект.

    Завдання 6: Швидко перевірте чергу запуску і насиченість CPU

    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
     2  0      0 120000  15000 210000    0    0     2     5  900 1400 85 10  5  0  0
     4  0      0 118000  15000 209000    0    0     0     8 1100 1800 92  7  1  0  0

    Що це значить: «r» (черга запуску) постійно вище за кількість CPU вказує на конкуренцію за CPU. Низький «id» означає зайнятість.

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

    Завдання 7: Знайдіть головних споживачів CPU і чи вони «крутяться» в циклі

    cr0x@server:~$ top -b -n 1 | head -n 15
    top - 12:14:01 up 21 days,  3:11,  1 user,  load average: 3.90, 3.60, 3.20
    Tasks: 184 total,   2 running, 182 sleeping,   0 stopped,   0 zombie
    %Cpu(s): 92.0 us,  7.0 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    2174 app      20   0  1856m  612m  122m R  98.7  7.6  12:11.02 java

    Що це значить: Високий user CPU все ще може бути «в очікуванні» (промахи пам’яті, промахи прогнозів гілок).
    Низький «wa» не означає, що CPU не стоїть.

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

    Завдання 8: Перевірте контекстні переключення і тиск переривань

    cr0x@server:~$ pidstat -w 1 5
    Linux 6.1.0 (server) 	01/09/2026 	_x86_64_	(2 CPU)
    
    12:14:20      UID       PID   cswch/s nvcswch/s  Command
    12:14:21     1001      2174   1200.00    350.00  java

    Що це значить: Дуже високі контекстні переключення можуть означати забагато потоків, контенцію за блокування або інтенсивний таймер/IO churn.

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

    Завдання 9: Сигнали перевантаження пропускної здатності пам’яті через perf (cycles vs stalled frontend/backend)

    cr0x@server:~$ sudo perf stat -p 2174 -e cycles,instructions,branches,branch-misses,cache-misses -a -- sleep 10
     Performance counter stats for 'system wide':
    
         32,100,000,000      cycles
         12,400,000,000      instructions              #    0.39  insn per cycle
          2,900,000,000      branches
            145,000,000      branch-misses             #    5.00% of all branches
            610,000,000      cache-misses

    Що це значить: Низький IPC (0.39) плюс багато кеш/гілкових промахів вказують, що CPU зайнятий, але непродуктивний.

    Рішення: Оптимізуйте шляхи коду для локальності/передбачуваності гілок або обирайте CPU з кращим IPC і обробкою пам’яті.

    Завдання 10: Підтвердіть, чи ви дійсно обмежені I/O

    cr0x@server:~$ iostat -xz 1 3
    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
              90.50    0.00    7.20    0.10    0.00    2.20
    
    Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await
    sda              2.10    1.00   45.0    28.0     0.0     0.2    3.0    1.2

    Що це значить: Диск ледь завантажений; await низький. Це не вузьке місце зберігання.

    Рішення: Перестаньте звинувачувати диски. Зосередьтесь на CPU/пам’яті/блокуваннях і профілюванні на рівні запиту.

    Завдання 11: Перевірте тиск пам’яті і сторінкування (тихий вбивця продуктивності)

    cr0x@server:~$ free -m
                   total        used        free      shared  buff/cache   available
    Mem:            2048        1720         120          12         207         210
    Swap:           2048         900        1148

    Що це значить: Використання swap може бути нормальним, але якщо при навантаженні відбувається активне сторінкування, будуть паузи і сплески.

    Рішення: Якщо активність swap корелює з латентністю — зменшіть пам’ятний відбиток, додайте RAM або перерозподіліть навантаження.

    Завдання 12: Підтвердьте активне сторінкування, а не просто використання swap

    cr0x@server:~$ sar -B 1 5
    Linux 6.1.0 (server) 	01/09/2026 	_x86_64_	(2 CPU)
    
    12:15:10  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgsteal/s
    12:15:11      0.00      0.00    820.00      0.00   1200.00      0.00      0.00
    12:15:12     10.00     45.00   2100.00     15.00    400.00    800.00    300.00

    Що це значить: Major faults (majflt/s) і сканування вказують на реальний тиск пам’яті.

    Рішення: Сторінкування під навантаженням — це проблема ємності. Усуньте проблему з пам’яттю, а не з налаштуваннями CPU.

    Завдання 13: Огляньте тиск планувальника одним поглядом

    cr0x@server:~$ cat /proc/pressure/cpu
    some avg10=12.34 avg60=10.01 avg300=8.55 total=987654321

    Що це значить: CPU PSI «some» вказує час, який задачі проводять в очікуванні CPU-ресурсів.

    Рішення: Якщо PSI росте разом з латентністю, вам потрібен більш ефективний CPU (IPC), менше runnable-потоків або політика зниження навантаження.

    Завдання 14: Виявлення контенції за блокуваннями (часто діагностують як «повільний CPU»)

    cr0x@server:~$ sudo perf top -p 2174
    Samples: 31K of event 'cpu-clock', 4000 Hz, Event count (approx.): 7750000000
    Overhead  Shared Object        Symbol
      12.40%  libc.so.6            [.] pthread_mutex_lock
       9.10%  libjvm.so            [.] SpinPause

    Що це значить: Час витрачається на блокування і спінінг, а не на корисну роботу.

    Рішення: Зменшіть контенцію (шардінг локів, зменшення потоків, виправлення гарячих критичних секцій). Більше ГГц вас не врятують.

    Завдання 15: Перевірте дружність до кешу через швидкий мікробенч (не заміна реальних тестів)

    cr0x@server:~$ taskset -c 0 sysbench cpu --cpu-max-prime=20000 run
    CPU speed:
        events per second:  580.21
    
    General statistics:
        total time:                          10.0004s
        total number of events:              5804

    Що це значить: Обчислювальний тест може виглядати «чудово», навіть якщо ваш сервіс обмежений пам’яттю або гілками.

    Рішення: Використовуйте мікробенчмарки тільки для базової перевірки; рішення приймайте на основі реалістичних тестів навантаження й латентності.

    Жарт №2: Якщо ваш план — «додайте потоків, поки не стане швидше», ви не оптимізуєте — ви закликаєте демони контенції.

    Швидка діагностика: що перевірити першим/другим/третім

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

    Перше: переконайтеся, що CPU, який ви думаєте, — це той, що у вас є

    1. Частота під навантаженням: перевірте /proc/cpuinfo MHz, governor та dmesg на предмет троттлінгу.
    2. Терміки: перегляньте термальні зони та стан вентиляторів/повітряного потоку через наявну телеметрію.
    3. Віртуалізація: підтвердіть, що вас не обмежують квоти CPU або «шумні сусіди» (PSI, cgroups).

    Мета: виключити «CPU буквально не працює на очікуваній швидкості» за 5 хвилин.

    Друге: визначте, чи ви обчислювально-зв’язні, пам’яттєво-зв’язні чи контенційно-зв’язані

    1. Черга запуску і PSI: vmstat та /proc/pressure/cpu для очікування CPU.
    2. perf IPC: cycles vs instructions; низький IPC вказує на зупинки/промахи.
    3. Сигнали контенції за локами: perf top, pidstat контекстні переключення, дампи потоків додатка.

    Мета: класифікувати проблему. Ви не зможете виправити те, що не назвали.

    Третє: підтвердіть, що це не I/O і не сторінкування

    1. Диск: iostat -xz для завантаження і await.
    2. Сторінкування: sar -B для major faults і сканування.
    3. Мережа: перевірте втрати/помилки і черги (не показано вище, але обов’язково).

    Мета: припинити марнувати час на невірну підсистему.

    Четверте: вирішіть, чи це проблема підходу апаратного забезпечення або ПО

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

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

    1) Симптом: «CPU завантажено, але пропускна здатність посередня»

    Корінна причина: Низький IPC через промахи кешу, помилки передбачення гілок або латентність пам’яті; CPU виглядає зайнятим, але застопореним.

    Виправлення: Використайте perf stat, щоб підтвердити низький IPC і високі промахи; оптимізуйте локальність, зменшіть pointer-chasing і профілюйте гарячі шляхи коду. Якщо купуєте залізо, віддавайте пріоритет IPC і підсистемі пам’яті, а не тактовій частоті.

    2) Симптом: «Сплески латентності з’являються лише в теплі після обіду / після заміни вентилятора»

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

    Виправлення: Підтвердіть через dmesg і показники термальних зон; усуньте охолодження, почистіть фільтри, перевірте криві вентиляторів і зберігайте запас по вхідній температурі. Розглядайте терміки як критичний SLO-залежник.

    3) Симптом: «Ми увімкнули Hyper-Threading і p99 стало гірше»

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

    Виправлення: A/B тест HT увімкн./вимкн. з production-подібною конкуренцією; зменшіть кількість потоків; виправте гарячі блокування; розглядайте HT лише для задач орієнтованих на пропускну здатність або I/O-стримируваних навантажень.

    4) Симптом: «Мікробенчмарки покращилися, але продакшн став повільнішим»

    Корінна причина: Мікробенчмарки — обчислювальні і передбачувані; продакшн — розгалужений і пам’яттєво важкий. Дизайн NetBurst винагороджує перше і карає друге.

    Виправлення: Бенчмаркуйте з реалістичними сумішами запитів, фазами гарячого/холодного кешу і хвостовою латентністю. Включіть конкуренцію, поведінку алокатора і реальні розміри даних.

    5) Симптом: «Load average зріс після того, як ми «оптимізували», додавши потоки»

    Корінна причина: Перепідписка і контенція; більше runnable-потоків збільшує накладні витрати планування і блокувань.

    Виправлення: Використайте pidstat для вимірювання контекстних переключень, perf top для символів, пов’язаних з блокуваннями, і зменшіть конкуренцію. Додавайте паралелізм тільки там, де робота є паралельною і вузьке місце зміщується.

    6) Симптом: «Оновлення CPU не допомогло базі даних»

    Корінна причина: Навантаження обмежене латентністю пам’яті або пропускною здатністю пам’яті (промахи буферного пулу, pointer-chasing у B-деревах, кеш-промахи).

    Виправлення: Збільшіть ефективний показник попадань в кеш (індекси, форма запитів), додайте RAM, зменшіть робочий набір і виміряйте кеш-промахи/IPC. Не кидайте ГГц на стіну пам’яті.

    7) Симптом: «Все виглядає нормально, окрім випадкових пауз і таймаутів»

    Корінна причина: Сторінкування, паузи GC або сплески контенції, які не показуються як сталий рівень завантаження.

    Виправлення: Перевірте major faults, PSI і метрики пауз додатка. Усуньте тиск пам’яті і зменшіть підсилення хвоста (таймаути, повторні спроби, «thundering herd»).

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

    Чеклист A: Купівля обладнання без повторення помилки NetBurst

    1. Визначайте успіх за латентністю і пропускною здатністю (p50/p95/p99 + стабільні RPS), а не за тактовою частотою.
    2. Вимірюйте IPC-проксі: використовуйте perf на репрезентативних навантаженнях; порівнюйте cycles/instructions і рівні промахів.
    3. Моделюйте поведінку пам’яті: розмір робочого набору, показник попадань у кеш, очікувана конкуренція і потреби в пропускній здатності.
    4. Перевіряйте терміки: тестуйте в стійці, з реалістичною температурою навколишнього середовища і профілем вентиляторів.
    5. Тестуйте вплив SMT/HT: увімкн./вимкн. з реальними кількостями потоків і відстеженням хвостової латентності.
    6. Віддавайте перевагу збалансованим системам: кількість каналів пам’яті, розміри кешу і міжз’єднання важливі так само, як і частоти ядер.

    Чеклист B: Коли розгортання «швидшого CPU» робить продакшн повільнішим

    1. Підтвердіть частоту і троттлінг (governor, температури, dmesg).
    2. Порівняйте perf IPC і рівні промахів до/після.
    3. Перевірте кількість потоків і контекстні переключення; першим відкотіть зміни з «подвійними потоками».
    4. Перевірте тиск пам’яті і сторінкування; негайно виправте major faults.
    5. Шукайте регресії контенції за локами, введені новою конкуренцією.
    6. Якщо неясно, захопіть flame graph або профіль і розгляньте його як хроніку інциденту.

    Чеклист C: Стабілізувати хвостову латентність на старих, гарячих, частотозалежних системах

    1. Зменшіть конкуренцію так, щоб відповідала кількості ядер (особливо з HT) і спостерігайте вплив на p99.
    2. Прив’язуйте критичні потоки тільки якщо ви розумієте топологію; інакше прив’язка може закутати вас у кут.
    3. Тримайте governor CPU послідовним (часто «performance» для вузлів з критичною латентністю).
    4. Забезпечте термальний запас: надсилайте оповіщення про температуру і події троттлінгу, а не лише про завантаження CPU.
    5. Оптимізуйте гарячі шляхи для локальності; по можливості усуньте непередбачувані гілки.
    6. Введіть зворотний тиск і розумні таймаути, щоб уникнути лавини повторних спроб.

    Питання та відповіді

    1) Чи був Pentium 4 справді «поганим», чи просто його неправильно розуміли?

    Це була вузька ставка. У навантаженнях, що відповідали його сильним сторонам (стрімінг, передбачуваний код, сильний виграш від частоти),
    він міг працювати добре. У змішаних серверних навантаженнях він часто давав гіршу продуктивність у реальному житті на ват і на долар,
    ніж альтернативи. «Неправильно зрозумілий» — це мило кажучи; «неправильно проданий» ближче до істини.

    2) Чому вищі ГГц не перетворювалися на вищу продуктивність?

    Бо продуктивність залежить від корисної роботи за такт (IPC) і від того, як часто ви зупиняєтеся через пам’ять, гілки і контенцію.
    NetBurst збільшував кількість тактів, але часто зменшував корисну роботу за такт у реальних навантаженнях.

    3) Який операційний урок для сучасних систем?

    Не приймайте заголовкову метрику як істину. Для CPU це ГГц; для зберігання — «IOPS»; для мережі — «Gbps».
    Завжди запитуйте: при якій латентності, з якою конкуренцією і як поводиться хвіст?

    4) Чи «виправив» Hyper-Threading NetBurst?

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

    5) Чому Pentium M іноді обганяв Pentium 4 при значно нижчих частотах?

    Pentium M (з роду P6) наголошував на IPC і ефективності. У розгалужених, чутливих до кешу навантаженнях вищий IPC і краща ефективність
    часто перемагають сирі такти, особливо коли висока частота спричиняє енергетичні і термічні троттлінги.

    6) Як визначити, чи моє навантаження обмежене пам’яттю, а не CPU?

    Шукайте низький IPC з високими промахами кешу в perf, а також обмежений ефект від додавання ядер або підвищення частоти.
    Ви також побачите плато пропускної здатності при «завантаженому» CPU. Це зазвичай стіна пам’яті або стіна контенції.

    7) Чи реально важливий троттлінг за температурою?

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

    8) Що потрібно бенчмаркувати, щоб уникнути помилок ери ГГц?

    Бенчмаркуйте фактичний сервіс: реальна суміш запитів, реальний розмір даних, реальна конкуренція і віддавайте звіт по p95/p99 та пропускній здатності.
    Додайте фазу «холодного кешу» і тривалий прогін, достатній, щоб система «пропіділася» по теплу.

    9) Чи є сучасні аналоги пастки NetBurst?

    Є. Будь-коли, коли ви оптимізуєте під один піковий показник ціною системної поведінки: турбо-частоти без термічного бюджету,
    бенчмарки зберігання, що ігнорують fsync-латентність, або тестування мережі, що ігнорує втрати пакетів під навантаженням.
    Патерн однаковий: пік виграє на слайді, хвіст втрачає клієнта.

    Висновок: що робити, коли вам знову продають ГГц

    NetBurst — це не лише ретро-трівіал про CPU. Це чітка історія про інцентиви, вимірювання і ціну ставки на одне число.
    Intel оптимізував під частоту, бо ринок платив за частоту. Навантаження, що мали значення —
    розгалужений серверний код, пам’яттєво важкі системи, стійки з термічними обмеженнями — виставили рахунок.

    Практичні кроки далі — нудні, і саме тому вони працюють:

    1. Визначайте продуктивність через хвостову латентність, а не пікову пропускну здатність і тим паче не через частоту.
    2. Інструментуйте вузькі місця: perf-лічильники, PSI, метрики сторінкування і термічні/троттлінгові сигнали.
    3. Бенчмаркуйте як продакшн: конкуренція, розмір даних, поведінка кешу, прогон з нагрівом і реалістичні суміші запитів.
    4. Розглядайте терміки як ємність: якщо CPU тротлиться, ваша архітектура «обмежена охолодженням». Визнайте це.
    5. Сумнівайтеся в «безкоштовній продуктивності»: HT/SMT, агресивна конкуренція і мікро-оптимізації, що ігнорують контенцію.

    Якщо запам’ятати лише одне: такти — це компонент, а не гарантія. Система — це продукт. Експлуатуйте її відповідно.