Заповнення диска — це такий інцидент, який раптово нагадує всім, скільки взагалі існує логів, скільки «тимчасових» експортів виявилися постійними, і як швидко база даних покарає оптимізм. Додаток здається «повільним». Потім — «недоступним». Потім виглядає, ніби ваш пейджер намагається досягти космічної швидкості.
Це польове порівняння: коли файловій системі стає 100%, який рушій повертає вас у зелений стан швидше — і який відновлюється чистіше (менше дивних послідкових ефектів, менше прихованих ризиків для даних, менше «ми полагодили, але тут привиди» наступних завдань).
Короткий висновок
Якщо оцінювати «хто відновлюється швидше» за критерієм «хто повертається в онлайн з мінімальним ручним доглядом», PostgreSQL часто перемагає за рахунок більшої зрозумілості і передбачуваних режимів відмов, особливо що стосується WAL і відновлення після краху. Якщо оцінювати «хто відновлюється чистіше» як «хто менш імовірно продовжить тихо працювати з прихованим ризиком корупції», PostgreSQL знову здається безпечнішим — бо він голосніший, більш транзакційний щодо метаданих і відмовляється вдавати, що все в порядку.
MySQL (InnoDB) теж може відновитися чисто, і він дуже хороший у відновленні після краху — проте інциденти з заповненням диска мають схильність перетворюватися на брудні часткові збої: тимчасові таблиці, redo/undo логи, бінарні логи й ОС сперечаються за останні мегабайти, мов останнє місце в електричці.
Мої практичні поради з експлуатації:
- PostgreSQL: зробіть пріоритетом забезпечення стійкості і обмеження WAL та
pg_wal(архівування, дисципліна replication slots, окремий том). Заповнення диска зазвичай виглядає як «неможливо записати WAL / неможливо зробити checkpoint», що страшно, але читабельно. - MySQL: зробіть пріоритетом обмеження бінлогів, tmpdir та росту undo/redo, і уникайте сюрпризів на рівні файлової системи (thin provisioning, snapshot-и). Заповнення диска часто виглядає як «усе щось ламається одночасно», що операційно дорого.
- Для обох: тримайте вільне місце як функцію, а не пропозицію. «Ми працюємо при 92% заповнення» — це рецепт інцидент-менеджменту, де ви ведете переговори зі сторедж-масивом.
Цитата, яку варто приклеїти над монітором, від Gene Kranz: «Невдача — не варіант». Коли ви керуєте продакшн-сховищем, розглядайте це як ідею, а не обіцянку.
Цікаві факти й історичний контекст
- Лінія WAL у PostgreSQL: підхід write-ahead logging у PostgreSQL виріс з дослідницьких коренів Postgres у стійку, консервативну модель надійності, що сильно визначає поведінку під час заповнення диска (WAL спочатку, усе інше пізніше).
- InnoDB не завжди був «за замовчуванням»: InnoDB став практичним дефолтом для MySQL, бо приніс відновлення після краху та транзакційні семантики, яких бракувало старішим MyISAM — це змінило прояви інцидентів з заповненням диска (redo/undo проти хаосу на рівні таблиць).
- Біль ibdata1 у MySQL: ранні налаштування InnoDB часто використовували спільний системний tablespace (ibdata1), який міг рости й не зменшуватися легко — тривала операційна рана для інцидентів «видалили дані, чому диск усе одно повний?».
- MVCC у PostgreSQL по визначенню потребує місця: MVCC PostgreSQL створює мертві кортежі, які потрібно прибирати vacuum; тож тиск на диск — не сюрприз, а рахунок, який ви платите регулярно або з відсотками пізніше.
- Replication slots змінили режими відмов: replication slots у PostgreSQL потужні, але вони можуть вічно прив’язувати WAL; сучасні інциденти «disk full» часто простежуються до забутого слота, що утримує WAL у заручниках.
- Ретенція binlog у MySQL — це важіль відмови: у MySQL бінарні логи одночасно й ресурс для відновлення, і бомба на диску. Значення за замовчуванням та операційні звички вирішують, чи буде заповнення диска «малим» чи «багатогодинним».
- Файлові системи важать більше, ніж хочеться: XFS, ext4 і ZFS поводяться по-різному при ENOSPC. База даних не може відмовитися від характеру ядра.
- Поводження checkpoint — великий диференціатор: чекпоінти в PostgreSQL та механізми скидання в MySQL створюють різні «вибухоподібні» патерни запису; при високому заповненні саме в цих сплесках ви виявляєте, що ковзали по тонкому льоду.
Що насправді означає «заповнення диска» в продакшні
«Заповнення диска» рідко буває однорідним явищем. Це суперечка між шарами:
- Файлова система повертає ENOSPC. Або EDQUOT, якщо ви вдарилися в квоту, про яку забули.
- Бекенд зберігання чемно бреше (thin provisioning), поки не перестане бути чемним, і тоді це проблема для всіх.
- Ядро може тримати деякі процеси живими, тоді як інші падатимуть на fsync, rename або allocate.
- База даних має кілька шляхів запису: WAL/redo, файли даних, тимчасові файли, спіл-у-під час сортування, autovacuum/vacuum, бінарні логи, метадані реплікації та фонова робота.
Практичне визначення для обробки інциденту: чи може база ще гарантувати довговічність і узгодженість? При 100% заповненні відповідь стає «не надійно» задовго до того, як процес фактично вмирає.
Крім того, «заповнення диска» — це не лише ємність. Це вільні блоки, іноди, резерв IOPS, ампліфікація запису та кількість суміжного простору, який може виділити файловa система при фрагментації.
Короткий жарт #1: інциденти з заповненням диска схожі на малюків — тихій фазі бути добре, крик — погано, але найгірше, коли вони знову стихають і ви розумієте, що вони знайшли фломастери.
Як PostgreSQL і MySQL поводяться при нестачі місця
PostgreSQL: WAL — король, і він скаже, коли королівство зламане
Стійкість PostgreSQL обертається навколо WAL. Якщо він не може записати WAL, він не може безпечно підтвердити транзакцію. Це не предмет переговорів. Коли диск заповнюється на pg_wal (або на файловій системі, що його хостить), ви часто побачите:
- Транзакції, що падають з «could not write to file … No space left on device».
- Попередження про checkpoint, що ескалюють до «PANIC» у найгірших випадках (залежно від конкретної помилки).
- Відставання реплікації, яке стає неактуальним, бо первинка не може надійно генерувати WAL.
Це жорстко, але чесно: Postgres схильний відмовлятися так, щоб ви виправили первісне обмеження зберігання, а не продовжували писати «трохи ще», доки не створите другий інцидент.
Чистіші сценарії відновлення при disk-full у PostgreSQL:
- Ясні повідомлення про помилки, які вказують на WAL-сегменти, тимчасові файли, базовий каталог.
- Відновлення після краху зазвичай детермініроване, коли ви відновите можливість запису.
- Обмежений набір підозрюваних:
pg_wal, тимчасові спіли, autovacuum, replication slots.
Все ще зустрічаються брудні сценарії в Postgres:
- Replication slots прив’язують WAL, поки диск не здасться.
- Довгі транзакції перешкоджають вакууму, роздувають таблиці та індекси, і потім диск «йде».
- Вибухи тимчасових файлів через погані запити: сорти, хеші, великі CTE або відсутні індекси.
MySQL (InnoDB): кілька шляхів запису — кілька способів постраждати
InnoDB має redo логи, undo логи, doublewrite buffer, файли даних, тимчасові tablespace-и, бінарні логи (рівень сервера), relay логи (реплікація) і ще файлову систему. Коли місця бракує, можна вдаритися в помилку в одній зоні, тоді як інша все ще пише — створюючи часткову функціональність і заплутані симптоми.
Поширені патерни disk-full у MySQL:
- Бінарні логи заповнюють розділ, особливо з row-based логуванням і навантаженим записом.
- tmpdir заповнюється від великих сортувань або тимчасових таблиць; запити починають падати дивними помилками, поки сервер «виглядає живим».
- InnoDB не може розширити tablespace (file-per-table або shared tablespace), викликаючи помилки при insert/update.
- Реплікація ламається асиметрично: джерело продовжує працювати, але репліка зупиняється на записі relay логів, або навпаки.
Чистіші шаблони відновлення в MySQL:
- Після звільнення місця InnoDB crash recovery зазвичай працює надійно.
- Очищення бінлогів — швидкий важіль, якщо ви дисципліновані й розумієте реплікаційні вимоги.
Брудніші патерни в MySQL:
- ibdata1 та undo tablespace-и можуть залишатися великими навіть після видалення; «звільнення місця» не завжди миттєве без перебудов.
- Підозра на корупцію таблиць зростає, коли файли писалися частково під тиском файлової системи — рідко, але її страх дорогий.
- Фонові потоки можуть продовжувати бити по IO, намагаючись скинути/об’єднати дані, поки ви намагаєтесь стабілізувати систему.
То хто відновлюється швидше?
Якщо ваш on-call потребує одну фразу: PostgreSQL зазвичай дає пряміший шлях від «disk full» до «знову безпечно», за умови, що ви розумієте WAL, чекпоінти та слоти. MySQL дає вам більше «швидких важелів» (purge binlogs, перемістити tmpdir), але також більше способів помилково обрізати гілку, на якій сидите.
А хто відновлюється чистіше?
Чисте відновлення — це про впевненість: після того, як ви звільнили місце, чи довіряєте ви системі, чи плануєте вихідні на «перевірку»? Позиція PostgreSQL — зупиняти світ, коли WAL не можна гарантувати — зазвичай дає менше ситуацій «він працює, але…». MySQL теж може бути чистим, але інциденти з disk-full частіше лишають чекліст «чи ми втратили позицію реплікації, чи ми усікли щось, чи tmpdir перемістився, чи purge binlog зламав репліку?»
План швидкої діагностики
Це порядок дій, який швидко знаходить вузьке місце. Не «теоретично правильний», а «завершує інцидент».
1) Підтвердити, що саме заповнено (блоки проти інодів проти квоти)
- Перевірте використання блоків файлової системи.
- Перевірте виснаження інодів.
- Перевірте квоти (користувача/проєкту).
- Перевірте thin provisioning / LVM / запас масиву.
2) Визначити домінантного споживача місця (і чи він ще росте)
- Знайдіть, яка директорія величезна (
/var/lib/postgresql,/var/lib/mysql,/var/log). - Перевірте відкриті, але видалені файли, що все ще займають місце.
- Перевірте, чи це «стабільні логи» або «раптовий спіл тимчасових файлів».
3) Визначити, чи база ще гарантує довговічність
- Postgres: чи може писати WAL? чи падають чекпоінти?
- MySQL: чи падають записи redo/binlog/tmp? чи під загрозою реплікація?
4) Спочатку застосуйте оборотний, низькоризиковий спосіб звільнення місця
- Видаліть/очистіть ротаційні логи, старі дампи, старі пакети.
- Пуржуйте MySQL binlogs лише якщо репліки в безпеці.
- Вирішіть проблему replication slots у Postgres, що прив’язують WAL.
- Перемісіть тимчасові каталоги на інший том як тимчасовий захід.
5) Після стабілізації — робота над коректністю
- Запустіть перевірки узгодженості, які підходять для рушія.
- Виправте політики зберігання та оповіщення про ємність.
- Заплануйте vacuum/reindex або перебудову таблиць, якщо причиною було роздування.
Практичні завдання: команди, виводи та рішення (12+)
Це ті команди, які ви виконуєте о 3 ранку. Кожна включає, що означає вивід і яке рішення ви приймаєте.
Завдання 1: Перевірити місткість файлової системи (блоки)
cr0x@server:~$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 80G 79G 120M 100% /
/dev/nvme1n1p1 xfs 500G 410G 90G 83% /var/lib/postgresql
tmpfs tmpfs 16G 1.2G 15G 8% /run
Значення: Коренева файлова система на 100% з лише 120M вільними; том даних Postgres у порядку. Багато сервісів ламаються, коли / повний (journald, оновлення пакетів, тимчасові файли).
Рішення: Звільніть місце на / негайно (логи, кеші). Не чіпайте файли бази даних, якщо вони не є причиною.
Завдання 2: Перевірити виснаження інодів
cr0x@server:~$ df -ih
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 5.0M 5.0M 0 100% /
/dev/nvme1n1p1 20M 1.2M 18.8M 6% /var/lib/postgresql
Значення: Іноди на / вичерпані. Це виглядає як «диск повний», але видалення одного великого файлу не допоможе.
Рішення: Знайдіть каталоги з мільйонами дрібних файлів (зазвичай логи, тимчасові або погано керовані кеші додатка). Очистіть їх перш за все.
Завдання 3: Безпечно знайти найбільших споживачів місця
cr0x@server:~$ sudo du -xhd1 /var | sort -h
120M /var/cache
2.4G /var/log
8.1G /var/tmp
55G /var/lib
Значення: /var/lib домінує. Там живуть бази даних. Але /var/log і /var/tmp теж не тривіальні та часто найпростіші для скорочення.
Рішення: Якщо база даних не працює, спочатку відновіть головний буфер, обрізавши логи та тимчасові; потім детальніше дослідіть каталоги бази даних.
Завдання 4: Знайти відкриті, але видалені файли (класична ситуація «df каже повний, du каже ні»)
cr0x@server:~$ sudo lsof +L1 | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
rsyslogd 812 syslog 7w REG 259,2 2147483648 0 12345 /var/log/syslog.1 (deleted)
java 1552 app 12w REG 259,2 1073741824 0 12346 /var/log/app.log (deleted)
Значення: Процеси все ще тримають дескриптори на видалені файли, тому простір не буде повернутий, доки процеси не перезапустяться або не закриють FD.
Рішення: Перезапустіть конкретний сервіс після підтвердження впливу, або коректно проведіть ротацію логів. Не перезавантажуйте сервер навмання, якщо не хочете тривалого простою.
Завдання 5: Перевірити використання журналів journald
cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 1.8G in the file system.
Значення: Журнали займають реальний простір. На невеликих кореневих томах це має значення.
Рішення: Проведіть vacuum старих логів за потреби; потім виправте політику зберігання, щоб це не стало вашою згубною хобі.
Завдання 6: Швидке vacuum journald (звільнення місця)
cr0x@server:~$ sudo journalctl --vacuum-time=7d
Vacuuming done, freed 1.2G of archived journals from /var/log/journal.
Значення: Ви отримали назад 1.2G. Часто цього достатньо, щоб база змогла зробити checkpoint, повернутися до роботи або чисто перезапуститися.
Рішення: Використайте отримане місце, щоб стабілізувати базу (або створити тимчасовий буфер безпеки), потім усуньте корінь проблеми.
Завдання 7: PostgreSQL — перевірити, чи WAL — джерело проблеми
cr0x@server:~$ sudo -u postgres du -sh /var/lib/postgresql/16/main/pg_wal
86G /var/lib/postgresql/16/main/pg_wal
Значення: WAL величезний. Зазвичай це означає (a) replication slot прив’язує WAL, (b) архівування зламалося, (c) репліка відстає, або (d) чекпоінти порушені.
Рішення: Розслідуйте replication slots та стан архівування перед видаленням чого-небудь. Ручне видалення WAL-файлів — шлях від інциденту до кар’єрної зміни.
Завдання 8: PostgreSQL — перелічити replication slots і знайти, хто тримає WAL
cr0x@server:~$ sudo -u postgres psql -x -c "SELECT slot_name, slot_type, active, restart_lsn, wal_status FROM pg_replication_slots;"
-[ RECORD 1 ]--+------------------------------
slot_name | analytics_slot
slot_type | logical
active | f
restart_lsn | 2A/9F000000
wal_status | reserved
-[ RECORD 2 ]--+------------------------------
slot_name | standby_1
slot_type | physical
active | t
restart_lsn | 2F/12000000
wal_status | extended
Значення: analytics_slot неактивний, але все ще резервує WAL через restart_lsn. Це поширена причина росту WAL до вичерпання диска.
Рішення: Якщо споживача немає або його можна скинути — видаліть слот, щоб звільнити утримання WAL. Якщо він потрібен — відновіть споживача, дозвольте йому наздогнати, або перемістіть WAL на більший том.
Завдання 9: PostgreSQL — видалити невикористаний logical слот (тільки якщо впевнені)
cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_drop_replication_slot('analytics_slot');"
pg_drop_replication_slot
--------------------------
(1 row)
Значення: Слот видалено; PostgreSQL тепер може переробляти WAL, якщо немає інших обмежень утримання.
Рішення: Моніторьте розмір pg_wal та використання диска; скоординуйтеся з командою, що володіла слотом, бо їхній пайплайн зламається (краще зламати, ніж повний диск).
Завдання 10: PostgreSQL — перевірити наявність безконтрольних тимчасових файлів (спіл запиту)
cr0x@server:~$ sudo -u postgres find /var/lib/postgresql/16/main/base -maxdepth 2 -type f -name "pgsql_tmp*" -printf "%s %p\n" | head
2147483648 /var/lib/postgresql/16/main/base/16384/pgsql_tmp16384.0
1073741824 /var/lib/postgresql/16/main/base/16384/pgsql_tmp16384.1
Значення: Тимчасові файли існують і великі, що вказує на спіли сортувань/хешів на диск. Зазвичай вони зникають, коли сесії закінчуються, але під час інциденту вони можуть бути його причиною.
Рішення: Знайдіть сесії, що створюють файли (наступне завдання), скасуйте їх за потреби і відрегулюйте запит/індексацію. Для негайного полегшення вбивання кількох сесій може звільнити гігабайти швидко.
Завдання 11: PostgreSQL — знайти важкі сесії й скасувати найгіршу
cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, usename, state, now()-query_start AS age, left(query,120) AS q FROM pg_stat_activity WHERE state <> 'idle' ORDER BY query_start ASC LIMIT 5;"
pid | usename | state | age | q
------+--------+--------+---------+------------------------------------------------------------
4412 | app | active | 00:34:12 | SELECT ... ORDER BY ...
4520 | app | active | 00:21:03 | WITH ... JOIN ...
cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_cancel_backend(4412);"
pg_cancel_backend
-------------------
t
(1 row)
Значення: Ви знайшли довготривалі запити; скасування для PID 4412 пройшло успішно. Якщо він спілиться на диск, тимчасові файли повинні перестати рости і, можливо, будуть видалені.
Рішення: Якщо тиск на диск критичний, скасуйте/завершіть сесії, що створюють спіл. Потім виправте план запиту спокійно.
Завдання 12: MySQL — перевірити, куди йде місце (datadir і логи)
cr0x@server:~$ sudo mysql -e "SHOW VARIABLES WHERE Variable_name IN ('datadir','tmpdir','log_bin','general_log_file','slow_query_log_file');"
+--------------------+---------------------------+
| Variable_name | Value |
+--------------------+---------------------------+
| datadir | /var/lib/mysql/ |
| tmpdir | /tmp |
| log_bin | ON |
| general_log_file | /var/lib/mysql/general.log|
| slow_query_log_file| /var/lib/mysql/slow.log |
+--------------------+---------------------------+
Значення: tmpdir на /tmp (часто на корені). Якщо / заповнюється, тимчасові операції MySQL падають дивними способами.
Рішення: Якщо / обмежений, перемістіть tmpdir на більший том і перезапустіть (або налаштуйте заздалегідь). Також перевірте, чи випадково не ввімкнено general log.
Завдання 13: MySQL — переглянути інвентар бінлогів і їх розмір
cr0x@server:~$ sudo mysql -e "SHOW BINARY LOGS;"
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| binlog.000231 | 104857600 |
| binlog.000232 | 104857600 |
| binlog.000233 | 104857600 |
| binlog.000234 | 104857600 |
+------------------+-----------+
Значення: Бінлоги присутні і можуть накопичуватися. Тут розміри схожі, але кількість може бути величезною.
Рішення: Перед очищенням перевірте стан реплікації. Видалення бінлогів, які все ще потрібні реплікам, — це самонанесена зупинка.
Завдання 14: MySQL — підтвердити позицію реплікації перед очищенням бінлогів
cr0x@server:~$ sudo mysql -e "SHOW MASTER STATUS\G"
*************************** 1. row ***************************
File: binlog.000234
Position: 89234122
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 3f1c2c3a-aaaa-bbbb-cccc-111111111111:1-982341
cr0x@server:~$ sudo mysql -e "SHOW SLAVE HOSTS;"
+-----------+-----------+------+-------------------+-----------+
| Server_id | Host | Port | Rpl_recovery_rank | Master_id |
+-----------+-----------+------+-------------------+-----------+
| 12 | replica01 | 3306 | 0 | 1 |
+-----------+-----------+------+-------------------+-----------+
Значення: У вас принаймні одна репліка. Потрібно забезпечити, що вона наздогнала достатньо (GTID або file/pos), перш ніж видаляти логи.
Рішення: Перевірте стан реплік на кожній (або через моніторинг). Видаляйте логи лише старіші за ті, що всі репліки вже спожили.
Завдання 15: MySQL — консервативно очистити бінлоги
cr0x@server:~$ sudo mysql -e "PURGE BINARY LOGS TO 'binlog.000233';"
Значення: Бінлоги старіші за binlog.000233 видалені. Якщо репліка їх ще потребувала, вона зупиниться і потребуватиме повторного насіння або іншого ремонту.
Рішення: Працюйте з purge лише після перевірки реплікаційного стану. Якщо невпевнені — звільніть місце іншим шляхом. Бінлоги — це бензопила, а не скальпель.
Завдання 16: MySQL — перевірити вибухи тимчасових таблиць та тиск на tmpdir
cr0x@server:~$ sudo mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Created_tmp_disk_tables | 184203 |
| Created_tmp_files | 91203 |
| Created_tmp_tables | 3312849 |
+-------------------------+----------+
Значення: Велика кількість тимчасових дискових таблиць вказує на запити, що спиляться на диск. При заповненій файловій системі ці запити падають і іноді блокують інші.
Рішення: Миттєво зменшіть тиск на диск (перемістіть tmpdir, додайте місце, вбийте найгірші запити), потім налаштуйте запити/індекси і параметри тимчасових таблиць.
Завдання 17: Підтвердити, що thin provisioning вас не вводить в оману (приклад LVM)
cr0x@server:~$ sudo lvs -a -o +data_percent,metadata_percent
LV VG Attr LSize Pool Data% Meta%
mysql vg0 Vwi-aotz-- 300.00g thinpool 98.44 92.10
thinpool vg0 twi-aotz-- 500.00g 98.44 92.10
Значення: Thin pool майже заповнений. Навіть якщо файловій системі «добре», алокації можуть скоро почати падати. Бази даних люблять відкривати це під час пікових записів.
Рішення: Розширіть thin pool або звільніть екстенти негайно. Розглядайте це як «очікуване заповнення диска».
Завдання 18: Перевірити, чи ви не вдарилися в квоту (приклад XFS project quota)
cr0x@server:~$ sudo xfs_quota -x -c "report -p" /var/lib/mysql
Project quota on /var/lib/mysql (/dev/nvme2n1p1)
Project ID: 10 (mysql)
Used: 498.0G Soft: 0 Hard: 500.0G Warn/Grace: [--------]
Значення: Ви вдарилися в жорстку квоту на 500G. Файлова система може мати вільне місце загалом, але ваша директорія MySQL не може рости.
Рішення: Збільшіть квоту або мігрируйте дані на більший проєкт. Не звинувачуйте базу даних у політичному рішенні.
Короткий жарт #2: Єдина річ, що росте швидше за ваш WAL — це впевненість людини, яка каже «нам не потрібні оповіщення про диск».
Три корпоративні міні-історії (анонімізовано, болюче правдоподібні)
1) Інцидент, спричинений неправильною припущенням: «У нас 20% вільного, усе добре»
Вони запускали PostgreSQL на віртуалці з окремим диском даних і невеликим кореневим диском. Дашборд показував диск даних заповненим на 78%. Всі відчували себе відповідальними. Ніхто не переймався.
Потім деплой увімкнув глибоке логування додатка під час сесії відладки. Логи пішли в /var/log на кореневому диску. До півночі корінь досяг 100% і journald почав відкидати повідомлення. До 00:30 PostgreSQL почав падати при тимчасових записах, а потім мав проблеми з записами, пов’язаними з чекпоінтами, бо навіть «не диск бази» все одно важливий для ОС і системних сервісів.
On-call зробив звичне: перевірив том бази, побачив запас і вирішив, що проблема десь іще. Тим часом «десь інше» був корінь, який також містив /tmp і кілька скриптів адміністрування, що писали власні тимчасові файли. Відмова виглядала як проблема бази, але корінь був у розкладці інфраструктури й припущенні, що «диск бази = всі диски, що важливі».
Відновлення було швидким, коли хтось запустив df -h замість того, щоб тупо дивитися на дашборд бази. Вони провакумували journald, обрізали збігайний лог, перезапустили говіркий сервіс, і Postgres відновився без драм. Постмортем-протокол — нудна дія: розміри розділів і утримання логів. І це спрацювало.
2) Оптимізація, що відбилася боком: «Тримаймо binlogs довше, на всяк випадок»
Команда MySQL хотіла кращого point-in-time recovery і гладшого відновлення реплік. Найпростіший регулятор — ретенція бінлогів. Вони її розширили. Ніхто не занотував новий граничний розмір, бо планування ємності робиться, коли є час, а часу ніколи немає.
Сховище було thin-provisioned. Файлова система все ще показувала вільне місце, тож алерти мовчали. Записи тривали. Бінлоги накопичувалися. Thin pool підповзав до заповнення. Одного дня алокації почали падати хвилями — спочатку під час пікових записів, потім частіше. MySQL почав видавати помилки про запис у бінлог, і раптово коміти стали падати періодично.
Найгірше було в патерні: сервер не вмир, він просто став ненадійним. Деякі транзакції підтверджувалися, інші — ні. Додаток почав ретраїти. Ретраїти збільшували навантаження запису. Навантаження генерувало ще більше тиску бінлогів. Це замкнене коло, якого ви не хочете.
Їх врятували, розширивши thin pool і консервативно видаливши бінлоги після підтвердження здоров’я реплік. Помилка оптимізації не була в самому «триманні бінлогів довше». Проблема була в тому, що це зробили без обмеженого бюджету, без оповіщень на рівні сховища й без відпрацьованої процедури очищення.
3) Нудна, але правильна практика, що врятувала день: окремі WAL/redo, бюджети, репетиції очищення
Інша організація запускала PostgreSQL з WAL на виділеному томі та суворим моніторингом: оповіщення при заповненні 70/80/85% і покроковий план з «перевірити replication slots, архівування, довгі транзакції». Це було не гламурно. Це було ефективно.
Одного дня downstream logical replication споживач завис після зміни мережі. Реплікаційний слот став неактивним. WAL почав накопичуватися. Спрацював алерт 70%. On-call не панікував; вони виконали рукбук. Підтвердили, що слот — причина, перевірили, що споживач справді впав, потім видалили слот і повідомили команду власника.
База ніколи не стала тільки для читання, не впала і не взяла простій. «Інцидент» був Slack-тредом і тикетом. Практика, що врятувала їх, була не в геніальності. Вона була в тихій дисципліні: розмістити найнебезпечніший шлях запису (WAL) на томі з бюджетом і алертами, що дають людям час поводитися як люди.
Якщо хочете швидше відновлення після заповнення диска, не починайте під час інциденту. Починайте, коли вирішуєте, де живуть WAL/binlogs і скільки запасу тримаєте.
Типові помилки: симптом → причина → виправлення
1) «df каже 100%, але я видалив файли й нічого не змінилося»
Симптом: Використання диска залишається високим після видалення великих логів або дампів.
Причина: Відкриті, але видалені файли (процес все ще тримає FD).
Виправлення: Використайте lsof +L1, перезапустіть конкретний сервіс, потім підтвердьте, що df -h впав. Не перезавантажуйте сервер без потреби.
2) PostgreSQL: «pg_wal великий і не зменшується»
Симптом: Директорія WAL росте до майже повного диска; архівування може іноді «працювати».
Причина: Неактивний replication slot, застряглий archiver або репліка, що не споживає WAL.
Виправлення: Перевірте pg_replication_slots, полагодьте споживача або видаліть слот; перевірте здоров’я archive_command; забезпечте запас на томі WAL.
3) PostgreSQL: «Немає місця, але диск даних має простір»
Симптом: Помилки запису тимчасових файлів, відмови під час сортувань/джоінів; основний том виглядає OK.
Причина: Тимчасові файли йдуть на іншу файлову систему (часто /tmp або корінь), або кореневий том повний і впливає на системні операції.
Виправлення: Перевірте temp_tablespaces і використання OS temp; звільніть корінь; опційно перемістіть тимчасові tablespace-и на більший том.
4) MySQL: «Сервер запущений, але запис падає випадково»
Симптом: Деякі транзакції падають, інші успішні; у помилках згадуються binlog або тимчасові файли.
Причина: Бінлоги або tmpdir на повному файловому розділі; thin provisioning майже повний викликає переривчасті помилки алокації.
Виправлення: Звільніть місце там, де живуть бінлоги; перевірте thin pool; перемістіть tmpdir; обмежте ретенцію бінлогів (binlog_expire_logs_seconds) і моніторьте.
5) «Ми звільнили 5G, але база відразу ж це з’їла»
Симптом: Ви видаляєте щось, тимчасово звільняєте місце, але через хвилини воно зникає.
Причина: Фоновий процес, що посилює запис: чекпоінти під тиском, autovacuum наздоганяє, backlog реплікації генерує логи, або runaway-запит, що пише тимчасові файли.
Виправлення: Визначте вектор зростання (WAL/binlog/temp). Зупиніть джерело кровотечі: скасуйте запит, виправте слот/репліку, паузуйте задачу або тимчасово обмежте інжест.
6) «Ми очистили бінлоги MySQL і тепер репліка мертва»
Симптом: Репліка помиляється через відсутні бінлог-файли.
Причина: Ви видалили бінлоги, які ще потрібні репліці; погана видимість відставання репліки/стану GTID.
Виправлення: Пересійте репліку або використайте відновлення на базі GTID, якщо доступно. Щоб уникнути повторення: автоматизуйте purge бінлогів на безпечній відстані і моніторьте lag реплік.
7) PostgreSQL: «VACUUM нам не допоміг; диск усе ще повний»
Симптом: Видалення записів відбулося, vacuum пройшов, але диск не зменшився.
Причина: MVCC звільняє місце всередині файлів відношень для повторного використання; це не обов’язково повертає простір ОС. Також індекси можуть бути надмірно роздуті.
Виправлення: Для реального зменшення: VACUUM FULL (блокує) або стратегії перезапису таблиці, плюс REINDEX там, де потрібно. Плануйте це; не імпровізуйте в середині інциденту.
Чеклісти / покроковий план
Під час інциденту: стабілізувати перед ремонтом
- Зупиніть ріст: призупиніть пакетні задачі, вимкніть говірке логування, обмежте інжест або тимчасово заблокуйте найгіршого винуватця.
- Підтвердіть, що саме заповнено:
- Запустіть
df -hTіdf -ih. - Перевірте квоти, якщо ваша організація любить невидимі стіни.
- Запустіть
- Швидко відновіть запас (ціль — 5–10% вільних, не «кілька МБ»):
- Vacuum journald і обріжте старі логи.
- Видаліть старі дампи/артефакти з відомих місць.
- Усуньте відкриті-але-видалені файли і перезапустіть конкретний сервіс.
- Тріаж специфічний для бази:
- Postgres: перевірте
pg_wal, слоти, архівування, довгі транзакції, спіли. - MySQL: перевірте бінлоги, розташування tmpdir, relay логи на репліках та статус thin pool.
- Postgres: перевірте
- Поверніть базу в безпечний стан:
- Надавайте перевагу контрольованому перезапуску над повторними краш-лупами.
- Підтвердіть, що шляхи довговічності працюють (WAL/binlog записуються).
- Перевірте здоров’я додатка: падіння кількості помилок, нормалізація затримок, реплікація надолужує.
Після інциденту: ускладніть повторення
- Розділіть критичні шляхи запису: WAL або redo/binlog на томах з алертами і передбачуваними бюджетами зростання.
- Явно встановіть політику ретенції:
- Postgres: керуйте слотами; переконайтесь, що архівування моніториться, якщо використовується.
- MySQL: встановіть
binlog_expire_logs_seconds; перевірте ротацію логів.
- Розмістіть тимчасові файли там, де вони можуть «дихати»: виділений том для temp або розумні ліміти; уникайте кореня для важких тимчасових навантажень.
- Оповіщайте про швидкість зміни, а не лише про відсоток заповнення: «+20G/год» краще, ніж «92% зайнято».
- Репетируйте рукбук на заповнення диска щоквартально. Мета — м’язова пам’ять, а не героїзм.
FAQ
1) Якщо диск повний, чи слід одразу перезапустити базу?
Ні. Спочатку звільніть достатньо місця, щоб база змогла стартувати й завершити відновлення після краху/чекпоінти. Перезапуск у стані ENOSPC може погіршити ризик корупції й продовжити простої.
2) Чи безпечно видаляти PostgreSQL WAL-файли, щоб звільнити місце?
Майже ніколи. Ручне видалення може зробити відновлення неможливим. Правильний підхід — усунути причину утримання WAL (слоти, відставання архівування, репліка) або додати місце.
3) Чи безпечно чистити MySQL бінлоги під час інциденту?
Так, але лише з урахуванням реплікації. Підтвердіть, що всі репліки спожили логи (GTID або file/pos). Пуржте консервативно, потім уважно спостерігайте за репліками.
4) Чому PostgreSQL споживає стільки диску навіть після видалення рядків?
MVCC зберігає старі версії рядків, доки vacuum не звільнить простір для повторного використання. Цей простір зазвичай використовується всередині файлів, а не повертається ОС. Щоб зменшити — потрібен перепис (наприклад, VACUUM FULL) або стратегії перебудови.
5) Чому MySQL «виглядає живим», але запити падають під тиском диска?
Тому що різні підсистеми ламаються незалежно: tmpdir може бути повним, записи бінлогів можуть падати, або tablespace не може розширитися. Відкритий TCP-порт — це не те саме, що «база здорова».
6) Яка база більше терпить роботу близько до повного диска?
Жодна. PostgreSQL частіше відмовляється від небезпечних комітів, коли WAL не пишеться. MySQL може довше «волочитися», але операційно бути бруднішим. Найкраща «функція толерантності» — це вільне місце.
7) Який найкращий превентивний контроль?
Обмежуйте ріст явними бюджетами: ретенція WAL/binlog, ротація логів і квоти де потрібно — плюс оповіщення за швидкістю зростання. «Ми помітимо» — це не контроль.
8) Скільки вільного місця тримати?
Тримайте стільки, щоб пережити найгірший сценарій росту до того, як люди зреагують: зазвичай 10–20% на томах баз даних, більше на томах для WAL/binlogs/temp, та додатково при пікових записах.
9) Чи змінює вибір файлової системи результати відновлення?
Він змінює поведінку відмови й інструменти. Зарезервовані блоки ext4 можуть маскувати «приповнення» для root; XFS-квоти поширені в мультиорендних налаштуваннях; ZFS має власні правила copy-on-write та «не заповнюйте пул». Обирайте свідомо й моніторьте відповідно.
10) Який найшвидший «повернути місце» крок, що зазвичай безпечний?
Очищення недатабазних артефактів: ротаційні логи, старі дампи, кеші пакетів, vacuum journald і очищення відкритих-але-видалених файлів. Торкайтеся внутрішностей бази лише після розуміння причин їх зростання.
Висновок: що робити наступного тижня
Інциденти з заповненням диска — це не про крихкість баз даних. Це про наш оптимізм. PostgreSQL має тенденцію відновлюватися чистіше, бо суворий щодо WAL і узгодженості. MySQL теж може відновитися швидко, але він дає більше операційних важелів — і більше можливостей поранитися.
Кроки, які дадуть миттєвий ефект:
- Розділіть і бюджетизуйте критичні шляхи запису: WAL PostgreSQL, binlogs/redo MySQL та місця для тимчасових спіл.
- Задійте оповіщення про швидкість росту і додайте «час до заповнення» на дашборди.
- Напишіть і відрепетируйте рукбук на випадок заповнення диска, який починається з
df,df -iіlsof +L1перед тим, як чіпати файли бази. - Нормалізуйте нудну гігієну: утримання логів, очищення дампів, дисципліна слотів/бінлогів і періодичне управління роздуванням.
Якщо ви зробите ці чотири речі, «заповнення диска» перестане бути трилером і стане звичайним завданням обслуговування. Це зниження рівня серйозності, якого варто прагнути.