PostgreSQL vs Percona Server міфи продуктивності: чому «швидше» залежить від навантаження

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

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

PostgreSQL і Percona Server (варіант MySQL, орієнтований на продуктивність) обидва можуть бути неймовірно швидкими. Вони також обидва можуть бути болісно повільними. Різниця — не в інтуїції. Вона в формі навантаження, дисципліні експлуатації та в тому, чи випадково ви не бенчмаркуєте кеш.

Міфи про продуктивність, які не вмирають

Міф 1: «Percona Server швидший за PostgreSQL»

Percona Server може бути швидшим за PostgreSQL для класичного MySQL-подібного OLTP: багато коротких індексованих точкових запитів, передбачувані шаблони доступу й команди, які вміють правильно експлуатувати InnoDB. PostgreSQL також може бути швидшим за Percona Server для того ж навантаження, якщо ваші шаблони запитів краще лягають на планувальник Postgres або інстинкти налаштування команди більше відповідають регулюванням і засобам спостереження в Postgres.

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

Міф 2: «PostgreSQL повільний через MVCC-блоат»

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

Міф 3: «MySQL завжди швидший для читань»

Читання — це не однорідна річ. Є точкові читання, скани діапазонів, fan-out join’и, аналітичні агрегати і «читання», які насправді є очікуванням блокувань. InnoDB може летіти на точкових читаннях з гарячим буферним пулом та простою схемою. Postgres теж може летіти — особливо з хорошими індексами, ефективним кешуванням і планами запитів, що уникають неочікуваних вкладених циклів.

Міф 4: «Бенчмарки все вирішують»

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

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

Міф 5: «Зміна движка вирішує проблеми продуктивності»

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

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

  • Родовід PostgreSQL бере початок від POSTGRES в UC Berkeley у 1980-х; назва «PostgreSQL» відображає раннє додавання SQL, яке закріпилося.
  • InnoDB став стандартним движком для MySQL у версії 5.5; до того MyISAM був поширений і давав зовсім іншу історію блокувань і стійкості.
  • Percona Server з’явився, бо багато команд хотіли сумісності з MySQL плюс інструменти інструментації й особливості продуктивності без очікування випусків upstream.
  • Postgres додав паралельне виконання запитів поступово (не відразу), і це змінило розрахунки для певних звітних навантажень, які раніше стандартно віддавали в Spark.
  • Історія реплікації MySQL починалася зі statement-based реплікації, що була швидшою в деяких випадках, але могла бути недетермінованою; row-based реплікація покращила коректність ціною додаткових витрат.
  • WAL у Postgres (журнал попереднього запису) завжди був центральним, але функції на кшталт logical decoding спростили побудову pipeline’ів змін даних без тригерів.
  • Буферний пул InnoDB — один із найважливіших важелів продуктивності MySQL/Percona; помилка в його налаштуванні може зробити швидкий сервер схожим на повільний диск.
  • Vacuum у Postgres не є опційним; це частина того, як MVCC підтримує стабільність продуктивності. Autovacuum існує, бо люди ненадійні.
  • Обидві екосистеми дозріли до рівня «справжніх» корпоративних систем; сьогоднішній аргумент рідко про можливості і в основному про відповідність і операції.

Що означає «швидше»: архетипи навантажень

1) Hot-key OLTP: багато точкових читань/записів на невеликому робочому наборі

Саме тут Percona Server (InnoDB) часто виглядає відмінно: гарячі сторінки сидять у буферному пулі, працюють механізми буферизації змін і адаптивна поведінка, а движок оптимізовано під стиль «багато дрібних запитів». Postgres теж може так працювати, але вам доведеться більше піклуватися про управління зʼєднаннями, вибір індексів і уникнення транзакційних шаблонів, що блокують vacuum.

Типова пастка: ви бенчмаркуєте на крихітному наборі даних, який поміщається в ОЗП, і оголошуєте перемогу. У продакшені робочий набір більший, і раптом ви вимірюєте латентність сховища і заміну буферів замість SQL.

