MariaDB vs PostgreSQL на HDD: хто сильніше страждає під дисковим навантаженням (і чому)

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

Жорсткі диски не виходять з ладу граціозно. Вони дають збій як запрошення на зустріч: спочатку тихо, а потім раптово всі запізнюються й ніхто не розуміє чому.
Коли MariaDB або PostgreSQL опиняються на HDD і робоче навантаження перестає вміщатися в пам’ять, виникає специфічне страждання: висока затримка, завислі коміти,
розлючені таймаути в застосунку та дашборд, що нагадує кардіограму.

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

Прямолінійна відповідь: хто більше страждає на HDD

При дисковому навантаженні на HDD обидві системи страждають. Але вони страждають по-різному, і відповідь «хто більше страждає» залежить від
якого саме дискового тиску ви маєте на увазі.

  • Тиск випадкових читань (робочий набір не вміщується в RAM, багато точкових запитів):
    MariaDB/InnoDB часто деградує сильніше. У InnoDB різкий провал продуктивності на HDD відбувається, коли промахи буфера перетворюються на випадкові звернення.
  • Тиск записів/комітів (багато транзакцій, увімкнена надійність):
    PostgreSQL може виглядати гірше, якщо WAL (і поведінка fsync) опиняються на тому ж повільному пристрої і чекпоінти не налаштовані.
    Ви побачите стрибки затримки комітів і «штормові чекпоінти».
  • Змішане OLTP (типове веб-навантаження):
    MariaDB зазвичай чутливіша до випадкових I/O та шаблонів вторинних індексів; PostgreSQL більше чутливий до WAL/чекпоінтів/автовакумації.

Якщо змусити мене вжати в заголовок: MariaDB зазвичай «страждає більше» на HDD при IOPS-обмеженні випадковими читаннями;
PostgreSQL зазвичай «страждає більше» при fsync/WAL/чекпоінт-обмеженні під записах за відсутності налаштувань.
У реальному продакшні ви можете зробити будь-яку з них жахливою комбінацією дефолтів, схеми та наївних сподівань.

Операційна порада: якщо вам мусить працювати на HDD, орієнтуйтеся на
менше випадкових I/O, більше пам’яті, повільніше але рівніше скидання сторінок, та ізоляцію шляхів послідовних записів від випадкових читань.
Обидві СУБД можна зробити життєздатними на HDD, але жодна не пробачає, коли ваше навантаження перетворюється на безупинну гонку голів.

Що HDD ненавидить (і що терпить)

HDD не «повільний». Він несумісний. Послідовна пропускна здатність може виглядати пристойно на папері. Вбивця — це латентність переміщення головки і черги.
Коли ви робите багато випадкових I/O з малими блоками, один диск поводиться як вузький прохід, через який усі намагаються протиснутися одночасно.

HDD алергічний до випадкового I/O

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

HDD терпить послідовні записи… поки не перестає

Послідовні записи журналів можуть бути прийнятні на HDD: операції append, відносно лінійні. Тут PostgreSQL WAL і InnoDB redo логи
можуть допомогти — якщо система зберігає їх послідовними. Але чекпоінти, doublewrite, скидання сторінок і vacuum можуть перетворити
«послідовно-подібне» на «випадкове», і ваша глибина черги стане анекдотом.

Жарт №1: Запуск бази з інтенсивними записами на одному HDD — як робити розподілені системи з липкими нотатками: технічно можливо, соціально дорого.

Бачимий вузький місць HDD: латентність, а не пропускна здатність

На інцидентах у продакшні сигнал зазвичай такий:
часи очікування стрибнули, p99 застосунку пішов вгору, а пропускна здатність впала.
Люди дивляться на графіки MB/s і пропускають справжню історію: 10ms seek стає 100ms, коли черга накопичується, і тепер fsync вашої транзакції чекає за vacuum та чекпоінтом.

Чому InnoDB і PostgreSQL поводяться інакше під навантаженням

InnoDB (MariaDB): промахи буфера перетворюються на випадкові читання

InnoDB — це движок на сторінках. Дані та індекси живуть у B-деревах. Коли сторінки немає в buffer pool, вона завантажується з диска.
На SSD ви здебільшого платите невеликий пенальті. На HDD кожен промах може означати seek.

