# 7. Использование CPU

> Потоки, привязка к CPU, полная загрузка, профилирование и особенности производительности

---

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

---

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

Обычно HAProxy проводит большую часть времени в системном режиме, а меньшую — в пользовательском пространстве. Тщательно настроенный CPU с частотой 3.5 GHz способен поддерживать около 80000 установок и закрытий сквозных соединений в секунду при загрузке CPU 100% на одном ядре. При полной загрузке одного ядра типичны следующие показатели:

- 95% в системном режиме, 5% в пользовательском — для длительных соединений TCP или больших объектов HTTP;
- 85% в системном режиме и 15% в пользовательском — для коротких соединений TCP или маленьких объектов HTTP в режиме закрытия соединений;
- 70% в системном режиме и 30% в пользовательском — для маленьких объектов HTTP в режиме постоянных соединений.

Обработка правил и регулярных выражений увеличивает долю пользовательского времени. Наличие правил межсетевого экрана, отслеживания соединений и сложных таблиц маршрутизации в системе, напротив, увеличивает долю системного времени.

В большинстве систем время CPU при передаче данных по сети можно разделить на 4 части:

- Время обработки прерываний: вся обработка при поступлении ввода-вывода, ещё до того, как определён целевой процесс. Обычно приём пакетов Rx учитывается как обработка прерываний. В некоторых системах, например Linux, обработка прерываний может откладываться и передаваться выделенному потоку; тогда она может отображаться как softirq, а поток называется ksoftirqd/0 (для CPU 0). CPU, выполняющий эту работу, обычно определяется настройками оборудования, хотя для softirq часто можно переназначить обработку на другой CPU. Эту долю времени часто воспринимают как паразитную, поскольку она не связана ни с одним процессом, но в действительности это подготовительная работа для процесса.

- Системное время: вся обработка кодом ядра, вызываемым из пользовательского пространства. Например, системные вызовы учитываются как системное время. Все синхронно отправляемые пакеты Tx также учитываются здесь. Если отправку некоторых пакетов приходится отложить из-за заполнения очередей, впоследствии они могут обрабатываться в контексте прерывания (например, при получении ACK, открывающего окно TCP).

- Пользовательское время: выполнение исключительно кода приложения в пользовательском пространстве. HAProxy работает только в этой области, хотя активно использует системные вызовы. Обработка правил, регулярные выражения, сжатие и шифрование увеличивают пользовательскую долю потребления CPU.

- Время простоя: то, что CPU делает, когда работы нет. Например, HAProxy ожидает входящего соединения или отправки данных, то есть система ждёт ACK от клиента, чтобы передать эти данные.

На практике для оценки работы HAProxy обычно достаточно разумно (хотя и совершенно неточно) считать, что прерывания и softirq вызваны обработкой Rx в драйверах ядра, пользовательское время — обработкой на уровне 7 в HAProxy, а системное время — сетевой обработкой на пути Tx.

Поскольку HAProxy построен вокруг цикла событий, он ожидает новых событий с помощью poll() (или аналога), максимально быстро обрабатывает их, а затем возвращается к poll() для ожидания следующих событий. Он измеряет время ожидания в poll() относительно времени обработки событий. Отношение времени опроса к общему времени называется временем простоя «idle»: это доля времени, затраченная на ожидание событий. Она отображается на странице статистики в строке «idle» или в CLI как «Idle_pct». Значение, близкое к 100%, означает крайне низкую нагрузку. Значение, близкое к 0%, означает постоянную активность. В перегруженной системе этот показатель не может быть очень точным, поскольку другие процессы могут вытеснять haproxy с CPU, однако он хорошо отражает, как HAProxy оценивает собственную работу. Если и нагрузка, и доля простоя низкие, это может означать, что HAProxy выполняет много работы, возможно, обрабатывает очень затратные правила. Напротив, если HAProxy показывает простой, близкий к 100%, но работа идёт медленно, он ничего не может сделать для ускорения: он уже ожидает входящих данных для обработки. В следующем примере haproxy полностью бездействует:

