Налаштування лімітів файлових дескрипторів для високонавантажених застосунків

Під піковим навантаженням сервіс починає відмовляти на рівному місці — і причина часто в неправильно виставлених лімітах. Ми налаштовуємо ліміти файлових дескрипторів під ключ: розбираємо профіль навантаження, приводимо ліміти системи та сервісу до узгодженого вигляду й передаємо команді регламенти. Після робіт відмови під піком зникають, а кількість простоїв скорочується в рази.
Часті запитання

Чому застосунок упирається в ліміти файлових дескрипторів

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

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

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

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

У логах це виглядає однаково: помилка «занадто багато відкритих файлів», відмова на прийомі нового з'єднання, при цьому процес живий і метрики здоров'я відповідають.

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

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

Що дають налаштовані ліміти: менше відмов і простоїв

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

Діагностика йде в бік застосунку та мережі, хоча причина в одному параметрі, який ніхто не виставляв під реальний профіль навантаження. Розбираємо, що змінюється, коли він виставлений.

Ліміти задаються явно — окремо soft і hard, прив'язані до самого сервісу, а не до інтерактивної сесії адміністратора. У systemd це LimitNOFILE в unit-файлі, для процесів поза менеджером служб — правила в limits.conf.

Інакше після перезапуску вузол повернеться до значення за замовчуванням, і інцидент повториться на тому самому місці.

Ефект вимірюється за трьома осями. Зниження відмов видно за часткою невдалих з'єднань і кодом повернення на прийомі — вона прямує до нуля в межах перевіреного запасу.

Час відновлення сервісу після піку скорочується: не потрібно чекати, поки звільняться дескриптори, які процеси тримають в очікуванні.

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

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

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

Варіанти налаштування лімітів: що підходить під навантаження

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

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

Тому налаштування під навантаження — це вибір рівня, а не пошук «правильного файлу».

Правка на одному рівні не діє на процеси, що стартують інакше, і ліміт мовчки залишається попереднім: застосунок упирається у відмову під час відкриття з'єднань, хоча в налаштуваннях усе виглядає коректно.

Рівень налаштування Коли підходить Обмеження
Системна стеля Загальний запас дескрипторів для всіх процесів вузла Задає верхню межу, але не розподіляє її між службами
М'який ліміт процесу Застосунок сам піднімає ліміт на старті Не може перевищити жорсткий ліміт
Жорсткий ліміт процесу Верхня межа, вище якої процес не підніметься Застосовується лише до нових процесів або після перезапуску
Ліміти для служб Демони, що стартують без входу користувача Не успадковуються інтерактивними сесіями та ручними запусками
Ліміти для користувацьких сесій Налагодження й ручний запуск з оболонки Не діють на служби, що стартують до входу

Перевіряють результат за фактичним значенням у працюючому процесі, а не за файлом налаштувань: перезапуск служби й нова сесія дають різну картину. Для навантаженого профілю піднімають і системну стелю — інакше служба упреться в загальний ліміт раніше, ніж у власний.

Як відбувається впровадження: від аудиту до передачі регламентів

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

  1. Аудит конфігурації. Збираємо фактичні значення за всіма шарами: обмеження ядра, системні пороги, параметри служби, яка запускає застосунок, і обмеження користувацької сесії. Розбіжність між шарами — типова причина, чому процес бачить не те, що прописали у файлі.
  2. Замір поточного стану. Дивимося, скільки дескрипторів процес тримає у спокої та на піку, як зростає число відкритих з'єднань і файлів, що залишилося в журналах після минулих відмов. Це дає відправну точку, а не здогад.
  3. Схема налаштування. Визначаємо, де саме правимо, які процеси отримують винятки, як значення застосується і до служби, і до ручного запуску. Розділяємо м'який і жорсткий пороги, фіксуємо запас щодо пікового споживання.
  4. Налаштування. Вносимо правки в конфігурацію та в опис служби, перезапускаємо за планом, а не в момент збою, перевіряємо, що нове значення справді підхоплено процесом.
  5. Перевірка під навантаженням. Відтворюємо робочий профіль: піковий трафік, сплеск з'єднань, відмова суміжного вузла. Окремо дивимося поведінку застосунку, коли він наближається до порогу.
  6. Спостережуваність. Налаштовуємо метрики за числом відкритих дескрипторів і з'єднань та оповіщення за швидкістю зростання — тоді витік видно задовго до того, як процес упреться в стелю.
  7. Передача регламентів. Віддаємо документ із цільовими значеннями, порядком застосування на новому вузлі, чек-лист перевірки після змін і таблицю «симптом → що дивитися».