2) Інтенсивний запис OLTP: постійні вставки/оновлення з вимогами на стійкість

Тут «швидше» обмежується обсягом журналу, поведінкою fsync і чекпоінтів. У Postgres важливі обсяг WAL і налаштування чекпоінтів, як і те, щоб autovacuum встигав. В InnoDB домінують розмір redo логу, поведінка флешу та подвійний запис (doublewrite). Обидва можуть впертися в I/O-стіни; вони просто вдаряться по різних стінах залежно від налаштувань і схеми.

3) Змішане навантаження з складними join’ами

Планувальник Postgres потужний, а його статистика та варіанти індексування (включно з expression та partial індексами) можуть дати дуже сильні плани для складних реляційних запитів. MySQL/Percona значно покращився, але є навантаження, для яких Postgres часто є «менш дивним» движком для join’ів і просунутої семантики SQL.

Точка рішення: якщо ваш застосунок орієнтується на реляційний дизайн і складні звітні запити поряд з OLTP, Postgres часто зменшує кількість випадків «ми переписали SQL, щоб задобрити оптимізатор».

4) Реплікація читань і фан-аут читань

Реплікація — це продуктивність. Якщо ви не можете масштабувати читання без того, щоб затримка реплікації не стала багом продукту, ваш «швидкий» первинний вузол не має значення. У Percona Server часто покладаються на флот реплік; у Postgres теж, але операційні шаблони відрізняються (фізична стрімінг-реплікація, логічна реплікація та розширення).

5) «Нудні» адміністративні роботи: бекапи, зміни схеми та відновлення

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

Де движки відрізняються (в частинах, які вам болять)

Контроль конкуренції: MVCC не однаковий скрізь

Обидві системи використовують концепти MVCC, але операційні наслідки різняться. Postgres зберігає кілька версій рядків у самій таблиці, а видимість визначається ID транзакцій. Це означає, що мертві кортежі накопичуються, поки vacuum їх не звільнить. Це не помилка; це домовленість.

InnoDB також використовує MVCC з undo-логами. Старі версії живуть в undo-сегментах, а не як мертві кортежі в сторінках таблиці. Це змінює форму «блоату» і симптоми, які ви бачите: довгі транзакції можуть перешкоджати очищенню, росте довжина історії і виникає деградація продуктивності в інший спосіб.

Стійкість і патерни I/O: WAL проти redo/undo + doublewrite

Postgres записує WAL-рядки та пізніше скидає брудні сторінки; чекпоінти можуть створювати піки записів при поганому налаштуванні. InnoDB має redo-логи та data-файли, плюс механізми як doublewrite для захисту від часткових записів сторінок. Регулювальні ручки відрізняються, але фізика одна: ваш підсистема зберігання має бюджет, і база даних його витрачає.

«Податок оптимізатора»: планувальники — упереджені машини

Планувальник Postgres може робити блискучі вибори і іноді жахливі, коли статистика застаріла або розподіли дивні. Оптимізатор MySQL/Percona має свої особливі моменти, особливо навколо складних join’ів і певних патернів підзапитів. Ви не обираєте «кращий оптимізатор». Ви обираєте оптимізатор, який ви можете передбачити, інструментувати і виправляти.

З’єднання: Postgres карає спалахи з’єднань

Postgres за замовчуванням використовує модель процес-на-з’єднання. Громада короткоживих з’єднань може стати самостійною DoS. Фікс — не «зробити базу більшу». Фікс — pooling (часто PgBouncer), правельне обмеження max connections і навчання застосунку повторно використовувати з’єднання.

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

Спостережуваність: обидва мають інструменти, але дефолти різні

Postgres з pg_stat_statements дає вам цінну видимість запитів. Percona Server відомий додатковою інструментацією і широкою екосистемою Percona toolkit. У будь-якому разі, якщо ви не збираєте правильні метрики й трасування запитів, ви в кінці кінців будете «налаштовувати» на основі забобонів.

Одна цитата, бо операційникам заслугою хоч одну хорошу фразу: Надія — не стратегія. — Vince Lombardi

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