```shell
$ echo "show info" | socat - /var/run/haproxy.sock | grep ^Idle
Idle_pct: 100
```

Когда доля простоя становится очень низкой, важно настроить систему и правильно распределить процессы и прерывания, чтобы сохранить как можно больше ресурсов CPU для всех задач. Если используется межсетевой экран, стоит попробовать отключить или настроить его, чтобы убедиться, что он не является основной причиной ограничения производительности. Учтите, что выгрузка межсетевого экрана с отслеживанием состояния обычно уменьшает и долю прерываний/softirq, и системное время, поскольку такие экраны работают на обоих путях — Rx и Tx. В Linux выгрузка модулей nf_conntrack и ip_conntrack покажет, можно ли получить выигрыш. Если да, значит модуль работает с настройками по умолчанию, и нужно выяснить, как настроить его для лучшей производительности. Обычно это сводится к значительному увеличению размера хеш-таблицы. Во FreeBSD команда «pfctl -d» одновременно отключает межсетевой экран «pf» и его механизм отслеживания состояния.

Если много времени уходит на прерывания/softirq, важно убедиться, что они не выполняются на том же CPU. Большинство систем старается закреплять задачи за тем CPU, на котором они получают сетевой трафик: при некоторых нагрузках это улучшает производительность. Но при сильно выраженной сетевой нагрузке эффект обратный, поскольку процесс haproxy вынужден конкурировать с соответствующей обработкой в ядре. Закрепление haproxy за одним ядром CPU, а прерываний — за другим ядром с общим кешем L3 обычно заметно повышает сетевую производительность. На практике объёмы работы haproxy и сетевого стека довольно близки, и каждый из них может почти полностью загрузить отдельный CPU. В Linux для этого используются taskset (для haproxy) или cpu-map (в конфигурации haproxy), а прерывания назначаются через /proc/irq. Многие сетевые интерфейсы поддерживают несколько очередей и прерываний. Обычно полезно распределить их по небольшому числу ядер CPU с общим кешем L3. Обязательно останавливайте irq_balance: при таких нагрузках он всегда действует наихудшим образом.

Для нагрузок, ограниченных CPU и связанных с большим объёмом трафика SSL или сжатия, может быть полезно выделить несколько процессов для определённых задач. Универсального правила здесь нет, поэтому придётся экспериментировать.

Для увеличения доступных ресурсов CPU можно запустить HAProxy в виде нескольких процессов с помощью директивы «nbproc» в секции global. Однако существуют ограничения:

- Проверки работоспособности выполняются каждым процессом, поэтому целевые серверы получают столько проверок, сколько процессов запущено.
- Значения maxconn и очереди относятся к отдельным процессам, поэтому необходимо правильно задать значение, чтобы не перегрузить серверы.
- В исходящих соединениях следует избегать использования диапазонов портов, чтобы не возникали конфликты.
- Таблицы stick-tables относятся к отдельным процессам и не являются общими для нескольких процессов.
- Каждая секция peers одновременно может работать только в одном процессе.
- Операции CLI одновременно действуют только на один процесс.

С учётом этих ограничений самый простой вариант часто состоит из первого уровня, работающего в нескольких процессах и выполняющего тяжёлую обработку, который передаёт трафик второму уровню, работающему в одном процессе. Этот механизм подходит для SSL и сжатия — двух наиболее затратных для CPU функций. Экземпляры легко объединить в цепочку через сокеты UNIX (они дешевле сокетов TCP и не расходуют порты) и протокол proxy, позволяющий передавать сведения о клиенте на следующий этап. При такой схеме обычно полезно закрепить все однопроцессные задачи за процессом номер 1, а дополнительные задачи — за следующими процессами: это упрощает генерацию похожих конфигураций для разных машин.

В Linux версии 3.9 и новее многопроцессный режим HAProxy гораздо эффективнее, если каждый процесс использует отдельный прослушивающий сокет на одной и той же паре IP:port. Тогда ядро равномерно распределяет нагрузку между процессами, вместо того чтобы пробуждать их все. Дополнительные сведения о параметре «process» в строках директивы «bind» приведены в руководстве по конфигурации.
