Більшість відмов баз даних у продакшені спричинені не якимось новим багом. Вони викликані припущенням, про яке ви не знали—бо налаштування бази даних за замовчуванням створювало відчуття безпеки. Ви деплоїте код, який «працює» в стаджингу, а продакшен навчає вас іншого значення слова «працює».
MariaDB і PostgreSQL — обидві відмінні. Водночас вони мають протилежні погляди. За замовчуванням MariaDB часто намагається тримати додаток у роботі, навіть якщо дані стають трохи… інтерпретативними. Налаштування PostgreSQL скоріше зупинить поїзд на станції і змусить вас пояснити свої дії. Ця різниця не академічна. Вона впливає на частоту інцидентів, час відлагодження та тип помилок, які ваша команда може робити.
Тезис: прощення проти покарання
У продакшені «прощати» не означає «дружньо». Це означає, що база даних спробує інтерпретувати ваш намір, і ця інтерпретація може відрізнятися від того, чого хотіли ваші аудитори, аналітики або ваше майбутнє «я».
MariaDB (особливо у сумісному з MySQL середовищі) має довгу традицію поблажливості: приведення типів, прийняття дивних значень дати залежно від модів, тихе обрізання та загалом прагнення не ламати додатки. PostgreSQL зазвичай суворіший: він кидає помилки там, де є неоднозначність, вимагає явних перетворень і змушує стикатися з обмеженнями на ранньому етапі. Одна підходить для підтримки апу в житті. Інша — для збереження цілісності даних.
Обидва підходи можуть бути правильними. Головне — обрати бажаний режим відмови:
- Дух за замовчуванням у MariaDB: «Ми щось збережемо; не піднімайте паніку.» Підходить, коли uptime — головний пріоритет і є надійна валідація на вході. Небезпечно, коли важлива коректність даних і її немає.
- Дух за замовчуванням у PostgreSQL: «Це неоднозначно; виправте код.» Підходить, коли коректність важлива і ви хочете, щоб помилки проявлялися раніше. Небезпечно, коли ви не можете терпіти помилки запису, а додаток не вміє правильно повторювати операції.
Жарт 1: MariaDB іноді відчувається як база даних, що каже «гаразд» так само, як втомлений бармен каже «гаразд». Вранці ви пошкодуєте.
Якщо ви будуєте системи, що впливають на гроші, запаси, контроль доступу, відповідність або все, що закінчується зустріччю з юристами — обирайте «карайте помилки на ранньому етапі». Якщо ви запускаєте високонавантажений пайплайн інжесту подій із надійною валідацією на краях — поблажливі налаштування можуть бути прийнятні, за умови що ви робите їх явними та спостережуваними.
Цікаві факти та історія (те, що досі має значення)
- MariaDB виникла через страх перед поглинанням: її створили після поглинання MySQL (Sun, потім Oracle), щоб зберегти форк під керівництвом спільноти з сумісністю як ключовою обіцянкою.
- Корені PostgreSQL академічні, але культура консервативна: він походить від дослідження POSTGRES; проект історично віддавав перевагу коректності та стандартам, а не «будь-якому рішенню, що працює».
- Поблажлива поведінка MySQL сформувала екосистему: ORM-інструменти та код додатків навчилися покладатися на тихі приведення. Перенесення таких додатків у Postgres часто виявляє «баги», які завжди були.
- InnoDB став стандартним движком у MySQL через проблеми MyISAM: транзакції та відновлення після збоїв перестають бути опцією після першого серйозного інциденту.
- MVCC у PostgreSQL центральний для операційної історії: він уникає блокувань читач/запис у багатьох випадках, але «оплачується» вимогою вакуумації та потенційним роздуванням, якщо ігнорувати прибирання.
- Екосистема MariaDB з часом розпалась: функції та налаштування розійшлися між MariaDB, MySQL і Percona Server; операційні рецепти не завжди портовані, навіть коли синтаксис SQL схожий.
- Культура розширень Postgres — її супервлада: ключові операційні можливості часто з’являються як розширення (для статистики, індексування, інструментів партиціювання). Це дає гнучкість, але й більше ручок для підтримки.
- Відповідність стандарту SQL — це практичний інструмент, а не трофей: Postgres ближчий до стандарту; це змінює передбачуваність семантики запитів при міграціях і між драйверами.
Налаштування за замовчуванням, що вирішують вашу долю
SQL-моди та «корисні» приведення
Якщо є одна причина, чому MariaDB здається поблажливою, то вона в тому, що часто приймає ваші дані й перетворює їх у щось зберігане. Іноді вона попереджає; іноді ви цього не помітите, бо бібліотека клієнта не показує попереджень. Ваш моніторинг, ймовірно, теж їх не відстежує.
MariaDB: поведінка сильно залежить від sql_mode. Без строгих модів вставки, що виходять за діапазон, обрізані або містять недійсні значення, можуть пройтити з попередженнями. У строгих режимах багато з таких випадків стають жорсткими помилками. База даних може бути або поблажливою, або караючою; проблема в тому, що багато середовищ не роблять вибір явно.
PostgreSQL: схильний кидати помилки при недійсних перетвореннях, значеннях поза діапазоном, недійсних відмітках часу тощо. Це операційно «гучно», але також запобігає тому, щоб погані дані тихо накопичувалися й перетворювалися на довготривалий інцидент, який виявиться в аналітиці через шість місяців.
Що це змінює на практиці:
- У MariaDB ви повинні трактувати
sql_modeяк контракт продакшену і версіонувати його як код. - У Postgres ви повинні трактувати повтори операцій і обробку помилок додатком як контракт продакшену і тестувати їх під реалістичною конкурентністю.
Транзакції та ізоляція: що ви отримуєте безкоштовно
Рівні ізоляції за замовчуванням — це не дрібниці. Це різниця між «нашi лічильники іноді дивні» і «у нас інцидент з фінансовою звіркою».
MariaDB (InnoDB): історично за замовчуванням REPEATABLE READ. Це зменшує деякі аномалії, але породжує інші (наприклад, gap locks і більш дивну поведінку блокувань). Воно може робити певні шаблони записів більш схильними до deadlock під навантаженням у спосіб, який команди не передбачають.
PostgreSQL: за замовчуванням READ COMMITTED. Для багатьох навантажень це розумний вибір і знижує деякі сюрпризи з блокуваннями, але допускає non-repeatable reads, якщо ви не використовуєте сильнішу ізоляцію або явні блокування.
Операційний висновок: жодне з цих значень за замовчуванням не є «морально безпечним». Вони безпечні для різних припущень розробників. Якщо ваш додаток припускає «я прочитав, отже це буде незмінним до коміту», Postgres може це порушити, якщо ви цього не попросите. Якщо ваш додаток припускає «блокування прості», InnoDB покарає вас deadlock-ом, що виникає лише в піковий трафік.
Набори символів і сортування: мовчазна корупція
Проблеми кодування і сортування рідко будять вас о 2-й ночі. Вони турбують під час міграції, при злитті даних під час поглинання або коли ви починаєте обслуговувати користувачів новою мовою. І це гірше, бо тепер проблема стає також політичною, а не лише технічною.
MariaDB: розгортання історично були різними: latin1 було поширеним, utf8 (що в старих MySQL/MariaDB контекстах могло означати «3-байтовий UTF-8») викликало сюрпризи, а новіші дефолти схиляються до utf8mb4 залежно від версії та конфігурації. Коллації відрізняються, і деякі коллації змінювали свою поведінку між версіями.
PostgreSQL: кодування задається при створенні бази і зазвичай це UTF-8 у сучасних розгортаннях, але коллації залежать від налаштувань ОС/ICU. Це чудово, доки ви не відновите дамп на іншому образі ОС і порядок сортування зміниться тонко.
Моя сильна порада: обирайте UTF-8 по всьому стеку, і в обох системах явно вказуйте коллації для сортування/пошуку, видимого користувачеві. «За замовчуванням коллація» — це не стратегія; це нетестована залежність.
Обмеження, зовнішні ключі та як помилки проявляються
Обмеження — це те, як ви перетворюєте поведінку бази даних з «найкраще намагання» на «контракт». Якщо ви не визначили контракт, він все одно існує — просто ви не знаєте який.
PostgreSQL: обмеження — першокласні і часто використовуються. Існують відкладені обмеження (deferred), які корисні, коли ви знаєте, що робите. Postgres охоче застосує ваші правила і голосно відхилить порушення.
MariaDB: підтримує зовнішні ключі в InnoDB, але багато екосистем MariaDB/MySQL історично недооцінювали їх, часто через спадщину MyISAM, побоювання щодо реплікації або страх перед витратами на запис. Результат: цілісність реалізовується кодом додатка — поки не перестає.
Якщо ви переходите з MariaDB на Postgres, ви часто виявите, що ваші дані вже порушують ті обмеження, у які ви завжди вірили. Postgres не «висуває труднощів». Він виконує ту роботу, яку ви не попросили MariaDB виконати.
Autovacuum проти purge: прибирання — це політика
MVCC у PostgreSQL означає, що рядки не зникають при видаленні; вони стають мертвими кортежами і мають бути vacuum-овані. Autovacuum працює за замовчуванням, але налаштований під «середнє», а ваш робочий профіль ніколи не є середнім. Якщо ви ігноруєте його, продуктивність повільно деградує, а потім раптово падає, і перший симптом зазвичай — «диск заповнений» або «запити неймовірно повільні».
В InnoDB у вас інша динаміка прибирання: undo логи, purge-потоки і довжина списку історії. Це не «немає vacuum», це «інше підпорядкування». В обох системах дефолти спрямовані на безпеку й загальність, а не на ваш конкретний патерн записів.
Тут поділ «прощення/покарання» інвертується. Postgres збереже ваші читання консистентними, але покарає вас пізніше, якщо ви не вакуумисте. MariaDB/InnoDB може продовжувати працювати, але покарає вас довгими паузами, відставанням purge або проблемами реплікації, коли внутрішнє прибирання не встигає.
Налаштування реплікації: компроміси консистентності та доступності
Реплікація — місце, де налаштування стають політикою: чи хочете ви підтверджувати запис до того, як він стане стійким на репліці? Чи має первинка чекати? Чи читати з реплік, які можуть бути застарілими?
Реплікація MariaDB: часто стартує як асинхронна. Її легко налаштувати, легко неправильно зрозуміти і вона дуже добре робить постмортеми типу «ми втратили дані під час фейловера», якщо ви явно не спроєктували поведінку.
Стрімінг-реплікація PostgreSQL: також часто асинхронна за замовчуванням, але синхронна реплікація — поширений і добре підтриманий патерн. СУБД змусить вас явно вказати, чи хочете ви семантику «без втрат даних», і змусить вас заплатити за це латентністю.
Дефолти не звільняють вас від фізики. Вони просто вирішують, хто здивується першим: ви чи ваші клієнти.
План швидкої діагностики
Коли збільшується латентність або росте кількість помилок, потрібна послідовність дій, що працює під тиском. Не філософська дискусія. Це порядок, який найшвидше знаходить вузьке місце як в MariaDB, так і в Postgres.
Перш за все: підтвердіть вид болю (CPU, IO, блокування чи насичення)
- Запити повільні, чи вони чекають? «Очікування» зазвичай означає блокування, IO або насичення пулу з’єднань.
- CPU бази даних завантажений? Думайте про поганий план, відсутній індекс, зсув статистики або забагато конкурентних воркерів.
- IO на максимумі? Мисліть про роздування/вакуумацію, чекпоінти, занадто малий buffer pool, випадкові зчитування або «шумних сусідів».
- Підключення вичерпані? Думайте про неправильні налаштування пулу, вихід клієнтів з-під контролю або повільні запити, що утримують з’єднання.
Друге: ідентифікуйте топ-офендер за очікуваннями/блокуваннями
- PostgreSQL: дивіться в
pg_stat_activityі події очікування; знайдіть блокувальників і довгі транзакції. - MariaDB: дивіться
SHOW PROCESSLIST, статус InnoDB і подання транзакцій/блокувань.
Третє: перевірте здоров’я прибирання движка збереження
- PostgreSQL: прогрес autovacuum, мертві кортежі, підозра на роздування, тиск чекпоінтів.
- MariaDB/InnoDB: довжина history list, відставання purge, коефіцієнт хітності buffer pool, налаштування IO-ємності.
Четверте: перевірте реплікацію та бекапи (бо реагування на інциденти гірше, якщо не можна переключитися)
- Затримка реплікації і помилки.
- Тренди заповнення диска та зростання WAL/binlog.
- Свіжість бекапів і довіра до відновлення.
Перефразована ідея (приписується): Джин Кім часто підкреслював операційну істину, що швидкий зворотний зв’язок кращий за героїзм; скоротіть цикл і ви запобіжите нічним сюрпризам.
Практичні завдання з командами (що означає вихід, яке рішення прийняти)
Це базові перевірки, які ви запускаєте під час інцидентів, робіт з продуктивністю та міграцій. Кожне завдання містить команду, приклад виводу, що це означає і яке рішення це підштовхує.
Завдання 1: Визначити SQL-mode MariaDB (рівень строгості і ризик приведень)
cr0x@server:~$ mysql -uroot -p -e "SELECT @@GLOBAL.sql_mode, @@SESSION.sql_mode\G"
Enter password:
*************************** 1. row ***************************
@@GLOBAL.sql_mode: STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
@@SESSION.sql_mode: STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
Що це означає: увімкнено строгий режим, тож обрізання/недійсні значення швидше спричинять помилки, а не попередження.
Рішення: якщо строгі моди відсутні в продакшені, вирівняйте їх зі стаджингом, оновіть обробку помилок в додатку і заплануйте очистку даних перед увімкненням строгості.
Завдання 2: Перевірити попередження MariaDB після підозрілої вставки (детектор тихої шкоди даних)
cr0x@server:~$ mysql -uroot -p -e "INSERT INTO t(amount) VALUES ('999999999999'); SHOW WARNINGS;"
Enter password:
Level Code Message
Warning 1264 Out of range value for column 'amount' at row 1
Що це означає: вставка пройшла, але значення було приведене/обрізане залежно від типу колонки і режиму.
Рішення: якщо ви бачите попередження на критичних шляхах, або посилюйте sql_mode, або виправляйте валідацію додатку і почніть збирати попередження в логах/метриках.
Завдання 3: Підтвердити рівень ізоляції PostgreSQL (відлагодження «він змінився під час мене» при читанні)
cr0x@server:~$ psql -U postgres -d appdb -c "SHOW default_transaction_isolation;"
default_transaction_isolation
-----------------------------
read committed
(1 row)
Що це означає: кожен запит бачить свіжий знімок; у межах транзакції пізніші запити можуть побачити закриті інші сесіями зміни.
Рішення: якщо додаток припускає repeatable reads, виправте додаток або явно використовуйте REPEATABLE READ для критичних транзакцій.
Завдання 4: Знайти сесії Postgres, що чекають на блокування (швидкий шлях до «хто блокує?»)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT pid, usename, state, wait_event_type, wait_event, now()-query_start AS age, left(query,120) FROM pg_stat_activity WHERE wait_event_type IS NOT NULL ORDER BY age DESC LIMIT 10;"
pid | usename | state | wait_event_type | wait_event | age | left
------+--------+--------+-----------------+---------------+----------+-----------------------------------------------------------
8421 | app | active | Lock | transactionid | 00:03:12 | UPDATE orders SET status='paid' WHERE id=$1
8399 | app | active | Lock | relation | 00:01:48 | ALTER TABLE orders ADD COLUMN notes text
(2 rows)
Що це означає: UPDATE чекає на lock за transactionid; ALTER чекає на lock за relation. DDL може блокувати DML.
Рішення: наступним кроком знайдіть блокувальника (Завдання 5). Якщо це випадковий DDL під час піку — завершіть його або відкладіть і зробіть міграцію правильно.
Завдання 5: Ідентифікувати блокуючий запит у Postgres (перетворити «таємницю» на ім’я)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT blocked.pid AS blocked_pid, blocker.pid AS blocker_pid, now()-blocker.query_start AS blocker_age, left(blocker.query,120) AS blocker_query FROM pg_locks blocked JOIN pg_locks blocker ON blocker.locktype=blocked.locktype AND blocker.database IS NOT DISTINCT FROM blocked.database AND blocker.relation IS NOT DISTINCT FROM blocked.relation AND blocker.page IS NOT DISTINCT FROM blocked.page AND blocker.tuple IS NOT DISTINCT FROM blocked.tuple AND blocker.virtualxid IS NOT DISTINCT FROM blocked.virtualxid AND blocker.transactionid IS NOT DISTINCT FROM blocked.transactionid AND blocker.classid IS NOT DISTINCT FROM blocked.classid AND blocker.objid IS NOT DISTINCT FROM blocked.objid AND blocker.objsubid IS NOT DISTINCT FROM blocked.objsubid AND blocker.pid!=blocked.pid JOIN pg_stat_activity blocked_act ON blocked_act.pid=blocked.pid JOIN pg_stat_activity blocker ON blocker.pid=blocker.pid WHERE NOT blocked.granted AND blocker.granted LIMIT 5;"
blocked_pid | blocker_pid | blocker_age | blocker_query
------------+------------+-------------+---------------------------------------------------------
8421 | 8302 | 00:06:40 | BEGIN; SELECT * FROM orders WHERE id=$1 FOR UPDATE;
(1 row)
Що це означає: довга транзакція, що утримує row lock, блокує оновлення.
Рішення: виправте шлях в додатку (відсутній commit/rollback), додайте таймаути і подумайте про завершення блокувальника, якщо це безпечно і інцидент цього вимагає.
Завдання 6: Швидка підказка про блокування/транзакції в MariaDB через processlist
cr0x@server:~$ mysql -uroot -p -e "SHOW FULL PROCESSLIST;"
Enter password:
Id User Host db Command Time State Info
21 app 10.0.2.15 appdb Query 187 Waiting for table metadata lock ALTER TABLE orders ADD COLUMN notes TEXT
34 app 10.0.2.16 appdb Query 183 Updating UPDATE orders SET status='paid' WHERE id=?
Що це означає: DDL чекає metadata lock; залежно від таймінгу, воно також може блокувати інших.
Рішення: не робіть онлайн DDL наївно. Використовуйте підхід міграції, що уникатиме довгих metadata lock, і плануйте такі операції.
Завдання 7: Швидка перевірка здоров’я InnoDB (відставання purge та підказки про deadlock)
cr0x@server:~$ mysql -uroot -p -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
Enter password:
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2025-12-30 01:42:11 0x7f4a4c1f9700 INNODB MONITOR OUTPUT
=====================================
...
History list length 148732
...
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 923847, ACTIVE 3 sec starting index read
...
Що це означає: велика довжина history list вказує на відставання purge, часто через довгі транзакції. Розділ про deadlock показує шаблони.
Рішення: шукайте довгі транзакції, виправляйте область транзакцій в додатку і налаштовуйте purge/IO-ємність лише після усунення довгих транзакцій.
Завдання 8: Стан autovacuum у PostgreSQL по таблицях (рання тривога про роздування)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT relname, n_live_tup, n_dead_tup, last_autovacuum, vacuum_count, autovacuum_count FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;"
relname | n_live_tup | n_dead_tup | last_autovacuum | vacuum_count | autovacuum_count
------------+------------+------------+------------------------+--------------+------------------
events | 81234921 | 23999110 | 2025-12-29 22:11:05+00 | 0 | 148
orders | 392110 | 88112 | 2025-12-29 20:03:12+00 | 2 | 67
(2 rows)
Що це означає: багато мертвих кортежів у events і часті autovacuum. Можливо, він справляється — або ледве встигає.
Рішення: якщо мертві кортежі залишаються високими, налаштуйте пороги autovacuum для таблиць, розгляньте партиціюцію і перевірте довгі транзакції, що блокують прибирання.
Завдання 9: Перевірка «чіпляємось за чекпоінти?» у Postgres
cr0x@server:~$ psql -U postgres -d appdb -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
------------------+-----------------+-----------------------+----------------------+-------------------
214 | 987 | 8241132 | 119883 | 18933211
(1 row)
Що це означає: багато запитуваних чекпоінтів відносно таймерних: система змушує чекпоінти, часто через обсяг WAL і налаштування.
Рішення: відрегулюйте налаштування чекпоінтів/WAL і/або зменшіть write amplification (індекси, роздування). Також перевірте затримку сховища; повільні диски перетворюють чекпоінти на видимий інцидент.
Завдання 10: Частота хітів buffer pool і брудні сторінки у MariaDB (чи працює кеш?)
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';"
Enter password:
Variable_name Value
Innodb_buffer_pool_read_requests 9134829123
Innodb_buffer_pool_reads 18239481
Variable_name Value
Innodb_buffer_pool_pages_dirty 48211
Що це означає: якщо Innodb_buffer_pool_reads високе відносно запитів, ви промахуєтеся в кеші і робите дискові зчитування. Брудні сторінки показують тиск на запис.
Рішення: якщо промахи кеша домінують і у вас є RAM — збільшіть buffer pool. Якщо брудні сторінки довго високі і IO насичено — перегляньте налаштування flush і патерни запису.
Завдання 11: Виявити топ SQL у Postgres через pg_stat_statements (хто «спалює» CPU?)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT calls, total_exec_time::int AS total_ms, mean_exec_time::numeric(10,2) AS mean_ms, rows, left(query,100) FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
calls | total_ms | mean_ms | rows | left
-------+----------+---------+--------+----------------------------------------------------
89122 | 9123412 | 102.38 | 89122 | SELECT * FROM events WHERE tenant_id=$1 AND ts>=$2 ORDER BY ts DESC LIMIT 100
12211 | 3421901 | 280.21 | 12211 | UPDATE orders SET status=$1, updated_at=now() WHERE id=$2
(2 rows)
Що це означає: є «топовий» запит, що споживає час виконання.
Рішення: запустіть EXPLAIN (ANALYZE, BUFFERS) по цьому запиту, додайте/підкоригуйте індекси або змініть структуру запиту. Також перевірте, чи сортування змушує spill у пам’ять.
Завдання 12: Виявити топ SQL у MariaDB за допомогою slow query log (нудний, але ефективний молот)
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-30T01:38:44.112233Z
# User@Host: app[app] @ 10.0.2.15 []
# Query_time: 4.982 Lock_time: 0.001 Rows_sent: 100 Rows_examined: 81234921
SET timestamp=1735522724;
SELECT * FROM events WHERE tenant_id=42 ORDER BY ts DESC LIMIT 100;
Що це означає: поведінка повного сканування (Rows_examined велика) і ORDER BY ймовірно змушує додаткову роботу.
Рішення: додайте композитний індекс (наприклад, (tenant_id, ts)), перевірте план за допомогою EXPLAIN і тримайте slow log увімкненим у продакшені з розумними порогами.
Завдання 13: Використання диска у Postgres по таблицях (знайти реальних «важковаговиків»)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS total_size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;"
relname | total_size
---------+-----------
events | 412 GB
orders | 38 GB
(2 rows)
Що це означає: events домінує у зберіганні. Це таблиця, що керуватиме вакуумацією, поведінкою кеша і часом бекапу.
Рішення: розгляньте партиціюцію, політики збереження і перегляд індексів. Не налаштовуйте всю базу, коли одна таблиця — це вся погода.
Завдання 14: Перевірка відставання реплікації MariaDB (готовність до фейловеру)
cr0x@server:~$ mysql -uroot -p -e "SHOW SLAVE STATUS\G" | egrep 'Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Last_SQL_Error'
Enter password:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 87
Last_SQL_Error:
Що це означає: реплікація працює, але відстає на 87 секунд. Якщо ви зараз переключитесь, ви втратите до 87 секунд підтверджених записів.
Рішення: якщо ваше RPO близьке до нуля, не прикидайтесь, що це нормально. Виправте причину відставання (IO, довгі транзакції, великі записи) або впровадьте синхронні/напівсинхронні патерни, де це доречно.
Завдання 15: Статус реплікації Postgres та відставання (видимість на первинці)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT application_name, state, sync_state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
application_name | state | sync_state | write_lag | flush_lag | replay_lag
------------------+-----------+------------+-----------+-----------+-----------
replica1 | streaming | async | 00:00:02 | 00:00:03 | 00:00:05
(1 row)
Що це означає: репліка трохи відстає; вона асинхронна, тож можливі втрати при фейловері.
Рішення: вирішіть, чи прийнятна асинхронність. Якщо ні — впровадьте синхронну реплікацію для критичного шляху даних і закладіть витрати на латентність.
Завдання 16: Підтвердити довгі транзакції у Postgres (вбивця vacuum і роздування)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT pid, usename, now()-xact_start AS xact_age, state, left(query,120) FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_age DESC LIMIT 10;"
pid | usename | xact_age | state | left
------+--------+----------+--------+----------------------------------------------------------
8302 | app | 01:12:44 | idle | BEGIN; SELECT * FROM orders WHERE id=$1 FOR UPDATE;
(1 row)
Що це означає: сесія idle_in_transaction утримує блокування і заважає просуванню vacuum.
Рішення: завершіть її, якщо потрібно, потім виправте додаток (налаштування пулу з’єднань, область транзакцій) і встановіть idle_in_transaction_session_timeout.
Три корпоративні міні-історії з поля бою
Міні-історія 1: Інцидент через неправильне припущення (прощальні налаштування приховали погані дані)
Середній маркетплейс роками працював на MariaDB. Команда додатку пишалась своєю «стійкістю». Якщо вхідний сервіс присилав трохи пошкоджені дані, пайплайн інжесту продовжував працювати. Ніяких помилок для користувача. Зелені дашборди. Усі спали спокійно.
Потім фінанси поставили просте питання: «Чому повернення коштів не збігаються з чарджбеками за останній квартал?» Числа були близькими, але не досить, щоб ігнорувати. Розслідування було повільним і принизливим, бо не було одного «поганого деплою». Погані дані були розмазані по місяцям.
Корінь проблеми — припущення: «Недійсні часові позначки приведуть до помилок вставки». У їхньому середовищі MariaDB строгий SQL-mode змінився між кластерами. Деякі кластери приймали певні недійсні значення дати з попередженнями; інші їх відхиляли. Пайплайн не перевіряв попередження. Він зберігав приведені значення, що виглядали як легітимні дати, але сортувалися неправильно. Декілька звітів трактували ці дати як істину і дали тонкі невідповідності.
Виправлення не було героїчним. Вони стандартизували sql_mode, додали валідацію на вході і написали джоб по ремонту даних. Культурне виправлення було складнішим: вони перестали вважати «немає помилок» за «правильно». Вони почали трактувати попередження як дефектний бюджет, який можна бачити і скорочувати.
Міні-історія 2: Оптимізація, що вдарила у відповідь (покарання виявило дефект дизайну додатку)
SaaS-компанія перенесла гарячий шлях з MariaDB на PostgreSQL. Причини були правомірні: кращі можливості індексування, краще планування запитів для аналітичних запитів і менше пасток навколо приведення типів. Міграція пройшла нормально в тестах. Потім у продакшені почалися спорадичні помилки запису.
Помилки не були випадковими. Вони корелювали з піковим трафіком. Додаток використовував патерн, який «зазвичай працював» в MariaDB: прочитати рядок, виконати бізнес-логіку, оновити його, повторити. За замовчуванням MariaDB і в тій поведінці їхній код обробляв конфлікти таким чином, що «останній запис перемагає» частіше, ніж вони усвідомлювали. Це було неправильно, але зберігало UX рухливим.
На Postgres вони посилили обмеження і використовували транзакції ретельніше. Тепер конфлікти виявлялися як помилки. База даних не карала їх заради забави; вона відмовлялася брехати. Але додаток не був побудований для правильних повторів транзакцій. Воно повторювало цілі запити, дублюючи побічні ефекти і породжуючи видимі для користувача дивні ситуації.
Вони спробували «оптимізувати» шляхом збільшення розміру пулу підключень і зняття деяких блокувань у коді. Це зробило ситуацію гіршою: більша конкурентність підвищила частоту конфліктів, а пул наситив CPU контекстними переключеннями й контенцією. Найшвидший запит усе ще повільний, коли він конкурує з 2000 сусідами.
Вони відновилися, зробивши нудне, але правильне: ключі ідемпотентності, правильні повтори транзакцій і чітка політика, які операції вимагають серіалізації. Продуктивність покращилась після того, як покращилась коректність. Порядок цей не опціональний.
Міні-історія 3: Нудна, але правильна практика, що врятувала день (налаштування не важливі, якщо ви можете їх бачити)
Компанія, що працює поруч з платіжними системами, використовувала і MariaDB, і Postgres в різних продуктових лініях. У них було просте правило: у кожної бази даних був «маніфест дефолтів» у системі контролю версій — параметри запуску, SQL-моди, очікування ізоляції, вибір кодування/коллації та операційні SLO, що з них випливають.
Це була не сексуальна робота. Це не приносило архітектурних нагород. Проте вона перетворювала міграції на передбачувану інженерію замість археології. Коли команда хотіла клонувати середовище, вони не «копіювали те, як виглядає прод». Вони застосовували маніфест, а потім перевіряли його скриптом, що запускав саніті-запити.
Одного дня оновлення образу ОС змінило поведінку локалі в репліці Postgres. Порядок сортування змінився тонко. Зазвичай це перетворилося б на повільний інцидент: користувачі скаржаться на результати пошуку, саппорт створює тікети, інженери сперечаються про рейтинг релевантності. Натомість валідація маніфесту відразу спіймала невідповідність коллації після провізування. Жодного впливу на продакшен. Вони виправили образ і пішли далі.
Жарт 2: Найдорожчий баг бази даних — той, що ввічливо чекає, поки ви будете в середині вікна міграції.
Типові помилки: симптоми → корінна причина → виправлення
1) «Дані виглядають неправильно, але нічого не впало»
Симптом: невідповідності в аналітиці, дивні дати, обрізані рядки, числові значення округлюються несподівано.
Корінна причина: MariaDB працює без строгих SQL-модів; попередження ігноруються; приймаються неявні приведення і обрізання.
Виправлення: встановіть строгий sql_mode послідовно, виводьте попередження в логи додатка, додайте валідацію при інжесті і проведіть одноразовий аудит даних на предмет пошкоджень через приведення.
2) «Postgres кидає помилки в продакшені після міграції»
Симптом: відмови вставки/оновлення для значень, що раніше «працювали», несподівані порушення обмежень, помилки приведення.
Корінна причина: стара система приймала неоднозначні вхідні дані; Postgres відмовляє. Додаток покладався на поблажливу поведінку.
Виправлення: виправте типи і валідацію в додатку, додайте явні касти де потрібно і зробіть повтори/ідемпотентність частиною дизайну.
3) «Випадкові deadlock-и в MariaDB під піком»
Симптом: помилки deadlock при конкурентних оновленнях; той самий код зазвичай працює поза піком.
Корінна причина: поведінка блокувань InnoDB під REPEATABLE READ, gap locks, невідповідний порядок оновлень і довгі транзакції.
Виправлення: зменшіть область транзакцій, доступайтеся до рядків у послідовному порядку, додайте відповідні індекси, щоб уникнути range-scan, і розгляньте зміни ізоляції лише після виправлення запитів/індексів.
4) «Postgres повільнішає протягом тижнів; диск росте»
Симптом: зростаюча латентність, зростаюче використання диска, сплески vacuum-активності, випадкові IO-шторми.
Корінна причина: autovacuum не встигає, довгі транзакції перешкоджають прибиранню, роздування таблиць/індексів.
Виправлення: завершіть/обмежте довгі транзакції, налаштуйте autovacuum для «гарячих» таблиць, впровадьте партиціюцію/політики збереження і плануйте ручний vacuum/reindex тільки за наявності доказів.
5) «Відбувся фейловер і ми втратили недавні записи»
Симптом: після фейловеру деякі недавні транзакції зникають; користувачі скаржаться на відсутні оновлення.
Корінна причина: асинхронна реплікація з ненульовим відставанням; фейловер сприймали як «безпечний» без вимірювання RPO.
Виправлення: вимірюйте відставання реплікації безперервно, визначте прийнятний RPO, впровадьте синхронну реплікацію там, де це потрібно, і адаптуйте UX додатка для eventual consistency там, де це припустимо.
6) «Шторм підключень: база працює, але все таймаутиться»
Симптом: CPU БД помірний, але spike таймаутів в додатку; кількість з’єднань на максимумі.
Корінна причина: невірне налаштування пулу, повільні запити, що утримують з’єднання, або цикли повторів, що посилюють навантаження (особливо на Postgres, де помилки частіші через строгість).
Виправлення: обмежте конкурентність, використовуйте пулер правильно (та налаштуйте його), виправте повільні запити і додайте джиттер/бекоф до повторів.
Чеклісти / покроковий план
Чекліст A: Якщо ви використовуєте MariaDB і хочете безпеку «як у Postgres»
- Стандартизувати
sql_modeдля всіх кластерів і сесій. Зробіть це частиною провізії, а не фольклору. - Перетворіть попередження на сигнали: збирайте їх у логах і ставте алерти при спайках.
- Використовуйте InnoDB скрізь (якщо ще не). Якщо щось досі MyISAM — трактуйте це як технічний борг з відсотками.
- Додавайте обмеження і зовнішні ключі там, де домен строгий (гроші, права, запаси). Дайте базі сказати «ні».
- Вимірюйте deadlock-и і очікування блокувань. Виправте область транзакцій і індекси перед тим, як тюнити «магічні» налаштування.
- Визначте кодування/коллацію явно. Віддайте перевагу UTF-8 (
utf8mb4) і тестуйте міграції на реальних мультимовних даних. - Робіть видимим відставання реплікації і пов’язуйте його з політикою фейловеру. Не «автофейлoвьте» у втрату даних без рішення.
Чекліст B: Якщо ви використовуєте Postgres і хочете uptime «як у MariaDB» без втрати коректності
- Розробіть повтори в додатку для помилок серіалізації та транзитних помилок. Протестуйте їх під конкурентністю.
- Встановіть розумні statement timeouts і
idle_in_transaction_session_timeout. - Встановіть і використовуйте
pg_stat_statementsі відстежуйте топ-запити як частину стандартних операцій. - Налаштовуйте autovacuum для важливих таблиць; дефолти — це стартова точка, а не обіцянка.
- Слідкуйте за чекпоінтами і обсягом WAL; переконайтеся, що затримка сховища відома і прийнятна.
- Використовуйте синхронну реплікацію лише там, де бізнес цього вимагає; інакше будьте чесні щодо RPO.
- Для великих таблиць плануйте партиціюцію та політику збереження на ранніх етапах. Роздування легше запобігти, ніж лікувати.
Чекліст C: План надійності при міграції (MariaDB ↔ Postgres)
- Аудит використання типів: дати, timestamps, булеві та числова точність. Перелічіть всі неявні приведення, на які покладається ваш додаток.
- Заморозьте дефолти: SQL-mode, очікування ізоляції, кодування/коллація, обробка часового поясу.
- Запустіть скан якості даних: недійсні дати, обрізані значення, сирітські записи (відсутні FK), дублікати ключів, де передбачалася унікальність.
- Репетируйте cutover з продакшен-подібною конкурентністю та інжекцією відмов (вбивання з’єднань, deadlock-и, шторми повторів).
- Визначте критерії відкату і перевірте бекапи реальним тестом відновлення, а не лише повідомленням «backup succeeded».
- Після cutover слідкуйте за нудними графіками: відставання реплікації, частота чекпоінтів, мертві кортежі/довжина history list і топ повільних запитів.
Питання та відповіді
1) Чи є MariaDB «небезпечним» за замовчуванням?
Ні. Але вона може бути поблажливою в такий спосіб, що погані дані увійдуть непомітно, якщо ви не встановите строгі SQL-моди і не відстежуватимете попередження. Ви можете зробити її суворою; для цього потрібно діяти свідомо.
2) Чи краще PostgreSQL тому, що він суворіший?
Суворість означає, що ваші помилки проявляться раніше. Це зазвичай краще для систем, де важлива коректність. Але це також означає, що вам потрібна надійна обробка помилок і логіка повторів, інакше ви поміняєте тихі проблеми даних на гучні відмови.
3) Яка СУБД має безпечніший рівень ізоляції за замовчуванням?
Жодна не є універсально безпечнішою. За замовчуванням REPEATABLE READ у MariaDB може зменшувати деякі аномалії, але збільшувати несподівані блокування. READ COMMITTED у Postgres простіший, але допускає non-repeatable reads, якщо ви не запросите сильнішу ізоляцію.
4) Чому міграції з MariaDB на Postgres «знаходять» проблеми з даними?
Тому що Postgres суворіше застосовує типи і обмеження, і не приводить так багато самостійно. Міграція діє як «сироватка правди» для прихованих припущень.
5) Який найбільший операційний підводний камінь в дефолтах Postgres?
Припущення, що autovacuum завжди встигає. Зазвичай він встигає — доки у вас не з’явиться «гаряча» таблиця, довгі транзакції або інтенсивний запис. Тоді це перетворюється на проблему продуктивності і місця на диску.
6) Який найбільший операційний підводний камінь в дефолтах MariaDB?
Припущення, що «вставлено» означає «збережено так, як я хотів». Без строгих модів і видимості попереджень ви можете накопичувати некоректні дані, поки все виглядає здоровим.
7) Чи можу я отримати фейловер без втрат даних з будь-якої з них?
Так, але ви за це платите. Потрібна синхронна реплікація (або еквівалентні гарантії) і модель додатка, що терпить додаткову латентність. Асинхронна реплікація — це рішення RPO, а не зручність дефолту.
8) Чи слід взагалі покладатися на налаштування за замовчуванням бази даних?
Покладайтеся на дефолти тільки якщо ви записали їх, перевірили і моніторите поведінку, яку вони передбачають. Інакше ви покладаєтесь на те, що встановилось разом із версією, яку ви випадково інсталювали.
9) Якщо можу зробити лише одне покращення цього місяця, що це має бути?
Зробіть дефолти явними і спостережуваними: SQL-моди / очікування ізоляції, кодування/коллація та дашборд «топ-запити + очікування блокувань + відставання реплікації». Це запобігає і повільному корумпуванню даних, і несподіваним відмовам.
Наступні кроки, які ви реально можете зробити цього тижня
- Запишіть поточні дефолти (MariaDB
sql_mode, ізоляція Postgres, кодування/коллація, часовий пояс) і зафіксуйте їх у системі контролю версій як «контракт часу виконання». - Запустіть швидкі діагностичні запити у спокійний період, не під час інциденту. Базові лінії — як ви помічаєте «дивне».
- Оберіть один засіб захисту коректності і впровадьте його: строгі моди в MariaDB або таймаути і повтори, безпечні транзакції в Postgres.
- Зробіть прибирання видимим: мертві кортежі/статистика autovacuum у Postgres; довжина history list і deadlock-и в InnoDB у MariaDB.
- Визначте свою правду про реплікацію: вимірюйте відставання, визначте прийнятний RPO і спроєктуйте фейловер відповідно. Не дозволяйте дефолту вирішувати в розпалі інциденту.
Якщо ви нічого не зробите більше: дефолти бази даних — це частина вашого продакшен-API. Трактуйте їх як код, бо вони поводяться як код — просто без модульних тестів.