Сильні сторони InnoDB — adaptive hash index, change buffering, фонове скидання — допомагають, але вони не відміняють фізику.
Якщо ваш гарячий набір не вміщується в RAM і патерн доступів випадковий, InnoDB перетворюється на генератор випадкових I/O.

  • Doublewrite buffer: захищає від часткових записів сторінок (важливо на HDD), але додає накладні записи.
  • Redo log: відносно послідовний, але політики скидання можуть змушувати часті fsync.
  • Change buffer (insert buffer): може відстрочити обслуговування вторинних індексів, зменшуючи випадкові записи, але пізні злиття можуть викликати сплески I/O.

PostgreSQL: WAL послідовний; чекпоінти вирішують, чи ви поспите

PostgreSQL також використовує сторінки фіксованого розміру та shared buffer cache. Але його шлях запису зосереджений навколо WAL:
коміт записує WAL-рекорди, а сторінки даних записуються пізніше background writer / checkpointer.

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

  • WAL fsync: затримка коміту залежить від поведінки fsync і продуктивності пристрою WAL.
  • Чекпоінти: примушують брудні сторінки на диск; надто часті або занадто різкі — означають I/O-шторм.
  • Autovacuum: тримає bloat під контролем, але може створювати фонове навантаження читання/запису; при неправильних налаштуваннях стає тихим податком на диск.

Де ховається біль HDD: write amplification і фонова робота

Коли кажуть «дисковий тиск», часто мають на увазі «база робить більше I/O, ніж я думаю».
Зазвичай це write amplification:

  • Забагато брудних сторінок скидається на одиницю корисної роботи (погана поведінка чекпоінтів/флашингу).
  • Bloat та фрагментація, які змушують читання/записи торкатися більше сторінок.
  • Індекси, що множать записи (кожен insert/update зачіпає кілька B-дерев).
  • Налаштування реплікації/логування, що змушують синхронний диск працювати частіше, ніж потрібно.

Цитата про надійність, яка має висіти на кожній операційній стіні

Надія — не стратегія. — генерал Гордон Р. Салліван

На HDD сподіватися, що кеш ядра або «пізніше налаштуємо» врятує вас — це те, як ви опиняєтеся на інцидент-резпонсі з холодним обідом.

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

1) Інцидент «усе повільно, але CPU лінивої роботи»

CPU виглядає нудьгуючим, load average високий, а потоки бази «працюють», але нічого продуктивного не роблять.
На Linux це часто iowait і заблоковані задачі. Глибина черги HDD зростає, await росте, і ваш p95 перетворюється на вибачення у вигляді p95.

2) Стрибки затримки комітів (fsync-стікання)

У PostgreSQL це часто проявляється як повільні коміти, коли WAL fsync затримується через насичений диск.
У MariaDB/InnoDB flush-и redo логів і fsync можуть аналогічно зупиняти коміти в залежності від налаштувань.

3) Фонова техпідтримка б’ється з вашою фронтальною роботою

Чекпоінти, vacuum/autovacuum і InnoDB page cleaners «допомагають», доки не перестають.
На HDD найгірший сценарій: фонові роботи стають агресивними, латентність диска стрибає, і тоді фронтова робота сповільнюється,
що викликає ще більше накопичення, що змушує фонову роботу ще активніше працювати. Це петля зворотного зв’язку з pager-ефектом.

4) Сюрприз «додали індекс і все стало гірше»

Індекси пришвидшують читання, але множать записи. На HDD цей множник — податок з відсотками: більше забруднених сторінок, більше скидань,
більше випадкових I/O, більше bloat. Якщо ви не вимірюєте, оптимізація може привести до простою.

5) Повільна реплікація та беклог «IO thread»

Насичення диска уповільнює запис WAL/binlogs і застосування змін. Репліки відстають, а failover стає захопливим у невірний спосіб.
HDD дає менше шансів бути неуважним щодо розміщення логів і частоти fsync.

План швидкої діагностики

Це порядок дій, який я використовую, коли база на HDD тане. Він спроектований, щоб швидко знайти вузьке місце, а не бути «повним».
Ви можете зробити це за 5–10 хвилин, якщо маєте доступ.

Перш за все: підтвердіть, що це латентність диска (а не CPU чи блокування)

  1. Перевірте await і %util по пристрою. Якщо await високий і util запікається, ви I/O-забезпечені.
  2. Перевірте, чи база чекає на fsync або buffer I/O проти очікувань на locks.
  3. Підтвердіть тиск на пам’ять: якщо відбувається свопінг або коефіцієнт попадань у кеш різко впав, HDD змушений обслуговувати випадкові читання, які не під силу.

