Настройка File Descriptor Limits для высоконагруженных приложений

Под пиковой нагрузкой сервис начинает отказывать на ровном месте — и причина часто в неверно выставленных пределах. Мы настраиваем лимиты файловых дескрипторов под ключ: разбираем профиль нагрузки, приводим пределы системы и сервиса к согласованному виду и передаём команде регламенты. После работ отказы под пиком уходят, а число простоев сокращается в разы.
Часто задаваемые вопросы

Почему приложение упирается в лимиты файловых дескрипторов

Каждое соединение, открытый файл, сокет или канал — это файловый дескриптор, взятый из таблицы процесса. Таблица не бесконечна: её размер задаёт мягкий и жёсткий порог, который наследуется от родителя при старте. 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