Ось що робити, коли хтось каже: «База даних повільна», і у вас є 15 хвилин, перш ніж канал інцидентів перетвориться на перформанс-арт.

Перше: підтвердіть симптом і одиницю

  • Це латентність чи пропускна здатність? p95 латентність запитів проти загального QPS. Вони ламаються по-різному.
  • Це база даних? Потоки додатка, мережа та зовнішні виклики можуть видавати себе за проблеми БД.
  • Це один клас запитів? Один поганий запит може отруїти весь пул.

Друге: класифікуйте вузьке місце в одну з чотирьох корзин

  1. CPU-bound: високий CPU, стабільний I/O, повільні запити з важкими обчисленнями (сорти, hash join’и, обробка JSON).
  2. I/O-bound: висока латентність читань/записів, низький відсоток попадань у кеш, тиск чекпоінтів/флешів.
  3. Lock/transaction-bound: очікування, конфлікти блокувань, довгі транзакції, затримки застосування на репліках.
  4. Пам’ять/невідповідність робочого набору: треш кешу, заміна буферного пулу, часте використання тимчасових файлів.

Третє: оберіть найкоротший шлях до доказу

  • На Postgres: почніть з pg_stat_activity, pg_stat_statements, співвідношення попадань у кеш, статистики чекпоінтів і прогресу vacuum.
  • На Percona: почніть з SHOW PROCESSLIST, метрик буферного пулу InnoDB, статистики redo/flush і зведення повільних запитів.
  • На хості: перевірте iostat, stalls під тиском, латентність файлової системи і чи тихо не кричить підсистема зберігання.

Ще один короткий жарт (і все): Найшвидша база даних — та, на яку ви випадково не спрямували звітну задачу.

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

Це реальні завдання, які можна виконати під час інциденту або огляду продуктивності. Кожне містить: команду, приклад виводу, інтерпретацію і наступне рішення. Команди розбиті між PostgreSQL і Percona/MySQL, плюс перевірки на рівні ОС, що вирішують, куди копати далі.

Завдання 1 (PostgreSQL): Побачити, що виконується і що чекає

cr0x@server:~$ psql -X -d appdb -c "select pid, usename, state, wait_event_type, wait_event, now()-query_start as age, left(query,80) as query from pg_stat_activity where state <> 'idle' order by age desc limit 10;"
 pid  | usename | state  | wait_event_type |  wait_event   |   age    |                                      query
------+--------+--------+-----------------+---------------+----------+--------------------------------------------------------------------------------
 8123 | app    | active | Lock            | transactionid | 00:02:11 | update orders set status='paid' where id=$1
 7991 | app    | active | IO              | DataFileRead  | 00:01:05 | select * from order_items where order_id=$1
 7902 | app    | active |                 |               | 00:00:40 | select count(*) from orders where created_at > now()-interval '1 day'

Що це означає: Маємо очікування блокування на transactionid (часто конфлікти на рівні рядка, що ескалюють до транзакційних очікувань) і I/O-очікування на читання файлів даних.

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

Завдання 2 (PostgreSQL): Визначити блокувальника

cr0x@server:~$ psql -X -d appdb -c "select blocked.pid as blocked_pid, blocker.pid as blocker_pid, blocked.query as blocked_query, blocker.query as blocker_query from pg_locks blocked_locks join pg_stat_activity blocked on blocked_locks.pid=blocked.pid join pg_locks blocker_locks on blocked_locks.locktype=blocker_locks.locktype and blocked_locks.database is not distinct from blocker_locks.database and blocked_locks.relation is not distinct from blocker_locks.relation and blocked_locks.page is not distinct from blocker_locks.page and blocked_locks.tuple is not distinct from blocker_locks.tuple and blocked_locks.virtualxid is not distinct from blocker_locks.virtualxid and blocked_locks.transactionid is not distinct from blocker_locks.transactionid and blocked_locks.classid is not distinct from blocker_locks.classid and blocked_locks.objid is not distinct from blocker_locks.objid and blocked_locks.objsubid is not distinct from blocker_locks.objsubid and blocker_locks.pid != blocked_locks.pid join pg_stat_activity blocker on blocker_locks.pid=blocker.pid where not blocked_locks.granted;"
 blocked_pid | blocker_pid |              blocked_query               |                blocker_query