По-друге: визначте, що саме — читання чи записи — керують болем

  1. Подивіться на read/write IOPS і середній розмір запиту. Випадкові читання виглядають як малі читання з високим await.
  2. Перевірте статистику СУБД: відсоток попадань у кеш, генерацію WAL/redo, активність чекпоінтів, скидання брудних сторінок.

По-третє: виберіть найменш-погане тимчасове пом’якшення

  1. Якщо випадкові читання: збільште ефективний кеш (RAM, buffer pool/shared_buffers), зменшіть робочий набір, виправте запити/індекси.
  2. Якщо сплески записів: згладьте чекпоінти/флашинг, по можливості винесіть WAL/redo на окремий пристрій, зменшіть частоту синхронізації лише якщо це прийнятно.
  3. Якщо фонові задачі агресивні: обмежте autovacuum або page cleaners, плануйте важкі роботи поза піком та усувайте bloat/фрагментацію правильно.

Практичні завдання: команди, виходи, рішення (12+)

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

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

cr0x@server:~$ iostat -x 1 5
Linux 6.5.0 (db01) 	12/30/2025 	_x86_64_	(8 CPU)

Device            r/s     w/s   rMB/s   wMB/s  avgrq-sz avgqu-sz await r_await w_await  %util
sda              35.0   120.0    1.2     6.8      128     7.50   58.2   42.1    62.9  99.4

Значення: %util близько 100% плюс await кількадесят мс вказує, що диск насичений. avgqu-sz показує чергу.

Рішення: розглядайте це як інцидент I/O. Припиніть звинувачувати CPU. Далі визначте, чи домінують читання чи записи і чи WAL/redo на тій самій пристрої.

Завдання 2: Подивіться, чи ядро витрачає час на iowait

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3 12      0  51200  12000 820000    0    0   980  5400  900 1800 10  6 40 44  0

Значення: високий b (blocked) і високий wa означають, що потоки чекають на диск.

Рішення: перейдіть до аналізу чеків на боці БД; також перевірте свопінг (si/so), бо своп на HDD — це підпал продуктивності.

Завдання 3: Підтвердіть, що ви не свопите (або не трешите)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        26Gi       120Mi       1.1Gi       4.9Gi       2.2Gi
Swap:          4.0Gi       1.8Gi       2.2Gi

Значення: використання swap на хості БД — червоний прапор; available критично мало.

Рішення: зменшіть використання пам’яті, відрегулюйте кеші БД, щоб уникнути OOM, або додайте RAM. На HDD використання swap часто корелює з бурею випадкових читань.

Завдання 4: Визначте топ-споживачів I/O на рівні процесів

cr0x@server:~$ sudo iotop -oPa
Total DISK READ: 18.25 M/s | Total DISK WRITE: 72.10 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 2211 be/4  postgres   2.10 M/s  18.50 M/s   0.00 %  89.00 % postgres: checkpointer
 1998 be/4  postgres   0.50 M/s   9.20 M/s   0.00 %  45.00 % postgres: autovacuum worker
 1870 be/4  mysql      1.20 M/s  22.30 M/s   0.00 %  70.00 % mariadbd

Значення: видно, чи checkpointer/autovacuum (Postgres) або mariadbd (InnoDB flushing) домінують у записах.

Рішення: якщо фонові процеси домінують, налаштуйте checkpoint/vacuum (Postgres) або параметри флашингу (InnoDB), і по можливості розділіть шляхи лог/дані.

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

cr0x@server:~$ mount | egrep ' /var/lib/(postgresql|mysql) '
/dev/sda2 on /var/lib/postgresql type ext4 (rw,relatime,errors=remount-ro)
/dev/sda3 on /var/lib/mysql type ext4 (rw,relatime,errors=remount-ro)

Значення: relatime — ок; noatime може зменшити метадані записів. Більше питання — чи логи та дані ділять шпінделі.

Рішення: розгляньте noatime де доречно; головніше — сплануйте розділення пристроїв для WAL/redo і даних на HDD.

Завдання 6: Перевірте wait events PostgreSQL на I/O проти блокувань

cr0x@server:~$ sudo -u postgres psql -c "select wait_event_type, wait_event, count(*) from pg_stat_activity where state='active' group by 1,2 order by 3 desc;"
 wait_event_type |  wait_event   | count