Після передачі команда замовника може сама розкочувати ліміти на нових серверах і розуміти, коли зростання числа дескрипторів — це норма, а коли сигнал проблеми.

Конфігурації, схеми та документація за підсумками робіт

Налаштування лімітів не вважається завершеним, поки воно живе лише в історії команд на конкретному сервері.

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

  • Конфігураційні файли з роздільними м'якими та жорсткими значеннями для системних служб і користувацьких сесій, з коментарями: звідки взявся запас і що станеться при його зниженні.
  • Профілі запуску застосунків — де саме піднімається ліміт: в описі служби, в обгортці запуску або в оточенні контейнера, щоб значення не губилося під час перезапуску й деплою.
  • Схема інфраструктури з ролями вузлів і позначкою, на яких машинах ліміти підвищені, а на яких залишені базовими й чому.
  • Політики доступу: які ролі можуть змінювати параметри ядра й перезапускати служби, як це пов'язано з правами на конфігурацію.
  • Регламент обслуговування: як перевірити фактичне значення в працюючого процесу, які лічильники дивитися при зростанні навантаження і з якою періодичністю.
  • Розрахунок цільових значень: пікові одночасні з'єднання на процес, число обробників, службові з'єднання та запас на відновлення.
  • Чек-лист приймання після застосування та порядок відкату до попередніх значень, якщо нова конфігурація дасть неочікуваний ефект.

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

Кейс: прибрали відмови сервісу під піковим навантаженням

Як було

Сервіс на піку переставав приймати нові з'єднання: у журналах — відмова відкрити файл або сокет, назовні — сплеск помилок і обриви з'єднань.

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

Що змінили й що отримали

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

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

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

Спостережуваність перевели на причину. Число відкритих дескрипторів та його частка від ліміту виводяться на графік поруч із трафіком у Prometheus і на загальних панелях, оповіщення спрацьовує на підході до стелі, а не після відмови.

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

Чим системний ліміт відрізняється від ліміту сервісу

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

Ліміт процесу й служби — скільки дескрипторів може відкрити конкретний процес; він завжди нижчий за системний і успадковується під час запуску.

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

Рівень обмеження Де задається На що впливає Що перекриває
Системний параметри ядра сумарно по хосту стеля для всіх служб
Служба опис юніта в systemd лише цей сервіс значення користувацьких профілів
Користувацька сесія limits.conf через PAM інтерактивні входи м'який ліміт сесії
Процес ulimit під час запуску поточний і дочірні процеси нічого вище жорсткого ліміту

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

У межах одного рівня м'який ліміт — робоче значення, жорсткий — верхня межа: м'який можна підняти, але не вище жорсткого, а жорсткий не вище системного.

Фактичне значення дивимося за /proc/<pid>/limits у конкретного процесу, а не за файлами конфігурації. Порядок застосування: системна стеля → параметри юніта або батьківського процесу → успадкування дочірніми процесами → значення, з яким процес реально працює.

Часті технічні сумніви при налаштуванні лімітів

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

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

Чи доведеться перезапускати сервіс?

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

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

Чи не замаскує новий ліміт реальний витік?

Ризик є, і він головний. Якщо дескриптори не закриваються в обробниках, високий ліміт просто відкладе відмову. Тому перед зміною знімаємо знімок відкритих дескрипторів на кількох процесах: зростання, прив'язане до числа запитів, — це витік, рівний профіль — нестача запасу. Діагностику робимо до правок, а не після.

Як переконатися, що запас справді потрібен?

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

Оповіщення ставимо на 75–80% від ліміту, щоб був запас до деградації, і звіряємо його з історією викликів — так видно, чи зростає споживання з часом, чи впирається в сезонні піки.

Чи не конфліктує це з поточною конфігурацією?

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

Обговоримо задачу й повернемося з оцінкою та планом робіт

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

  • Склад сервісів: які процеси тримають відкриті з'єднання та файли — вебсервери, воркери черг, проксі, бази, — і скільки екземплярів працює на одному вузлі.
  • Характер навантаження: пікове число одночасних з'єднань, добовий профіль, як поводиться система в момент піку.
  • Поточні налаштування: м'який і жорсткий ліміт для користувача й процесу, обмеження в юніті служби, загальносистемний ліміт на відкриті файли.
  • Симптоми відмов: тексти помилок із логів, у який момент з'являються, що саме падає й перезапускається.
  • Спосіб запуску: хто ініціює процеси — служба, супервізор або контейнер, — і чи передаються ліміти всередину.
  • Що вже змінювали: які значення правили і чому результат не влаштував.
  • Обмеження оточення: контейнери, оркестратор, чи є права на зміну системних параметрів.

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

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    815
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139