------------+------------+------------------------------------------+---------------------------------------------
       8123 |       7001 | update orders set status='paid' where id=$1 | begin; update orders set ...; -- long txn

Що це означає: PID 7001 утримує блокування. Зверніть увагу на шаблон begin;: довга транзакція.

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

Завдання 3 (PostgreSQL): Топ запитів за загальним часом (вимагає pg_stat_statements)

cr0x@server:~$ psql -X -d appdb -c "select calls, round(total_exec_time::numeric,1) as total_ms, round(mean_exec_time::numeric,2) as mean_ms, rows, left(query,100) as query from pg_stat_statements order by total_exec_time desc limit 5;"
 calls | total_ms | mean_ms | rows  |                                                query
-------+----------+---------+-------+-----------------------------------------------------------------------------------------------------
 12000 | 980000.0 |   81.67 | 12000 | select * from order_items where order_id = $1
   800 | 410000.0 |  512.50 |   800 | select count(*) from orders where created_at > now()-interval '1 day'
   300 | 220000.0 |  733.33 |  9000 | select * from orders o join customers c on c.id=o.customer_id where c.email like $1

Що це означає: Один запит домінує за загальним часом через велику кількість викликів; інший повільний на виклик.

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

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

cr0x@server:~$ psql -X -d appdb -c "select datname, round(100*blks_hit/nullif(blks_hit+blks_read,0),2) as cache_hit_pct, blks_read from pg_stat_database where datname='appdb';"
 datname | cache_hit_pct | blks_read
--------+---------------+-----------
 appdb  |         93.41 |   1829934

Що це означає: 93% може бути добре або погано залежно від навантаження. Але якщо вчора було 99% — робочий набір виріс або пам’ять зменшилась.

Рішення: Якщо відсоток попадань падає й читається більше — перевірте тиск на ОЗП, коректність shared_buffers і чи новий шаблон запитів не сканує великі таблиці.

Завдання 5 (PostgreSQL): Знайти таблиці з ознаками блоату (мертві кортежі)

cr0x@server:~$ psql -X -d appdb -c "select relname, n_live_tup, n_dead_tup, round(100*n_dead_tup/nullif(n_live_tup+n_dead_tup,0),2) as dead_pct from pg_stat_user_tables order by n_dead_tup desc limit 5;"
  relname   | n_live_tup | n_dead_tup | dead_pct
------------+------------+------------+---------
 orders     |   12500000 |    4100000 |   24.69
 sessions   |     800000 |    2900000 |   78.38
 events     |   90000000 |    1200000 |    1.32

Що це означає: sessions — фабрика блоату. Імовірно часті оновлення/видалення плюс поганий прогрес vacuum, можливо довгі транзакції.

Рішення: Виправте шаблон churn (TTL-партіціонування, менше оновлень), і налаштуйте autovacuum для цієї таблиці. Якщо вона вже величезна — плануйте контрольований VACUUM (FULL) або перезапис таблиці у вікні обслуговування.

Завдання 6 (PostgreSQL): Перевірити, чи чекпоінти вам дошкуляють

cr0x@server:~$ psql -X -d appdb -c "select checkpoints_timed, checkpoints_req, round(checkpoint_write_time/1000.0,1) as write_s, round(checkpoint_sync_time/1000.0,1) as sync_s, buffers_checkpoint from pg_stat_bgwriter;"
 checkpoints_timed | checkpoints_req | write_s | sync_s | buffers_checkpoint
------------------+-----------------+---------+--------+-------------------
              124 |             198 |  905.2  |  210.3 |          88412211

Що це означає: Багато запитаних чекпоінтів свідчить про тиск WAL або налаштування, що примушують часті чекпоінти. Це може створювати I/O-сплески і латентність.

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