-----------------+---------------+-------
 IO              | DataFileRead  |    18
 IO              | WALWrite      |     6
 Lock            | relation      |     2

Значення: багато сеансів застрягли на DataFileRead — це випадкові читання; WALWrite — тиск лог-записів.

Рішення: якщо домінує DataFileRead, працюйте над кеш-хітом, поганими запитами і bloat. Якщо домінує WALWrite, зосередьтесь на пристрої WAL і налаштуванні чекпоінтів.

Завдання 7: Поведінка чекпоінтів PostgreSQL (чи ви викликаєте шторми?)

cr0x@server:~$ sudo -u postgres psql -c "select checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint from pg_stat_bgwriter;"
 checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time | buffers_checkpoint
------------------+-----------------+-----------------------+----------------------+-------------------
              120 |             310 |              9876543  |              432109 |           9823410

Значення: високий checkpoints_req проти timed підказує, що ви часто досягаєте max_wal_size або інші тригери; великі write/sync часи вказують на болісні скидання.

Рішення: збільшіть max_wal_size, налаштуйте checkpoint_timeout і checkpoint_completion_target, і розгляньте переміщення WAL на окремий шпіндель.

Завдання 8: Швидка перевірка hit ratio кеша PostgreSQL

cr0x@server:~$ sudo -u postgres psql -c "select sum(blks_hit) as hit, sum(blks_read) as read, round(100.0*sum(blks_hit)/nullif(sum(blks_hit)+sum(blks_read),0),2) as hit_pct from pg_stat_database;"
    hit    |  read   | hit_pct
-----------+---------+---------
 987654321 | 5432109 | 99.45

Значення: ~99% все ще може бути погано, якщо залишок 1% великий при вашому QPS. На HDD цей 1% може спричинити весь інцидент.

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

Завдання 9: Знайти важкі запити PostgreSQL, що викликають читання/записи

cr0x@server:~$ sudo -u postgres psql -c "select queryid, calls, rows, shared_blks_read, shared_blks_hit, (shared_blks_read*8) as read_kb, left(query,120) from pg_stat_statements order by shared_blks_read desc limit 5;"
 queryid  | calls | rows  | shared_blks_read | shared_blks_hit | read_kb | left
----------+-------+-------+------------------+-----------------+---------+-------------------------------
 12345678 |  9200 | 18400 |          812345  |        91234567 | 6498760 | SELECT * FROM events WHERE ...

Значення: кілька запитів часто домінують у читаннях. На HDD це ваш список для виправлень запитів/індексів.

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

Завдання 10: MariaDB — перевірка buffer pool та тиску на читання

cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+------------+
| Variable_name                         | Value      |
+---------------------------------------+------------+
| Innodb_buffer_pool_read_requests      | 9876543210 |
| Innodb_buffer_pool_reads              | 43210987   |
+---------------------------------------+------------+

Значення: Innodb_buffer_pool_reads — це фізичні читання. На HDD, якщо це швидко зростає, ви робите звернення до диска.
Співвідношення дає підказку; важливіша швидкість під час інциденту ніж сумарне значення.

Рішення: якщо фізичні читання сплескують, збільшіть innodb_buffer_pool_size (в межах RAM), виправте індекси/запити, і зменшіть повні сканування таблиць.

Завдання 11: MariaDB — перевірка поведінки flush-ів redo логу і тиску fsync

cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs';"
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| innodb_flush_log_at_trx_commit| 1     |
+-------------------------------+-------+
+---------------------+----------+
| Variable_name       | Value    |
+---------------------+----------+
| Innodb_os_log_fsyncs| 55443322 |
+---------------------+----------+

Значення: innodb_flush_log_at_trx_commit=1 — найнадійніше (fsync при кожному коміті). На HDD з високим TPS це може бути жорстоко.
Innodb_os_log_fsyncs показує, як часто це відбувається.

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

Завдання 12: MariaDB — інспекція скидання брудних сторінок і зависань

cr0x@server:~$ sudo mariadb -e "SHOW ENGINE INNODB STATUS\G" | egrep -i 'Modified db pages|buffer pool size|free buffers|Pending writes|Page cleaner'
Modified db pages   245678
Buffer pool size    2097152
Free buffers        1024
Pending writes: LRU 0, flush list 37, single page 0
Page cleaner: 1000ms intended loop took 8450ms

