Зараз 02:14. У панелі стану застосунок позначений як «up», але кожен запит, який звертається до бази даних, повертає ввічливу 500 і зовсім неввічливий рядок у логах: Забагато відкритих файлів. Ви збільшуєте ліміт, перезапускаєте — і «працює». Три дні. Потім знову відбувається, під час виплати заробітної плати, квартального закриття або будь-якого іншого ритуалу, який викликає хаос у бізнесі.
Це один із тих відмов, що виглядає як питання з аркуша про ОС, але насправді — проблема про системний дизайн. MariaDB і PostgreSQL зіштовхуються з цим по-різному, з різних причин і з різними налаштуваннями. Виправлення рідко зводиться до «встановити nofile на мільйон і рухатись далі». Це не виправлення. Це ставка.
Що насправді означає «Забагато відкритих файлів» (і чому це вводить в оману)
У Linux «Забагато відкритих файлів» зазвичай відповідає EMFILE: процес досягнув свого обмеження файлових дескрипторів для процесу. Іноді це ENFILE: система досягла глобального обмеження файлових дескрипторів. Іноді це ні те, ні інше — тоді ви маєте справу з обмеженням на рівні застосунку, яке логують як «open files», бо інженери оптимісти й називати речі — складно.
Файловий дескриптор (FD) — це дескриптор до «відкритої речі»: звичайний файл, директорія, Unix domain socket, TCP-сокет, pipe, eventfd, inotify watch, epoll-інстанс. Бази даних використовують усе це. Якщо думати лише «файли таблиць», ви поставите неправильний діагноз і невірно виправите проблему.
Дві важливі операційні істини:
- Виснаження FD рідко є проблемою лише одного перемикача. Це взаємодія між обмеженнями ОС, системними налаштуваннями systemd, конфігурацією бази даних, поведінкою підключень і формою навантаження.
- Виснаження FD — це симптом. Корінна причина зазвичай: забагато підключень, забагато об’єктів (таблиць/індексів/партицій) або налаштування кешу, яке перетворило «повторне використання відкритих файлів» на «відкривати все й ніколи не закривати».
Також: ви можете «виправити» EMFILE, підвищивши ліміти до того моменту, коли сервер зможе відкривати достатньо файлів, щоб просунутись, а потім зсунути відмову кудись іще: тиск пам’яті, вичерпання inode, інтенсивна робота dentry-кешу ядра або проста операційна складність. Мета — не нескінченні дескриптори. Мета — контрольоване використання ресурсів.
Цитата для нотатки: «Надія — не стратегія.» — General Gordon R. Sullivan. В опсах це не лише лозунг, а діагностичний інструмент.
Як у реальних серверах баз даних споживаються файлові дескриптори
Якщо ви налагоджуєте це в продакшені, вам потрібна модель того, що саме тримає FD відкритими. Ось неповний список того, що має значення.
Підключення: тихий фабрикатор FD
Кожне клієнтське підключення споживає щонайменше один FD на стороні сервера (сокет), плюс внутрішню обвязку. З TLS додається навантаження на CPU; із погано реалізованим пулюванням підключень з’являється чохлова зміна підключень і сплески. Якщо у вас 5000 активних підключень через «мікросервіси», ви не сучасні — ви просто платите за кожен сокет.
Файли даних, індексів і файли об’єктів
Бази даних намагаються уникати постійного повторного відкриття файлів. Кеші існують частково для того, щоб тримати FD відкритими, щоб ОС могла використовувати page cache і база уникала системних викликів. Але кеші можуть бути надмірно великими або неправильно налаштованими.
- MariaDB/InnoDB: кілька tablespace, redo логи, undo логи, тимчасові таблиці, per-table .ibd файли коли
innodb_file_per_table=ON. - PostgreSQL: кожен fork реляції (main, FSM, VM) відповідає файлам; великі реляції сегментуються в кілька файлів; тимчасові файли з’являються під
base/pgsql_tmpабо в тимчасових директоріях per-tablespace.
Тимчасові файли та spill-to-disk
Сортування, хеші, великі агрегати та певні плани запитів сповзають на диск. Це означає тимчасові файли. Достатньо паралельних запитів — і ви отримаєте невелику заметіль відкритих дескрипторів.
Реплікація та фонові воркери
Реплікаційні потоки, WAL senders/receivers, I/O-потоки та фонова робота тримають сокети й файли. Зазвичай не найбільший споживач, але в зайнятому кластері з кількома репліками це додається.
Логи, slow логи, аудиторські логи та «просто додайте більше спостережливості»
Логи — це файли. Деякі конфігурації логування відкривають кілька файлів (схеми ротації, окремі аудиторські логи, error/general логи). Якщо ви підсовуєте логи інструментами, які відкривають додаткові дескриптори, або запускаєте сайдкари, що роблять те саме, ви додаєте до тиску на FD. Зазвичай це не головний винуватець, але це частина рахунку.
Жарт №1: «Забагато відкритих файлів» — це спосіб сервера сказати, що він зараз емоційно недоступний.
MariaDB vs PostgreSQL: як вони поводяться при тиску на FD
Режими відмов MariaDB (InnoDB): кеш таблиць зустрічає реальність файлової системи
Найчастіший біль MariaDB пов’язаний з використанням файлів таблиць/індексів і поведінкою кешу таблиць у поєднанні з високою конкурентністю. Історично сервери сімейства MySQL покладалися на кеш таблиць (table_open_cache, table_definition_cache), щоб зменшити churn відкриття/закриття. Це добре — поки не перестає бути.
Що відбувається у «поганому» випадку:
- У вас багато таблиць або багато партицій (які фактично поводяться як таблицеподібні об’єкти), або багато схем.
- Ви ставите
table_open_cacheвисоким, бо хтось сказав, що це покращує продуктивність. - Навантаження торкається багатьох різних таблиць у різних сесіях.
- MariaDB намагається тримати їх відкритими, щоб задовольняти cache hits.
- Процес досягає
RLIMIT_NOFILE(пер-процес), або внутрішнього ліміту відкритих файлів сервера, і починає фейлити операції.
InnoDB додає свої власні аспекти:
innodb_open_filesзадає ціль, скільки InnoDB-файлів він може тримати відкритими, але це обмежується лімітами ОС та іншими користувачами файлів у процесі.- Використання тимчасових таблиць (на диску) може спричинити сплески FD.
- Інструменти резервного копіювання (логічні чи фізичні) можуть додавати навантаження і відкривати дескриптори.
Режими відмов PostgreSQL: підключення та накладні витрати на сесію
PostgreSQL використовує модель процес-на-підключення (з нюансами, як фонові воркери). Це означає, що кожне підключення — це власний процес з власною таблицею FD. Хороша новина: виснаження FD у межах одного процесу менш імовірне, якщо кожен бекенд має помірне використання FD. Погана новина: забагато підключень означає забагато процесів, забагато сокетів, забагато пам’яті, забагато контекстних перемикань і гуркіт ресурсів.
PostgreSQL зазвичай потрапляє на «забагато відкритих файлів» у таких сценаріях:
- Висока кількість підключень плюс низький FD-ліміт для postmaster/backends під systemd.
- Велика кількість об’єктів плюс запити, що торкаються багатьох реляцій в одній сесії (наприклад, партиційні таблиці з широкими сканами).
- Інтенсивне створення тимчасових файлів через сорти/хеші і паралельні запити, погіршене низьким
work_mem(більше spill) або надто великою паралельністю (більше одночасних spill-ів). - Autovacuum і технічне обслуговування для багатьох реляцій плюс робоче навантаження користувачів. Багато відкриттів файлів.
PostgreSQL також має тонку, але реальну поведінку: навіть якщо ви підвищите FD-ліміт ОС, ви все одно можете бути обмежені внутрішніми очікуваннями або іншими обмеженнями ОС (наприклад, max processes, налаштування shared memory або обмеження cgroup). EMFILE рідко приходить сам по собі.
Практична різниця, яка змінює підхід до виправлення
MariaDB частіше досягає виснаження FD через відкриті файли таблиць і кеші. Виправлення зазвичай комбінація правильної настройки LimitNOFILE, коректного open_files_limit і розумного розміру кешу таблиць — плюс робота над вибуховою кількістю таблиць/партицій.
PostgreSQL частіше досягає виснаження FD через поведінку підключень і сплески тимчасових файлів. Виправлення часто: пулювання підключень, зменшення кількості підключень, підвищення лімітів ОС за потреби та налаштування пам’яті/паралельності, щоб зменшити кількість spill-ів.
Цікавинки та історичний контекст (що справді має значення)
- Unix-файлові дескриптори були створені як уніфікована абстракція для «усе — це файл», що елегантно, доки ваша БД не вирішує трактувати все як «відкрито й ніколи не відпускати».
- Ранні Unix мали крихітні дефолтні ліміти FD (часто 64), і звичка консервативних налаштувань не зникла — systemd-дефолти все ще підводять сучасні сервери.
- Модель процес-на-підключення PostgreSQL — давній архітектурний вибір, який міняє простоту та ізоляцію на вищі накладні витрати при дуже великій конкурентності.
- Налаштування кешу таблиць MySQL походять із світу, де операції метаданих файлової системи були дорогими, і «тримати відкритим» було вигідно.
- Файлова система Linux /proc зробила інспекцію FD набагато простішою; до того діагностика витоків FD була як археологія.
- cgroups і контейнери змінили гру: на хості можуть бути високі ліміти, але в контейнері — низькі; процес бачить менший світ і падає там.
- Сучасні файлові системи зробили open/close дешевшими ніж раніше, але «дешево» не означає «безкоштовно», коли множити тисячами запитів за секунду.
- Реплікація збільшила схем використання FD в обох екосистемах, додаючи сокети та активність логів — особливо в топологіях з кількома репліками.
Швидкий план діагностики
Це та частина, якої ви дотримуєтесь, коли чергуєте, напівпрокинутий, і ваш мозок намагається укласти перемир’я з реальністю.
Перш за все: підтвердіть, яке обмеження ви досягаєте (процесне чи системне)
- Перевірте джерело помилки: логи бази даних, системні логи та логи застосунку. Визначте, чи сам процес DB не може відкрити файли, чи клієнти не можуть підключитись.
- Перевірте пер-процесний ліміт: інспектуйте
Max open filesпроцесу бази даних у/proc. Якщо він низький (часто 1024/4096), ви знайшли ймовірну негайну причину. - Перевірте системний тиск файлових дескрипторів:
/proc/sys/fs/file-nr. Якщо системний рівень близький до максима, підвищення пер-процесного ліміту не допоможе без збільшення глобальної ємності і знаходження споживача.
Друге: ідентифікуйте, хто тримає FD
- Порахуйте відкриті FD по PID і знайдіть найбільших споживачів. Якщо це DB — продовжуйте. Якщо це сайдкар, відправник логів або агент резервного копіювання — у вас інший інцидент.
- Класифікуйте типи FD: чи це в основному сокети (підключення) або звичайні файли (таблиці, тимчасові файли, логи)? Це покаже, які налаштування бази даних важливі.
Третє: визначте, чи це «сплеск» чи «витік»
- Сплеск: FD різко зростають під час трафікового сплеску або пакетної роботи, потім падають. Виправлення: ємність і контроль конкурентності.
- Витік/стика: FD поступово зростають і не повертаються. Виправлення: знайдіть, що тримає відкритим (занадто великий кеш, помилка, застряглі підключення, витік дескрипторів у інструментах).
Четверте: зупиніть кровотечу безпечно
- Короткостроково: підвищуйте ліміти лише якщо впевнені, що ядро має запас і ви не викликаєте тиск пам’яті. Віддавайте перевагу контрольованому перезапуску з правильними лімітами над хаотичним ulimit-маніпулюванням.
- Зменшіть конкурентність: обмежте пакетні задачі, зменшіть кількість робітників у застосунку або увімкніть пулювання. База даних, яка не може відкривати файли, також не може обробляти запити.
Практичні задачі: команди, виводи та рішення (12+)
Це ті завдання, що переводять «я думаю» в «я знаю». Кожне містить команду, реалістичний фрагмент виводу, що це означає і який висновок робити далі.
Задача 1: Підтвердіть процес DB та PID
cr0x@server:~$ ps -eo pid,comm,args | egrep 'mariadbd|mysqld|postgres' | head
1287 mariadbd /usr/sbin/mariadbd
2140 postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main
2142 postgres postgres: checkpointer
Значення: У вас MariaDB на PID 1287 і PostgreSQL postmaster на PID 2140 (плюс воркери). Зрозумійте, який саме падає; не «виправляйте» обидві системи відразу.
Рішення: Виберіть PID(и), які будете перевіряти в наступних кроках. Якщо помилка в додатку, підтвердіть, який DB endpoint використовується.
Задача 2: Перевірте пер-процесний максимум відкритих файлів (той, що зазвичай кусає)
cr0x@server:~$ cat /proc/1287/limits | egrep -i 'open files|max processes'
Max open files 1024 1048576 files
Max processes 127636 127636 processes
Значення: Soft ліміт — 1024; hard — 1048576. MariaDB живе на індексній дієті.
Рішення: Виправте сервісний юніт або PAM-ліміти, щоб DB стартувала з адекватним soft-лімітом (наприклад, 65535 або вище залежно від розрахунку). Не лише підвищуйте hard і забувайте про soft.
Задача 3: Порахуйте поточні відкриті FD для PID
cr0x@server:~$ ls -1 /proc/1287/fd | wc -l
1008
Значення: Процес сидить під стелею 1024. EMFILE неминучий або вже відбувається.
Рішення: Негайні заходи: зменшити навантаження і підготувати перезапуск з виправленими лімітами. Також знайдіть, що споживає FD (наступні задачі).
Задача 4: Ідентифікуйте типи відкритих FD (файли vs сокети)
cr0x@server:~$ ls -l /proc/1287/fd | awk '{print $11}' | sed -e 's/.*socket:.*/socket/' -e 's/.*pipe:.*/pipe/' -e 's/.*anon_inode:.*/anon_inode/' | sort | uniq -c | sort -nr | head
612 socket
338 /var/lib/mysql/db1/orders.ibd
42 anon_inode
16 pipe
Значення: Переважно сокети та InnoDB-файли таблиць. Це не просто «занадто багато таблиць» або лише «занадто багато підключень». Це і те, й інше.
Рішення: Розслідуйте одночасно кількість підключень і налаштування кешу таблиць. Виправивши тільки одну сторону, ви можете просто зсунути вузьке місце.
Задача 5: Перевірте системне використання файлових дескрипторів (глобальний тиск)
cr0x@server:~$ cat /proc/sys/fs/file-nr
38144 0 9223372036854775807
Значення: Система загалом в порядку; глобальний ліміт фактично гігантський. Це проблема на рівні процесу, а не глобальна.
Рішення: Зосередьтесь на systemd/PAM-лімітах і конфігурації DB, а не на kernel fs.file-max.
Задача 6: Перегляньте ліміти служби systemd (прихований винуватець)
cr0x@server:~$ systemctl show mariadb -p LimitNOFILE -p LimitNPROC -p TasksMax
LimitNOFILE=1024
LimitNPROC=127636
TasksMax=4915
Значення: systemd явно встановлює LimitNOFILE=1024. Ви можете редагувати /etc/security/limits.conf скільки завгодно; systemd все одно переможе для сервісів.
Рішення: Додайте systemd override з вищим LimitNOFILE і перезапустіть сервіс. Також розгляньте TasksMax, якщо у вас PostgreSQL з багатьма бекендами.
Задача 7: Застосуйте systemd override для MariaDB або PostgreSQL
cr0x@server:~$ sudo systemctl edit mariadb
# (opens editor)
cr0x@server:~$ sudo cat /etc/systemd/system/mariadb.service.d/override.conf
[Service]
LimitNOFILE=65535
Значення: Ви встановили новий сервісний FD-ліміт. Це правильний рівень для сервісів.
Рішення: Перезавантажте systemd і перезапустіть MariaDB у контрольоване вікно. Потім перевірте /proc/<pid>/limits.
Задача 8: Перезавантажте systemd і перевірте, що новий ліміт застосовано
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart mariadb
cr0x@server:~$ systemctl show mariadb -p LimitNOFILE
LimitNOFILE=65535
Значення: Сервіс тепер стартує з більшим FD-стелею.
Рішення: Якщо ви все ще отримуєте EMFILE, то проблема не в «занадто малому ліміті» — це «навантаження споживає надто багато FD». Продовжуйте діагностику.
Задача 9: MariaDB — перевірте поточні налаштування відкритих файлів і кешу таблиць
cr0x@server:~$ mariadb -e "SHOW VARIABLES WHERE Variable_name IN ('open_files_limit','table_open_cache','table_definition_cache','innodb_open_files');"
+------------------------+--------+
| Variable_name | Value |
+------------------------+--------+
| innodb_open_files | 2000 |
| open_files_limit | 65535 |
| table_definition_cache | 4000 |
| table_open_cache | 8000 |
+------------------------+--------+
Значення: MariaDB дозволено відкривати багато файлів, і вона налаштована тримати багато таблиць відкритими. Це може бути доречно — або надто оптимістично — в залежності від кількості таблиць і пам’яті.
Рішення: Порівняйте з реальністю: кількість таблиць/партицій, шаблон доступу і використання FD. Якщо в steady state ви відкриваєте 30k файлів, 65k може бути нормою; якщо у вас 60k і зростає — потрібні архітектурні зміни.
Задача 10: MariaDB — оцініть кількість таблиць і партицій
cr0x@server:~$ mariadb -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys');"
18432
Значення: Вісімнадцять тисяч таблиць (або партицій, представлених як таблиці в метаданих) — це багато. Кеші таблиць, встановлені на 8000, можуть або частково працювати, або підтримувати тисячі відкритих файлів залежно від шаблону доступу.
Рішення: Якщо це стратегія партиціонування, що вийшла з-під контролю, розгляньте консолідацію партицій, використання меншої кількості схем або винесення архівних даних з активної БД. Якщо це легітимно, визначте FD-ліміти і кеші свідомо та моніторте.
Задача 11: PostgreSQL — перевірте max_connections і активні сесії
cr0x@server:~$ sudo -u postgres psql -c "SHOW max_connections; SELECT count(*) AS current_sessions FROM pg_stat_activity;"
max_connections
-----------------
800
(1 row)
current_sessions
------------------
742
(1 row)
Значення: Ви близькі до налаштованого ліміту підключень. Кожне підключення — це процес. Навіть якщо FD-ліміти високі, це запах ресурсного тиску.
Рішення: Якщо додатки відкривають сотні простаючих підключень, впровадьте пулер (PgBouncer у transaction mode — це звичний вибір) і зменшіть max_connections до числа, яке ви можете підтримувати.
Задача 12: PostgreSQL — швидко перевірте використання FD на бекенд
cr0x@server:~$ for p in $(pgrep -u postgres -d ' ' postgres); do printf "%s " "$p"; ls -1 /proc/$p/fd 2>/dev/null | wc -l; done | sort -k2 -n | tail
3188 64
3191 68
3201 71
3210 74
3222 91
Значення: Бекенди індивідуально не споживають багато FD (десятки кожен), але помножені на 700 сесій це все одно багато сокетів та внутрішніх дескрипторів.
Рішення: Якщо postmaster або спільна підсистема досягає ліміту, підвищте LimitNOFILE для сервісу. Якщо система загалом перевантажена, спочатку виправте стратегію підключень.
Задача 13: PostgreSQL — знайдіть тиск тимчасових файлів (spill-и)
cr0x@server:~$ sudo -u postgres psql -c "SELECT datname, temp_files, temp_bytes FROM pg_stat_database ORDER BY temp_bytes DESC LIMIT 5;"
datname | temp_files | temp_bytes
-----------+------------+--------------
appdb | 18233 | 429496729600
postgres | 0 | 0
template1 | 0 | 0
template0 | 0 | 0
(4 rows)
Значення: Багато тимчасових файлів і сотні ГБ, що були вивантажені з моменту скидання статистики. Це корелює з churn-ом FD і піками дискового вводу-виводу під час важких запитів.
Рішення: Знайдіть запити, що спричиняють spill-и, тонко налаштуйте work_mem і/або зменшіть конкурентність/паралельність. Менше spill-ів — менше тимчасових файлів і відкритих дескрипторів.
Задача 14: Подивіться, хто ще споживає FD (топ процесів)
cr0x@server:~$ for p in $(ps -e -o pid=); do n=$(ls -1 /proc/$p/fd 2>/dev/null | wc -l); echo "$n $p"; done | sort -nr | head
18421 1287
2290 1774
1132 987
640 2140
Значення: MariaDB — топ споживач FD (18421). PostgreSQL postmaster значно менший. Інцидент ймовірно пов’язаний з MariaDB, а не «хостом» загалом.
Рішення: Сконцентруйтеся на виправленні. Якщо сайдкар або проксі на другому місці — перевірте його також: іноді «проблема БД» насправді спричинена зловмисним сайдкаром.
Задача 15: Перевірте повідомлення ядра про помилки, пов’язані з FD
cr0x@server:~$ sudo dmesg -T | tail -n 10
[Wed Dec 31 02:13:51 2025] mariadbd[1287]: EMFILE: too many open files
[Wed Dec 31 02:13:52 2025] mariadbd[1287]: error opening file ./db1/orders.ibd (errno: 24)
Значення: Чітке підтвердження: errno 24 (EMFILE). Це не помилка зберігання; це проблема ліміту FD.
Рішення: Розглядайте як проблему ємності/конфігурації. Не витрачайте час на перевірки файлової системи, якщо не бачите I/O помилок.
Три мініісторії з корпоративного життя
Міні-історія 1: Інцидент через хибне припущення
Вони мігрували моноліт до «сервісів», залишили ту саму MariaDB і святкували перший тиждень зелених дашбордів. Нова команда сервісів мала акуратну звичку: кожен сервіс тримав теплий пул підключень «для продуктивності». Ніхто не координував; кожен робив те, що працювало локально.
На кінець місяця запустилася пакетна задача, що торкнулась великого набору таблиць. Тим часом сервіси робили свою звичну роботу — плюс шторм повторних підключень через зріст латентності. MariaDB почала кидати «Забагато відкритих файлів». Інженер на виклику припустив, що це ядро, і збільшив fs.file-max. Помилка тривала.
Справжній ліміт був у systemd — LimitNOFILE=1024 для сервісу MariaDB. І навіть після його збільшення сервер усе ще перебував у небезпечній зоні, бо кількість підключень подвоїлася, різко піднявши кількість сокетів. Неправильне припущення полягало в тому, що глобальні налаштування хоста переважать сервісні ліміти, і що пулювання підключень — це безкоштовна розкіш.
Вони виправили ситуацію правильно: задали явний LimitNOFILE, розумно підібрали кеші MariaDB і ввели справжній шар пулювання на боці додатків. Також ввели правило: розмір пулу підключень має бути бюджетований як пам’ять — бо він і є пам’ять і ще файлові дескриптори.
Міні-історія 2: Оптимізація, що повернулась бумерангом
Інша компанія працювала на PostgreSQL і мала хронічні проблеми з латентністю під час аналітики. Доброзичливий інженер збільшив налаштування паралельних запитів і підсунув кілька планувальних перемикачів. Перший бенчмарк виглядав чудово. Усі тихенько аплодували.
Потім прийшло реальне навантаження: багато одночасних користувачів звітності, кожен запускав запит, що спускався на диск. Паралельні воркери примножили кількість творців тимчасових файлів. Тимчасові файли вибухнули. Диск I/O підскочив. І так — використання FD зросло, бо кожен воркер відкривав свій набір файлів.
Відмова не була завжди «забагато відкритих файлів». Вона була переривчастою: кілька сесій падали, деякі запити зависали, і додаток тайм-аутував. Таймлайн інциденту перетворився на кашу, бо симптоми виглядали як «повільне сховище», потім як «погані плани», і нарешті як «випадкова ОС-флецюватість».
Оптимізація зіграла зле, бо збільшила внутрішню конкурентність у найгіршому місці: у двигуні БД під час операцій з великими spill-ами. Виправлення: зменшити паралельність, обережно підвищити work_mem для ролі звітності та ввести ліміти підключень для цього шару. Продуктивність покращилась, і FD-сплески перестали бути подією.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Одна команда мала нудну на папері операційну стандартну практику: для кожного хоста БД задокументовано бюджет FD з алертами на 60% і 80% від ефективного пер-сервіс ліміту. Вони також логували «топ споживачів FD» як періодичну метрику, а не лише в інцидентах.
Це виглядало як бюрократія, поки оновлення від вендора не ввело тонку зміну: нова функція відкривала нове підключення під кожен запит при вімкненому feature flag. Кількість підключень повільно росла протягом тижня. Аварій поки не було — лише поступове збільшення сокетів.
Алерт на 60% спрацював у робочий час. Вони розслідували без тиску, побачили тренд і пов’язали його з feature flag. Відкликнули його, потім впровадили PgBouncer і обмежили створення підключень у додатку.
Нічого не загорілося. Нікому не довелося звітувати про запобігувальний проступок фінансам. Це був найменш захопливий інцидентний звіт — що є найвищою похвалою практиці SRE.
Справжнє виправлення: розміри, ліміти та регулятори, що справді мають значення
«Збільшити ulimit» — це аспірин. Іноді аспірин потрібен. Але якщо ви приймаєте аспірин щодня — ви не лікуєте хворобу.
Крок 1: Встановіть розумні OS/сервісні ліміти (правильний рівень, правильна персистентність)
Для сучасних Linux-розгортань правда така: systemd — джерело істини для сервісів. Задайте LimitNOFILE у drop-in override для сервісу бази даних. Перевірте після рестарту через /proc/<pid>/limits.
Обирайте число свідомо:
- Невеликі сервери (один інстанс, помірна схема): 65535 — поширена базова величина.
- Великі MariaDB з багатьма таблицями/партиціями або великою конкурентністю: 131072+ може бути виправданим.
- PostgreSQL з пулюванням і контрольованими підключеннями: вам, можливо, не потрібні величезні значення, але не лишайте 1024 — це самоcаботаж.
Також: уникайте встановлення «безкінечності» просто тому, що можете. Кожен FD має накладні витрати в ядрі. Надзвичайні ліміти приховують витоки, доки вони не стануть катастрофою.
Крок 2: Зменшіть реальний попит на FD
Ось де MariaDB і PostgreSQL розходяться на практиці.
MariaDB: припиніть накопичувати таблиці ніби це 2009 рік
MariaDB може тримати тисячі таблиць відкритими, якщо ви її так налаштуєте. Якщо ваша схема має десятки тисяч таблиць/партицій, «тримати багато відкрито» стає структурним ризиком.
Що робити:
- Підійміть до обґрунтованого розміру
table_open_cacheіtable_definition_cache. Більше — не завжди краще. Якщо у вас немає пам’яті, щоб тримати метадані і дескриптори теплими, ви просто отримаєте інший вигляд трясіння системи. - Встановіть
open_files_limitтаinnodb_open_filesузгоджено. Не дозволяйте одному бути маленьким, а іншому — гігантським. Це створює хибну впевненість «має працювати». - Слідкуйте за партиційною експлозією. Тисячі партицій здаються приємними, доки не перетворяться на проблему дескрипторів і планування запитів.
PostgreSQL: спочатку вирішіть підключення, потім spill-и
Найпростіший FD-вин у PostgreSQL — це не FD-налаштування. Це пулювання підключень. Якщо ви запускаєте сотні чи тисячі клієнтських сесій безпосередньо до Postgres, ви поводитесь з базою даних як з вебсервером. Вона ним не є.
Що робити:
- Використовуйте пулер (PgBouncer — звичний вибір) і зменшіть
max_connectionsдо числа, яке ви можете забезпечити. - Виправляйте шторм повторних підключень. Якщо клієнти агресивно перепідключаються при транзієнтних помилках, вони можуть створити сокетні шторми, що виводять FD за межі.
- Зменшіть spill-и. Spill-и створюють тимчасові файли; тимчасові файли споживають FD під час свого життя. Налаштуйте пам’ять для класів навантаження і зменшіть паралельні worker-и, якщо вони створюють більше одночасних spill-ів, ніж ви можете обробити.
Жарт №2: Встановити LimitNOFILE на мільйон — як купити більший шафа замість того, щоб викинути колекцію коробок зі сувенірами з конференцій.
Крок 3: Перевірте, що ви не просто перемістили вузьке місце
Після підвищення FD-лімітів і зменшення попиту перевірте наступні режими відмов:
- Тиск пам’яті: більше підключень і кешів означає більше RSS. Слідкуйте за свапом уважно; свап для БД — це пародія на продуктивність.
- CPU і контекстні перемикання: занадто багато PostgreSQL-бекендів можуть розплавити CPU без явного окремого «поганого» запиту.
- Диск і використання inode: інтенсивне використання тимчасових файлів може швидко споживати inode і місце на диску, особливо на малих кореневих томах.
- Інші kernel-ліміти, крім nofile: max processes, обмеження cgroup pids, вичерпання епемерних портів (на стороні клієнта) і налаштування мережевого backlog.
Типові помилки: симптом → корінна причина → виправлення
Цей розділ навмисно різкий. Більшість EMFILE-інцидентів — самонанесені, просто не тим, хто зараз тримає pager.
Помилка 1: «Ми збільшили fs.file-max, чому це не допомогло?»
Симптом: «Забагато відкритих файлів» триває після збільшення /proc/sys/fs/file-max.
Корінна причина: Пер-процесний/сервісний ліміт (RLIMIT_NOFILE) все ще низький, часто встановлений systemd.
Виправлення: Встановіть LimitNOFILE у systemd unit override, перезапустіть БД, перевірте через /proc/<pid>/limits.
Помилка 2: «Ми встановили ulimit у /etc/security/limits.conf; усе ще не працює»
Симптом: У ручних сесіях оболонки ulimit -n високий, але сервіс не успадковує це.
Корінна причина: PAM-ліміти впливають на інтерактивні сесії; systemd-сервіси не успадковують їх так само.
Виправлення: Налаштуйте systemd unit. Розглядайте PAM-ліміти як релевантні для інтерактивних сесій, а не для демонів.
Помилка 3: «Ми збільшили table_open_cache; тепер EMFILE» (MariaDB)
Симптом: MariaDB помилки при відкритті таблиць; логи показують errno 24; кількість FD продовжує рости.
Корінна причина: Кеш таблиць надто великий для схеми/навантаження; сервер намагається тримати надто багато дескрипторів відкритими.
Виправлення: Зменшіть table_open_cache до вимірюваного значення, підвищте LimitNOFILE у сервісі відповідно та опрацюйте кількість таблиць/партицій.
Помилка 4: «Postgres може впоратися з 2000 підключень, це нормально»
Симптом: Випадкові відмови підключень, високі навантаження, іноді EMFILE, іноді просто тайм-аути.
Корінна причина: Надто багато бекенд-процесів; FD і пам’ять масштабуються з сесіями; сплески штовхають ліміти.
Виправлення: Додайте пулер, зменшіть max_connections і впровадьте бюджет підключень на рівні сервісу.
Помилка 5: «БД протікає FD» (коли насправді це сплески тимчасових файлів)
Симптом: Кількість FD стрибає під час деяких запитів/пакетів, потім знижується.
Корінна причина: Тимчасові файли на диску і паралельність створюють тимчасові хвилі FD.
Виправлення: Ідентифікуйте запити, що викликають spill-и; налаштуйте пам’ять/паралельність; плануйте батчі; обмежте одночасність.
Помилка 6: «Це сховище» (коли це насправді дескриптори)
Симптом: Запити не можуть відкрити файли; підозрюють корупцію файлової системи або повільні диски.
Корінна причина: errno 24 (EMFILE) — це не I/O помилка; це ліміт FD.
Виправлення: Підтвердьте errno через логи/dmesg; перевірте /proc ліміти; відкоригуйте налаштування сервісу та БД.
Помилка 7: «Ми виправили перезапуском»
Симптом: Перезапуск тимчасово вирішує проблему; під навантаженням вона повертається.
Корінна причина: Перезапуск скидає використання FD і кеші; базовий попит не змінився.
Виправлення: Виконайте роботу з розрахунку: ліміти + стратегія підключень + розумний розмір кешів таблиць/схеми + моніторинг.
Чеклісти / покроковий план
Чекліст A: Екстрена стабілізація (15–30 хвилин)
- Підтвердіть, чи MariaDB, чи PostgreSQL видає EMFILE (логи + PID).
- Перевірте
/proc/<pid>/limitsна Max open files. - Порахуйте відкриті FD:
ls /proc/<pid>/fd | wc -l. - Класифікуйте типи FD: сокети vs файли таблиць vs тимчасові файли.
- Якщо сервісний ліміт низький — застосуйте systemd override (
LimitNOFILE) і сплануйте контрольований рестарт. - Тротлінг: зменшіть кількість робітників додатка, призупиніть важкі пакетні задачі та відключіть шторм повторних спроб, якщо можливо.
- Після рестарту перевірте, що ліміт застосовано і використання FD стабілізувалося нижче 60% від ліміту.
Чекліст B: Корінна причина і довгострокове виправлення (впродовж дня)
- Задокументуйте базове використання FD в стані спокою, під нормальним піком і під найгіршим піком.
- Для MariaDB: інвентаризуйте кількість таблиць/партицій; перегляньте
table_open_cache,open_files_limit,innodb_open_files. - Для PostgreSQL: виміряйте кількість підключень у часі; визначте клієнтів, що створюють найбільше сесій; впровадьте пулер.
- Перевірте статистику тимчасових файлів і повільні запити; зв’яжіть сплески FD з розкладами пакетних задач.
- Встановіть алерти на використання FD по PID бази та на кількість підключень.
- Запустіть контрольоване навантаження, щоб підтвердити виправлення під реалістичною конкуренцією і схемою.
Чекліст C: Запобігання (тут перемагають досвідчені)
- Створіть бюджет дескрипторів для середовищ: dev, staging, production.
- Забезпечте бюджети підключень для кожного сервісу. Без винятків без рев’ю.
- Відслідковуйте зріст схеми (таблиці, партиції, індекси) як ключовий показник ємності.
- Зробіть systemd overrides частиною конфігураційного керування, а не племінним знанням.
- Тестуйте відновлення і поведінку рестарту з вашими лімітами, щоб забезпечити швидке відновлення.
FAQ
1) Чи завжди «Забагато відкритих файлів» — вина бази даних?
Ні. Часто тригером є БД, але це може бути проксі (наприклад, HAProxy), відправник логів, агент резервного копіювання або навіть сервер застосунку, що вичерпав власні FD і неправильно це промаркував.
2) У чому різниця між EMFILE і ENFILE?
EMFILE означає, що процес досягнув пер-процесного ліміту FD. ENFILE означає, що система досягла глобального ліміту файлових дескрипторів. Більшість інцидентів з БД — це EMFILE.
3) Чому systemd ігнорує мої зміни в /etc/security/limits.conf?
PAM-ліміти зазвичай застосовуються до login-сесій. Сервіси systemd використовують власні ліміти, якщо не налаштовано інакше. Виправте unit з LimitNOFILE.
4) Який розумний LimitNOFILE для MariaDB?
Почніть з 65535, якщо не знаєте. Потім підбирайте за кількістю підключень (сокети), відкритих таблиць/партицій, тимчасових файлів і допоміжних FD. Якщо у вас величезна кількість партицій, можливо, знадобиться 131072 або більше — але тоді варто запитати, чому їх так багато.
5) Який розумний LimitNOFILE для PostgreSQL?
Зазвичай 65535 як базова величина підходить. Більший виграш — контроль підключень та зменшення spill-ів. Якщо вам потрібно масивне число FD для Postgres, ймовірно у вас неконтрольована конкурентність або екстремальний churn реляцій.
6) Чи можна просто збільшити max_connections для вирішення помилок підключень?
Можна, але ви міняєте «відмова при підключенні» на «сервер у вогні». Для PostgreSQL використовуйте пулювання підключень і тримайте max_connections в межах, які пам’ять і CPU можуть обслуговувати.
7) Чому в списках FD багато сокетів?
Бо кожне клієнтське підключення — це сокет FD. Якщо сокети домінують, фокусуйтеся на кількості підключень, пулуванні і поведінці повторних спроб. Якщо звичайні файли домінують, фокус — на кеші таблиць, розмірі схеми та churn-і тимчасових файлів.
8) Чи має підвищення FD-лімітів негативні сторони?
Так. Вищі ліміти полегшують витік або неконтрольоване навантаження, яке споживатиме більше ресурсів ядра перед відмовою. Ви впадете пізніше, можливо сильніше, і зона ураження збільшиться. Підвищуйте ліміти, але паралельно зменшуйте попит і моніторте.
9) Як зрозуміти, витік це чи сплеск?
Якщо використання FD постійно зростає і не повертається після зниження навантаження — підозрюйте витік або поведінку кешу, що тримає ресурси відкритими. Якщо це сплеск під час пакету чи трафіку, що потім повертається до бази — це питання ємності/конкуренції.
10) Чи дійсно партиції важливі для FD?
Так. У обох екосистемах партиції збільшують кількість об’єктоподібних реляцій. Більше об’єктів може означати більше метаданих, більше відкритих файлів і більше накладних на планування/обслуговування. Партиціонування — це інструмент, а не святий грааль.
Практичні наступні кроки
Якщо ви в середині інциденту: застосуйте швидкий план діагностики, виправте сервісний FD-ліміт і зменшіть конкурентність. Це дасть вам повітря.
Потім зробіть дорослу роботу:
- Вимірюйте використання FD за типом (сокети vs файли) і по станах: steady-state vs пік.
- MariaDB: налаштуйте кеші таблиць під розмір схеми, контролюйте партиції; вирівняйте
open_files_limitіinnodb_open_filesз лімітами ОС. - PostgreSQL: впровадьте пулювання підключень, зменшіть
max_connections, і зменшіть spill-и, налаштовуючи пам’ять/паралельність та оптимізуючи найгірші запити. - Моніторьте використання FD і встановіть алерти перед тим, як ви впадете з урвища. Уривок — це не навчальний майданчик; це генератор простоїв.