Завдання 7 (Percona/MySQL): Хто виконується й хто завис

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G"
*************************** 1. row ***************************
     Id: 22341
   User: app
   Host: 10.0.2.19:51244
     db: appdb
Command: Query
   Time: 132
  State: Waiting for row lock
   Info: update orders set status='paid' where id=?
*************************** 2. row ***************************
     Id: 22110
   User: app
   Host: 10.0.2.20:49821
     db: appdb
Command: Query
   Time: 141
  State: Sending data
   Info: select * from order_items where order_id=?

Що це означає: Маємо очікування блокувань рядків і запит, що застряг у стані «Sending data» (часто означає, що він сканує/повертає багато рядків або чекає на I/O).

Рішення: Для очікувань рядків — ідентифікуйте блокуючу транзакцію. Для «Sending data» — перевірте план запиту й індекси і підтвердіть попадання в буферний пул і читання з диска.

Завдання 8 (Percona/MySQL): Знайти очікування InnoDB і блокувальника

cr0x@server:~$ mysql -e "SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id=w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id=w.requesting_trx_id\G"
*************************** 1. row ***************************
 waiting_trx_id: 9012231
 waiting_thread: 22341
  waiting_query: update orders set status='paid' where id=?
blocking_trx_id: 9012198
blocking_thread: 21990
 blocking_query: update orders set status='processing' where id=?; -- txn open

Що це означає: Потік 21990 блокує. Імовірно довга транзакція або потік застосунку, що утримує блокування занадто довго.

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

Завдання 9 (Percona/MySQL): Перевірити ефективність буферного пулу

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+------------+
| Variable_name                         | Value      |
+---------------------------------------+------------+
| Innodb_buffer_pool_read_requests      | 9834421190 |
| Innodb_buffer_pool_reads              | 8832211    |
+---------------------------------------+------------+

Що це означає: Співвідношення пристойне (читання з диска — невелика частка від запитів). Якщо Innodb_buffer_pool_reads швидко росте — ви втрачаєте кеш.

Рішення: Якщо читань з диска багато — збільшіть розмір буферного пулу (у межах ОЗП) або зменшіть робочий набір через індекси і зміни запитів.

Завдання 10 (Percona/MySQL): Шукати тиск redo логу / чекпоінтів

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
=====================================
2025-12-30 10:12:01 0x7f2c3c0a0700 INNODB MONITOR OUTPUT
=====================================
Log sequence number          146990331122
Log flushed up to            146990220981
Last checkpoint at           146988102000
0 pending log flushes, 0 pending chkp writes

Що це означає: LSN, позиції flushed і чекпоінтів показують, наскільки відстає запис/флеш. Великі розриви під навантаженням можуть означати тиск redo або обмеження I/O.

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