Значення: багато змінених сторінок + цикл Page cleaner, що тривав значно довше, ніж передбачалося, означає, що InnoDB не встигає флашити.
На HDD це часто передує зависанням і раптовим вибухам латентності.

Рішення: налаштуйте innodb_io_capacity/innodb_io_capacity_max під реальні можливості HDD (а не фантазії), зменшіть швидкість записів і переконайтеся, що розмір redo логу адекватний.

Завдання 13: Перевірте, чи WAL/redo і дані на одному пристрої

cr0x@server:~$ lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS
NAME   SIZE TYPE MOUNTPOINTS
sda   1.8T  disk
├─sda1 512M part /boot
├─sda2 900G part /var/lib/postgresql
└─sda3 900G part /var/lib/mysql

Значення: усе на одному диску означає, що WAL/redo записи і читання даних конкурують за переміщення головки. На HDD це самонамічена рана.

Рішення: розділіть: помістіть WAL/binlog/redo на інший шпіндель (або RAID10 набір) якщо можете. Навіть «посередній» виділений диск може стабілізувати латентність.

Завдання 14: Виявлення чергування диска та планувальника

cr0x@server:~$ cat /sys/block/sda/queue/scheduler
[mq-deadline] none kyber bfq

Значення: планувальник впливає на латентність при змішаних навантаженнях. Для HDD дедлайн-тип планувальника часто поводиться краще за «none».

Рішення: якщо ви використовуєте none на HDD, розгляньте mq-deadline (тестуйте спочатку). Не робіть за інерцією; виміряйте await і хвостову латентність.

Завдання 15: Обсяг WAL PostgreSQL і його розташування

cr0x@server:~$ sudo -u postgres psql -c "show data_directory; show wal_level; show synchronous_commit;"
      data_directory
-------------------------
 /var/lib/postgresql/16/main
(1 row)

 wal_level
-----------
 replica
(1 row)

 synchronous_commit
-------------------
 on
(1 row)

Значення: налаштування WAL визначають, скільки WAL генерується і коли коміти чекають. Якщо synchronous_commit увімкнено (за замовчуванням), коміти чекають на флаш WAL.

Рішення: тримайте synchronous_commit увімкненим, якщо ви не готові втратити недавні транзакції. Для полегшення на HDD перемістіть WAL на окремий пристрій і згладьте чекпоінти.

Завдання 16: Індикатор bloat у PostgreSQL (швидка перевірка розміру таблиць)

cr0x@server:~$ sudo -u postgres psql -c "select relname, pg_size_pretty(pg_total_relation_size(relid)) as total, n_live_tup, n_dead_tup from pg_stat_user_tables order by pg_total_relation_size(relid) desc limit 5;"
  relname  | total  | n_live_tup | n_dead_tup
-----------+--------+------------+------------
 events    | 220 GB | 410000000  | 98000000
 sessions  |  85 GB | 150000000  | 72000000

Значення: велика кількість dead tuples означає bloat. На HDD bloat = зайві сторінки = зайві seeks = додаткові страждання.

Рішення: налаштуйте autovacuum для цієї таблиці, розгляньте партиціювання і плануйте vacuum/rewrite операції обережно (вони інтенсивні по I/O).

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

Міні-історія 1: Інцидент через неправильне припущення

Середня SaaS-компанія працювала на MariaDB на парі великих HDD у RAID1. Навантаження було класичним OLTP: sessions, billing events,
і декілька «корисних» аналітичних запитів, що постійно просочувалися в продакшн.
Команда припускала, що RAID1 означає «читання в основному нормальне». Вони також вважали, що їхній buffer pool hit ratio «достатній»,
бо зазвичай він був вище 98%.

Потім маркетинг випустив фічу з endpoint-ом «пошук за префіксом email». Запит виглядав безпечним,
але на практиці пропускав індекси при певних налаштуваннях колації і викликав range scans по великій таблиці.
Під час піку ці скани спричинили хвилю churn у buffer pool. Відсоток пропусків не здавався драматичним,
але абсолютна кількість фізичних читань була великою.

На HDD випадкові читання вбили систему. Не тому, що диск не міг віддавати мегабайти в секунду — міг.
Але він не міг шукати достатньо швидко. Латентність зросла, запити накопичилися, пул з’єднань наситився. Застосунок почав ретраї.
Ретрай подвоїв навантаження. Ви знаєте продовження.

