# 5. Ограничения файловых дескрипторов

> Лимиты дескрипторов, расчёт потребности, системные ограничения и устранение неполадок

---

Индекс LLMS: [llms.txt](/ru/llms.txt)

---

<!-- Generated by scripts/generate-haproxy-docs.py from pinned upstream text. -->

Чтобы обеспечить успешное обслуживание всех входящих соединений, HAProxy при загрузке вычисляет
общее число файловых дескрипторов, которые потребуются за время жизни процесса.
Обычному процессу Unix по умолчанию обычно предоставляется 1024 файловых дескриптора, а привилегированный процесс
может самостоятельно увеличить этот лимит. Это одна из причин запускать HAProxy от имени root и позволять ему
настроить лимит. Лимит по умолчанию в 1024 файловых дескриптора позволяет обслуживать примерно 500 одновременных
соединений. Расчёт основан на глобальном параметре maxconn, ограничивающем общее
число соединений на процесс, числе слушателей, числе серверов с включённой
проверкой работоспособности, агентских проверках, одноранговых узлах, получателях журналов и, возможно, нескольких других технических
потребностях. Для грубой оценки достаточно удвоить значение maxconn
и добавить несколько десятков: получится приблизительное число необходимых файловых дескрипторов.

Изначально HAProxy не умел вычислять это значение, и его нужно было передавать
через параметр "ulimit-n" в глобальной секции. Поэтому даже сегодня этот параметр
встречается во множестве конфигураций. К сожалению, его часто рассчитывали неверно,
что приводило к сбоям соединений при приближении к maxconn вместо ограничения приёма входящих соединений
на время ожидания нужных ресурсов. Поэтому важно удалить все унаследованные
параметры "ulimit-n", которые могли остаться от очень старых версий.

Для работы даже с умеренной нагрузкой число файловых дескрипторов обязательно нужно увеличить, но это требует
некоторых настроек, зависящих от ОС. Во-первых, механизм опроса select() ограничен 1024 файловыми
дескрипторами. На самом деле в Linux он раньше мог обрабатывать и больше, однако некоторые ОС поставляются с
излишне строгими политиками SELinux, запрещающими select() использовать более 1024 файловых
дескрипторов. Поэтому теперь HAProxy в таком случае отказывается запускаться, чтобы избежать проблем во время выполнения.
Во всех поддерживаемых операционных системах доступен poll(), который не имеет этого ограничения. Он
выбирается автоматически, так что для получения рабочей конфигурации ничего делать не требуется. Однако poll()
становится очень медленным с ростом числа файловых дескрипторов. Хотя HAProxy старается уменьшить
влияние на производительность (например, используя внутренний кэш файловых дескрипторов и пакетную обработку),
практическое правило таково: использование poll() при более чем тысяче одновременных соединений создаёт
большую нагрузку на CPU.

В системах Linux с ядром 2.6 и выше используется системный вызов epoll(). Это гораздо
более масштабируемый механизм на основе обратных вызовов в ядре, которые гарантируют постоянное время пробуждения
независимо от числа зарегистрированных для наблюдения файловых дескрипторов. Он используется автоматически,
если обнаружен, при условии, что HAProxy собран для одного из вариантов Linux. Его наличие и
поддержку можно проверить с помощью "haproxy -vv".

В системах BSD с соответствующей поддержкой доступна альтернатива — kqueue(). Он значительно быстрее
poll() и даже немного быстрее epoll() благодаря пакетной обработке изменений. Его поддерживают как минимум
FreeBSD и OpenBSD. Как и в случае с Linux epoll(), сведения о его поддержке и доступности
выводятся командой "haproxy -vv".

Наличие хорошего механизма опроса — лишь часть задачи: процесс обязательно должен иметь возможность достичь нужных лимитов. При
запуске HAProxy сразу устанавливает новые лимиты файловых дескрипторов процесса и проверяет успешность
операции. В случае неудачи он сообщает об этом до создания дочернего процесса, чтобы администратор увидел
проблему. Если процесс запущен от имени root, оснований для отказа в этой настройке быть не должно.
Однако она может завершиться неудачно, если процесс запускает непривилегированный пользователь. Если есть
веская причина *не* запускать haproxy от имени root (например, его запускают конечные пользователи или
учётная запись отдельного приложения), системный администратор может увеличить лимит файловых дескрипторов
для конкретного пользователя. Действие настройки можно проверить командой "ulimit -n"
в оболочке этого пользователя. Она должна показать новый лимит.

Предупреждение: при изменении лимитов в учётной записи непривилегированного пользователя часто
оказывается, что новые значения учитываются только при входе пользователя в систему и совсем не применяются к некоторым сценариям,
выполняемым при загрузке системы, или к заданиям crontab. Это полностью зависит от операционной системы, поэтому
при таком способе запуска не забывайте проверять "ulimit -n" перед запуском haproxy. Общая рекомендация — никогда не
запускать haproxy от имени непривилегированного пользователя в промышленной эксплуатации. Ещё одна веская причина в том, что
такой запуск мешает haproxy включить некоторые защитные механизмы.

Когда установлено, что система разрешает процессу haproxy использовать запрошенное число
файловых дескрипторов, могут встретиться ещё два системных ограничения. Первое —
общесистемный лимит файловых дескрипторов, то есть общее число дескрипторов, открытых в
системе всеми процессами. При достижении этого лимита accept() или socket() обычно
возвращают ENFILE. Второе — жёсткий лимит числа файловых дескрипторов на процесс, который
не позволяет установить через setrlimit() более высокое значение. Оба сильно зависят от операционной системы. В
Linux системный лимит задаётся при загрузке в зависимости от объёма памяти. Его можно изменить
параметром sysctl "fs.file-max". Жёсткий лимит на процесс по умолчанию равен 1048576, но его можно
изменить параметром sysctl "fs.nr_open".

Если лимиты файловых дескрипторов слишком низкие, их действие можно наблюдать на работающем процессе.
Утилита strace покажет, что accept() и socket() возвращают "-1 EMFILE", когда достигнуты лимиты
процесса. В этом случае достаточно увеличить значение "ulimit-n" (или удалить его), чтобы решить
проблему. Если эти системные вызовы возвращают "-1 ENFILE", это означает, что достигнуты лимиты
ядра и нужно изменить общесистемный параметр. Такие проблемы обязательно необходимо
устранить: они приводят к высокой загрузке CPU (при сбоях accept()) и неудачным соединениям,
обычно заметным пользователю. Ещё один вариант решения — уменьшить глобальное значение maxconn,
чтобы обеспечить последовательное обслуживание, и, возможно, отключить режим HTTP keep-alive, чтобы соединения освобождались
и использовались повторно быстрее.