Завдання 11 (OS): Підтвердити латентність диска і чи ви I/O-bound

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (db01)  12/30/2025  _x86_64_ (32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.21    0.00    4.12   22.33    0.00   55.34

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await wareq-sz  aqu-sz  %util
nvme0n1         220.0  18944.0     0.0   0.00    9.40    86.10   480.0  61200.0   18.70   127.50   12.30  98.00

Що це означає: Високе %iowait, велике w_await і %util близько 100% — сигнал про насичення сховища. Ваша база чекає на диск, який не тримає обіцянок.

Рішення: Не починайте з налаштування SQL. Зменшіть write amplification (індекси, churn), розподіліть I/O (окремі пристрої для WAL/redo і даних, якщо можливо) або оновіть сховище. Також перевірте «шумних сусідів» на загальному диску.

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

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  81264  14000 612000    0    0   120   980 9200 8800 18  4 56 22  0
 3  1      0  64220  13890 590100    0    0   110  1100 9400 9100 17  4 54 25  0

Що це означає: Свопінгу немає (si/so = 0), але є I/O wait. Якби був свопінг — ви б побачили драматичну латентність і випадкові «все повільно» скарги.

Рішення: Якщо є свопінг — зупиніться і виправте розмір пам’яті (буферні пули, shared_buffers, work_mem, тимчасові таблиці). Свопінг для баз даних — це хобі, а не стратегія.

Завдання 13 (PostgreSQL): Підтвердити використання тимчасових файлів (сорти/hashes на диск)

cr0x@server:~$ psql -X -d appdb -c "select datname, temp_files, pg_size_pretty(temp_bytes) as temp_size from pg_stat_database where datname='appdb';"
 datname | temp_files | temp_size
--------+------------+-----------
 appdb  |      41290 | 98 GB

Що це означає: 98 GB тимчасових даних — запити спилюють у файл. Часто причина — погані індекси, неправильні плани або недостатній work_mem для конкретного навантаження (не глобально).

Рішення: Ідентифікуйте запити, що спилюють, через EXPLAIN (ANALYZE, BUFFERS). Спочатку виправляйте запит/індекс. Якщо підвищуєте work_mem — робіть це обережно, щоб не викликати OOM при високій конкуренції.

Завдання 14 (Percona/MySQL): Перевірити, чи slow query log дає правду

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| slow_query_log      | ON    |
| slow_query_log_file | /var/log/mysql/slow.log |
+---------------------+-------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| long_query_time | 0.2   |
+-----------------+-------+

Що це означає: Slow log увімкнено і поріг агресивний для фіксації p95-поведінки (0.2s тут). Якщо встановлено 10s — це фактично інструмент лише для постмортему.

Рішення: Переконайтеся, що slow logging увімкнено в продакшені з розумним порогом і ротацією. Інакше ви будете гадати, який запит вас поклав.

Завдання 15 (PostgreSQL): Швидко перевірити затримку реплікації

cr0x@server:~$ psql -X -d appdb -c "select client_addr, state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
 client_addr |   state   | write_lag | flush_lag | replay_lag
-------------+-----------+-----------+-----------+-----------
 10.0.3.12   | streaming | 00:00:01  | 00:00:02  | 00:00:08

Що це означає: Replay lag — 8 секунд. Для когось це може бути нормою, для когось — катастрофою в залежності від вимог read-your-writes.

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

Завдання 16 (MySQL/Percona): Перевірити здоров’я реплікації і затримку

cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep 'Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Relay_Log_Space'
 Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 37
Relay_Log_Space: 1849423872

Що це означає: SQL-потік працює, але затримка — 37 секунд, і relay-логи великі. Репліка не встигає застосовувати зміни.

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

Три корпоративні міні-історії з полі бою продуктивності

1) Інцидент через неправильне припущення: «Бенчмарк сказав, що Percona швидший»

Компанія була у фазі росту з платіжним навантаженням: багато коротких транзакцій, але також небезпечна бічна звітність, що робила ad-hoc фільтри по кількох великих таблицях. Техлід запустив бенчмарк: прості пошуки по первинному ключу, кілька вставок і акуратний набір даних, що помістився в пам’ять. Percona Server показав кращі цифри за обраним тестом. Рішення прийняте.

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

Сам інцидент був класичним: квартальний звіт запустився під пік. Затримка реплікації зросла. Читання продукту почали потрапляти на застарілі репліки і показувати неконсистентний стан. Підтримка назвала це «корупцією даних». Це не було корупцією — це була затримка плюс припущення. Інженери ганялися за фантомними багами годинами, поки хтось не перевірив Seconds_Behind_Master і slow query log.

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

2) Оптимізація, що повернулася бумерангом: «Ми збільшили пам’ять і прибрали fsync-очікування»

Інша організація використовувала Postgres для пайплайну інґесту подій з інтенсивним записом. Вони бачили періодичні сплески латентності. Хтось вказав на чекпоінти. І дійсно: були видимі сплески запису чекпоінтів, і p99 мав свій ритм.

Початкове налаштування було розумним: коригування темпу чекпоінтів, збільшення WAL-розміру, моніторинг I/O. Але потім «оптимізація» еволюціонувала: підняли work_mem глобально, тому що кілька запитів спилювали, і підняли ліміти з’єднань, бо команда додатка хотіла швидшої паралельності.