Фікс не був екзотичним. Вони додали правильний композитний індекс, змусили запит його використовувати, а потім збільшили buffer pool,
щоб гарячий набір реально вміщувався. Також вони винесли «аналітичні» endpoint-и на репліку, де повільні запити могли фейлитися, не перетворюючи первинку на генератор випадкових I/O.

Урок: RAID1 не робить випадкові seeks дешевими. Воно дає вам дві головки, які разом розчаровують.

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

Платформа, близька до платежів, працювала на PostgreSQL на HDD, бо середовище відповідності було «стабільне», а закупівлі повільні.
Їх долбили затримки комітів під час пакетного імпорту.
Хтось запропонував «простіше» рішення: робити чекпоінти частіше, щоб кожен чекпоінт писав менше і закінчувався швидше.

Вони зменшили checkpoint_timeout і тримали max_wal_size маленьким. Чекпоінти дійсно закінчувалися «швидше».
Також вони відбувалися постійно. Це перетворило потенційно більш рівномірний фон записів на майже постійний стан флашингу.
Checkpointer і бекенди конкурували з читаннями. Autovacuum як і раніше існував — бо ентропія непереможна.

Симптом був жорстокий: p99 латентність набувала ритмічного патерну.
Кожні кілька хвилин API сповільнювався настільки, що викликав таймаути.
На черговому виклику співробітник бачив у логах повідомлення «checkpoint complete» як метроном зла.

Остаточний фікс був протилежним: дозволити WAL накопичуватися більше (більший max_wal_size), підвищити
checkpoint_completion_target, щоб записи розподілялися, і перемістити WAL на власний диск.
Вікна імпорту знову стали нудними. Нудьга — це бажане.

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

Велика внутрішня система одночасно використовувала MariaDB і PostgreSQL для різних сервісів.
Усе мешкало на HDD масивах, бо середовище збудовано роки тому, а «швидкий шар» зарезервовано для інших систем.
Команда не могла швидко змінити залізо, тож зробила непопулярне: зробила I/O нудним.

Вони розділили шляхи логів і даних де можливо. WAL PostgreSQL отримав виділені шпінделі. MariaDB redo логи і binlogs також були розділені.
Вони ввели правило: жодних нефільтрованих змін схеми в пікові години, і кожен новий індекс потребував вимірювання вартості записів на staging з продакшн-обсягами даних.

Вони планували операції vacuum та InnoDB техобслуговування поза піком і уважно стежили за метриками фонової роботи:
час запису чекпоінту, читання buffer pool, брудні сторінки та реплікаційний лаг.
Коли інциденти траплялись, у них був план швидкої діагностики і люди ним користувалися замість того, щоб запускати протокол направлення провини.

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

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

1) Симптом: p99 латентність стрибає кожні кілька хвилин (PostgreSQL)

Корінь проблеми: сплески чекпоінтів (max_wal_size занадто малий, настройки чекпоінту надто агресивні, WAL і дані конкурують).

Фікс: збільшити max_wal_size, підняти checkpoint_completion_target (розподілити записи), і перемістити WAL на окремий диск, якщо можливо.

2) Симптом: повільні «прості» SELECT, CPU низький, await диска високий (MariaDB)

Корінь проблеми: промахи buffer pool породжують випадкові читання; відсутні або неправильні індекси; гарячий набір не вміщується в RAM.

Фікс: збільшити innodb_buffer_pool_size, виправити індекси/плани запитів, зменшити повні сканування таблиць, розглянути партиціювання або кешування.

3) Симптом: коміти періодично зависають; «fsync» з’являється всюди

Корінь проблеми: пристрій логів насичений; надто багато транзакцій змушують часті fsync; WAL/binlog/redo ділять HDD з даними.

Фікс: розділити логи на виділені шпінделі; об’єднувати записи в батчі; зберігати налаштування надійності, якщо ви не готові втрачати дані.

4) Симптом: реплікаційний лаг зростає під час піку

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

Фікс: обмежити фонові роботи, налаштувати паралельність застосування там, де можливо, розділити лог/дані, і зменшити write amplification (індекси, bloat).

5) Симптом: після додавання індексу все сповільнилося

Корінь проблеми: write amplification (додаткове обслуговування індексів), більше брудних сторінок, більше флашів і випадкових I/O.

Фікс: додавайте лише індекси, які окуповують себе; розгляньте часткові/covering індекси (Postgres), видаляйте неактивні індекси і валідовуйте на write-heavy тестуванні.