Через два дні база почала вбиватися OOM під навантаженням. Не тому, що work_mem — зло, а тому, що це пам’ять на сорти/хеші, помножена на одночасні сесії. Додайте занадто багато з’єднань — і отримаєте пам’яткову бомбу з повільним запаленням. Чекпоінт-сплески були видимою проблемою; налаштування пам’яті — тихим вбивцею.

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

3) Нудна, але правильна практика, що врятувала день: «Ми репетирували відновлення»

Ця історія без геройств. Тут були дорослі люди в кімнаті.

SaaS-компанія працювала на Percona Server з репліками і нічними бекапами. Вони також робили непопулярну річ: квартальні drills відновлення. Не просто «бекап успішний», а реальне відновлення в staging, валідація запитів на рівні застосунку і вимірювання часу відновлення. Рунбук містив реальні команди й оцінки.

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

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

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

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

Корінна причина: сплески записів чекпоінтів через агресивне чекпоінтінг та тиск WAL; сховище не може поглинути сплеск.

Виправлення: Збільшити max_wal_size, налаштувати темп чекпоінтів, перевірити латентність сховища і розглянути розміщення WAL на швидшому носії.

2) Симптом: поступове падіння продуктивності протягом днів (Postgres)

Корінна причина: autovacuum не встигає; довгі транзакції перешкоджають очищенню; блоат росте.

Виправлення: Знайти довгі транзакції, виправити патерни застосунку, налаштувати autovacuum по таблицях і запланувати ремедіацію блоату (reindex, rewrite) там, де потрібно.

3) Симптом: «CPU в порядку, але запити повільні» (обидва)

Корінна причина: I/O wait і промахи кешу; набір даних виріс більше пам’яті; новий шаблон запитів робить скани.

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

4) Симптом: раптові шторми блокувань після релізу (обидва)

Корінна причина: нова область транзакцій, відсутній індекс на предикат оновлення або патерни «select тоді update», що підвищують конфлікти.

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

5) Симптом: репліки відстають у піку, але відновлюються у непік (MySQL/Percona)

Корінна причина: потік застосування на репліці не встигає, важкі читання на репліці або сплески обсягу binlog через масові оновлення.

Виправлення: Оптимізувати патерни записів (уникати масових multi-row оновлень), налаштувати паралельну реплікацію відповідно до навантаження і ізолювати звітність від критичних реплік.

6) Симптом: Postgres «занадто багато з’єднань» і велика кількість контекстних переключень

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

Виправлення: Додати PgBouncer, обмежити розмір пулів у застосунку, знизити max_connections і трактувати кількість з’єднань як показник пропускної спроможності.

7) Симптом: MySQL «Sending data» скрізь

Корінна причина: запити повертають надто багато рядків, відсутні індекси або сканування при прив’язці до диска. Іноді — мережевий зворотний тиск.

Виправлення: EXPLAIN запиту, додати covering індекси, впровадити пагінацію і перевірити, чи проблема в мережі, а не в читаннях з диска.

8) Симптом: продуктивність гірша після «додавання індексу» (обидва)

Корінна причина: ви підвищили write amplification; кожна вставка/оновлення тепер платить за додаткове обслуговування індексу. Або оптимізатор вибрав гірший план.

Виправлення: Додайте лише ті індекси, які ви можете собі дозволити. Перевірте плани запитів до/після. Використовуйте partial/expression індекси (Postgres) або продумані covering індекси (MySQL).

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

Покроково: вибір між PostgreSQL і Percona Server за продуктивністю (без самообману)

  1. Опишіть своє навантаження у 10 запитах, що важливі, з указаною конкуренцією і співвідношенням читань/записів. Якщо не можете — ви не готові робити вибір.
  2. Визначте метрики успіху: p95/p99 для ключових запитів, пропускна здатність при фіксованій латентності, бюджет затримки реплікації, цільовий час відновлення.
  3. Тестуйте на даних, схожих на продакшн: реалістичний скіп. Мінімум — перевищити ОЗП, щоб виміряти поведінку сховища.
  4. Тестуйте режими відмов: затримка репліки під піком, поведінка фейловера і як швидко ви можете відновити.
  5. Спочатку інструментуйте: увімкніть pg_stat_statements або slow logs MySQL і збирайте системні метрики.
  6. Зробіть нудне налаштування: розмір пам’яті, розміри журналів/чекпоінтів, пулінг з’єднань і розумні налаштування autovacuum/flush.
  7. Тільки потім порівнюйте. Якщо один движок перемагає — ви знатимете чому й зможете утримувати перемогу в продакшні.

Операційний чекліст: тримати Postgres швидким

  • Моніторити активність autovacuum і мертві кортежі; сповіщати про довгі транзакції, що блокують vacuum.
  • Обмежити з’єднання і використовувати пулінг для пікової навантаженості.
  • Слідкувати за ростом тимчасових файлів; трактувати спилювання як проблему запиту/індексу перед підвищенням пам’яті.
  • Слідкувати за статистикою чекпоінтів і обсягом WAL; налаштовувати під плавні записи, а не героїчні сплески.
  • Репетирувати відновлення і перевіряти бекапи реальними тестами відновлення.

Операційний чекліст: тримати Percona Server швидким

  • Розмір буферного пулу InnoDB під робочий набір і перевірка через метрики читань.
  • Увімкнути slow query лог з порогом, що відповідає вашим SLO.
  • Моніторити очікування блокувань і довгі транзакції; налаштовувати гарячі рядки і область транзакцій.
  • Стежити за затримками реплікації і ростом relay-логів; не використовувати критичні репліки для звітності.
  • Переконатися, що налаштування стійкості відповідають бізнес-риску, а не фантазіям бенчмарку.

FAQ

1) Чи Percona Server — це «просто MySQL»?

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

2) Чи PostgreSQL завжди повільніший для OLTP?

Ні. Postgres може відмінно працювати для OLTP. Він менш толерантний до неохайного управління з’єднаннями і запущеного vacuum. Якщо експлуатувати його правильно — він дуже конкурентний.

3) Який найшвидший спосіб дізнатися, чи я I/O-bound?

Перевірте iostat -xz на латентність і використання, та корелюйте з метриками рівня БД (Postgres blks_read, читання буферного пулу InnoDB). Якщо латентність диска росте разом з латентністю запитів — відповідь є.

4) Чи варто налаштовувати Postgres, різко збільшуючи shared_buffers?

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

5) Чи просто поставити величезний InnoDB buffer pool?

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

6) Чому бенчмарки показують великі виграші, яких продакшн ніколи не бачить?

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

7) Що краще для складного SQL і звітності?

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

8) Як не помилитися з вибором бази, орієнтуючись на враження?

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

9) Реплікаційна затримка — це проблема продуктивності чи коректності?

І те, і інше. Затримка — це «продуктивність», коли вона ламає SLO, і «коректність», коли продукт передбачає read-your-writes. Трактуйте затримку як метрику першого класу.

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

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

  1. Проінвентаризуйте навантаження: топ-10 шаблонів запитів, обсяг записів, конкуренція і цільові латентності.
  2. Інструментуйте обидві сторони: Postgres з pg_stat_statements і views активності; Percona з slow logs і InnoDB status, плюс метрики хосту.
  3. Запустіть реалістичний тест: набір даних більший за ОЗП, ті ж індекси, той самий мікс запитів, ті ж очікування по стійкості.
  4. Зробіть один швидкий drill діагностики: навмисно навантажте, знайдіть вузьке місце за планом і подивіться, у якій системі ваша команда швидше розуміє проблему.
  5. Оберіть движок, який ви зможете тримати швидким: не того, що виграє демо, а того, який ви зможете чисто експлуатувати о 3-й годині ночі.
← Попередня
Ubuntu 24.04: Продуктивність упала після оновлення — перші 6 перевірок, що виявляють винуватця
Наступна →
Інженер «Bus Factor»: коли одна людина контролює всю систему

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