6) Симптом: autovacuum «випадково» шкодить продуктивності (PostgreSQL)

Корінь проблеми: накопичення bloat і dead tuples; autovacuum змушений працювати інтенсивніше під час піку, скануючи heap-и й індекси.

Фікс: налаштуйте autovacuum на рівні таблиці; тримайте dead tuples низькими; розгляньте партиціювання; плануйте ручний vacuum/rewrite, якщо потрібно.

7) Симптом: InnoDB зависає з попередженнями «page cleaner»

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

Фікс: відкоригуйте innodb_io_capacity, збільште розмір redo логу там, де доречно, зменшіть сплески записів і переконайтеся, що buffer pool не надто великий для можливостей диска.

8) Симптом: «У нас багато MB/s запасу», але латентність жахлива

Корінь проблеми: ви обмежені IOPS/seek-ами, а не пропускною здатністю. Малі випадкові I/O вбивають вас, поки графіки MB/s спокійно посміхаються.

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

Чеклісти / покроковий план (виживання на HDD)

Покроково: стабілізувати інцидент, що триває

  1. Підтвердіть насичення I/O: iostat -x показує високий await + високий util.
  2. Зупиніть кровотечу: тимчасово обмежте пакетні роботи, важкі звіти та довготривалі техзаходи.
  3. Розділіть читання й записи там, де можна: направте аналітику на репліки; призупиніть некритичні записи.
  4. Визначте топ-запити: використайте pg_stat_statements або slow query log; завершіть найгірших, якщо безпечно.
  5. Зменшіть агресію чекпоінтів/vacuum (Postgres): уникайте екстреного налаштування, що підвищує частоту чекпоінтів.
  6. Перевірте тиск пам’яті: якщо відбувається свопінг, зменшіть кеші або перезапустіть безпечно після виправлення конфігурації, щоб повернути пам’ять.

Чекліст: запобігання спіралям випадкових читань

  • Розміруйте буферні кеші реалістично: MariaDB innodb_buffer_pool_size, Postgres shared_buffers плюс кеш ОС.
  • Тримайте робочі набори маленькими: видаляйте невикористані індекси, архівуйте холодні дані, партиціюйте великі таблиці подій.
  • Виправляйте плани запитів: уникайте seq scans на гарячих шляхах; слідкуйте за nested loops, що роблять багато index lookups.
  • Віддавайте перевагу менше, але кращим індексам, а не «індексувати все».

Чекліст: запобігання катастрофам від сплесків записів

  • Розділяйте WAL/redo/binlog від даних на HDD.
  • Postgres: збільшуйте місце для WAL і розподіляйте чекпоінти (max_wal_size, checkpoint_completion_target).
  • MariaDB: моніторте брудні сторінки, затримки page cleaner, тиск redo логу; налаштовуйте I/O capacity під реальну поведінку диска.
  • Розглядайте autovacuum та InnoDB фонові процеси як повноцінні робочі навантаження, а не «фонова магія».

Чекліст: коли HDD — це незмінно

  • Для випадкових записо-важких навантажень обирайте RAID10 замість RAID5, якщо треба вирішувати.
  • Тримайте fsync надійність увімкненою, якщо бізнес не погодився на втрату транзакцій.
  • Вимірюйте хвостову латентність, а не лише пропускну здатність.
  • Документуйте план «швидкої діагностики» і практикуйте його.

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

  1. InnoDB не завжди був дефолтом у MySQL: раніше MyISAM був поширений, і він поводився дуже інакше під тиском записів та під час відновлення після краху.
  2. MariaDB відгалузилася від MySQL у 2009: питання управління після придбання Sun/Oracle змусили багато команд перестрахуватись.
  3. WAL-дизайн PostgreSQL старий і перевірений: відновлення через WAL було центральним протягом десятиліть, і тому послідовні журнали так важливі.
  4. InnoDB використовує doublewrite buffer за замовчуванням: він захищає від часткових записів сторінок — важливіше в умовах відключень живлення і епохи HDD.
  5. Чекпоінти PostgreSQL свідомо налаштовані для тонкої настройки: дефолти роблять акцент на безпеці, а не на «HDD з жорстким навантаженням і без терпіння».
  6. Autovacuum запровадили, щоб зменшити операційну нагрузку: але погано налаштований autovacuum — класичне джерело несподіваного I/O на спіночних дисках.
  7. Планувальники I/O Linux змінилися з blk-mq: те, що працювало на старих ядрах, не завжди найкраще зараз; HDD досі виграє від планування, орієнтованого на латентність.
  8. ПЗ прошивки HDD іноді лукавить: write cache та reorder можуть робити латентність непередбачуваною; бази компенсують це fsync-ом і консервативними припущеннями.

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

FAQ

1) Якщо я застряг на HDD, що обрати — MariaDB чи PostgreSQL?

Обирайте залежно від вашого навантаження та операційної зрілості. Якщо ви контролюєте запити і тримаєте гарячий набір в RAM, обидві СУБД можуть працювати.
Якщо очікуєте тиск випадкових читань, PostgreSQL часто деградує передбачуваніше; якщо очікуєте високі темпи комітів і не можете розділити WAL, MariaDB може здаватися плавнішою, поки не настане fsync-тиск.
Розумна відповідь: оберіть ту, яку ваша команда вміє налаштовувати та експлуатувати, і проєктуйте для меншої кількості випадкових I/O.

2) Яка найкраща апаратна зміна на HDD?

Розділення логів від читань даних. Покладіть PostgreSQL WAL (і MariaDB redo/binlog) на виділені шпінделі або окремий масив.
Це зменшує контенцію головки і стабілізує затримку комітів.

3) Чи вимкнути fsync або послабити налаштування надійності, щоб вижити?

Лише з явним дозволом бізнесу. PostgreSQL fsync=off — це заклик до корупції після збою.
MariaDB innodb_flush_log_at_trx_commit=2 може бути прийнятним у деяких випадках (ви ризикуєте втратити до ~1 секунди транзакцій),
але робіть це як продуктову вимогу, а не як нічний хак.

4) Чому на графіках видно пристойні MB/s, але застосунок таймаутиться?

Бо HDD обмежений IOPS/латентністю. Кілька MB/s малих випадкових читань можуть наситити seeks і створити величезні черги.
Дивіться await, глибину черги і wait events БД — не лише пропускну здатність.

5) Чи вирішує проблему HDD збільшення shared_buffers у PostgreSQL?

Іноді так, але це не панацея. PostgreSQL сильно залежить від page cache ОС. Надмірне збільшення shared_buffers може зменшити ефективний кеш ОС і нашкодити.
На HDD мета — тримати гарячий набір закешованим десь і уникнути свопа.

6) Чи завжди допомагає збільшення innodb_buffer_pool_size MariaDB на HDD?

Допомагає, поки не викликає своп або не позбавляє ОС ресурсів. На HDD своп — катастрофа. Збільшуйте buffer pool помірно і вимірюйте фізичні читання та латентність.
Також перевірте, чи виграють від цього ваші запити; поганий запит охоче сканує будь-що, що ви кешуєте.

7) Яка найпоширеніша помилка налаштування PostgreSQL для HDD?

Недостатній простір для WAL, що тригерить часті requested checkpoints, а потім дивуються, чому латентність ритмічно стрибає.
Дайте WAL місце і розподіліть I/O чекпоінтів.

8) Яка найпоширеніша помилка налаштування InnoDB для HDD?

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

9) Чи допоможе RAID?

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

10) Чи є схеми, які особливо болючі на HDD?

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

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

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

  1. Правильно вимірюйте латентність: iostat -x, wait events і статистика БД під час піку.
  2. Розділіть логи від даних: ізоляція WAL/redo/binlog — найвищий ROI на спінових дисках.
  3. Змусьте гарячий набір вміститись: правильно розміріться з кешами і припиніть своп. Якщо не можете — зменшіть робочий набір (партиціювання/архів/видалення індексів).
  4. Виправте топ-5 запитів: зазвичай не «база», а одна-дві шаблонні операції роблять HDD генератором seeks.
  5. Згладьте фонові роботи: налаштування чекпоінтів (Postgres) і поведінка флашингу (InnoDB) повинні прагнути до steady-state, а не «героїчних сплесків».
  6. Запишіть свій playbook: використайте план швидкої діагностики і потренуйтеся застосовувати його до наступного інциденту, перш ніж інцидент навчить вас.
← Попередня
pveproxy.service у Proxmox не запускається: 7 поширених причин і правильний порядок виправлення
Наступна →
Хмарний геймінг не вб’є GPU — ось чому

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