Глибока оптимізація Linux-серверів під production-навантаження
Запустити сервер в Linux - справа нескладна. Але налаштувати його так, щоб він витримував тисячі одночасних з'єднань, мінімізував латентність і не «падав» під піковим навантаженням - це вже інженерна задача, яка потребує системного підходу. Дистрибутиви Linux поставляються з універсальними налаштуваннями, придатними для широкого кола сценаріїв, але жодним чином не оптимальними для production-середовища.У цій статті розглянемо конкретні техніки глибокої оптимізації: від параметрів ядра та файлових дескрипторів до планувальника введення-виведення та мережевого стеку. Матеріал орієнтований на інженерів, які вже мають досвід адміністрування Linux і хочуть перейти від базового налаштування до справжнього performance tuning.
Усі наведені приклади перевірені на Ubuntu 22.04 LTS та RHEL 9 з ядром 5.15+. Частина параметрів є загальними для більшості дистрибутивів, частина - специфічна для певних версій ядра. Завжди тестуйте зміни в staging-середовищі перед застосуванням у production.
Важливо розуміти: оптимізація без профілювання - це стрілянина наосліп. Перш ніж змінювати будь-який параметр, зафіксуйте базові метрики: CPU utilization, memory pressure, I/O wait, network throughput і latency. Це дасть змогу об'єктивно оцінити ефект кожної зміни.
Оптимізація параметрів ядра через sysctl
Файл /etc/sysctl.conf (або файли в /etc/sysctl.d/) - перша точка входу для системного тюнінгу. Параметри ядра безпосередньо впливають на поведінку мережевого стеку, файлової системи та менеджменту пам'яті.
Мережевий стек
# /etc/sysctl.d/99-production.conf # Збільшення розміру буферів TCP net.core.rmem_max = 134217728 net.core.wmem_max = 134217728 net.ipv4.tcp_rmem = 4096 87380 134217728 net.ipv4.tcp_wmem = 4096 65536 134217728 # Черга для нових з'єднань (важливо при DDoS і пікових навантаженнях) net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 # Повторне використання TIME_WAIT сокетів net.ipv4.tcp_tw_reuse = 1 # Збільшення розміру черги мережевого пристрою net.core.netdev_max_backlog = 50000 # TCP keepalive — виявлення «мертвих» з'єднань net.ipv4.tcp_keepalive_time = 60 net.ipv4.tcp_keepalive_intvl = 10 net.ipv4.tcp_keepalive_probes = 6 # BBR — сучасний алгоритм контролю перевантаження (ядро 4.9+) net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr
Після редагування файлу застосуйте зміни без перезавантаження:
sysctl -p /etc/sysctl.d/99-production.confЧому BBR? Класичний алгоритм CUBIC реагує на втрати пакетів як сигнал перевантаження мережі. BBR (Bottleneck Bandwidth and Round-trip propagation time) натомість моделює пропускну здатність каналу і RTT, що дає суттєвий приріст throughput особливо у мережах з великою затримкою або незначними втратами.
Управління пам'яттю та swap
# Зменшення агресивності використання swap (0–100) # Для серверів з великою кількістю RAM рекомендується 10 vm.swappiness = 10 # Частота синхронізації брудних сторінок на диск vm.dirty_ratio = 15 vm.dirty_background_ratio = 5 # Прозорі величезні сторінки (Transparent Huge Pages) # Для баз даних (PostgreSQL, MySQL) часто краще вимкнути echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag
Для збереження налаштування THP після перезавантаження додайте в /etc/rc.local або створіть systemd unit-файл.
Ліміти файлових дескрипторів
Одна з найпоширеніших причин деградації high-load сервісів - вичерпання файлових дескрипторів. За замовчуванням Linux дозволяє відносно небагато відкритих файлів на процес і на систему загалом.
Системні ліміти
# Перегляд поточних лімітів cat /proc/sys/fs/file-max ulimit -n # Збільшення системного ліміту echo "fs.file-max = 2097152" >> /etc/sysctl.d/99-production.conf sysctl -p /etc/sysctl.d/99-production.conf
Ліміти для процесів через limits.conf
# /etc/security/limits.conf * soft nofile 1048576 * hard nofile 1048576 * soft nproc 65535 * hard nproc 65535 root soft nofile 1048576 root hard nofile 1048576
Ліміти для systemd-сервісів
Якщо ваш застосунок запускається через systemd, limits.conf може не застосовуватись — потрібно явно вказати ліміти в unit-файлі:
[Service] LimitNOFILE=1048576 LimitNPROC=65535 LimitMEMLOCK=infinity
Перевірте поточний стан дескрипторів для конкретного процесу:
# Перегляд лімітів для процесу за PID cat /proc/<PID>/limits # Кількість відкритих файлів процесу ls /proc/<PID>/fd | wc -l
Планувальник введення-виведення (I/O Scheduler)
Вибір планувальника I/O критично важливий для серверів із інтенсивним дисковим навантаженням. Linux підтримує кілька планувальників, і оптимальний вибір залежить від типу накопичувача.
Визначення та зміна планувальника
# Перегляд поточного планувальника для диска cat /sys/block/sda/queue/scheduler # Зміна планувальника без перезавантаження echo mq-deadline > /sys/block/sda/queue/scheduler # або echo none > /sys/block/nvme0n1/queue/scheduler
Рекомендації за типом накопичувача:
- NVMe SSD - none (No-op). NVMe має власну чергу команд (NVMe queues), і додатковий планувальник лише додає накладні витрати.
- SATA SSD - mq-deadline або kyber. Забезпечують мінімальну латентність при збереженні впорядкованості запитів.
- HDD - bfq (Budget Fair Queuing). Оптимізує пропускну здатність, мінімізуючи переміщення головки диска.
Параметри черги диска
# Глибина черги (queue depth) — для NVMe можна збільшити cat /sys/block/nvme0n1/queue/nr_requests echo 2048 > /sys/block/nvme0n1/queue/nr_requests # Read-ahead — розмір блоку попереднього зчитування (в секторах по 512 байт) # Для баз даних з випадковим доступом зменшіть до 0–256 # Для потокової обробки даних збільшіть до 2048–8192 blockdev --setra 256 /dev/sda
Для збереження налаштувань між перезавантаженнями використовуйте udev rules:
# /etc/udev/rules.d/60-io-scheduler.rules ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none" ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline" ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
Оптимізація файлової системи
Налаштування монтування ext4 та XFS
Параметри монтування безпосередньо впливають на продуктивність файлової системи та надійність даних.
# /etc/fstab — приклад для ext4 на production-сервері /dev/sda1 /data ext4 defaults,noatime,nodiratime,data=ordered,barrier=1 0 2 # Для XFS /dev/sdb1 /logs xfs defaults,noatime,nodiratime,logbufs=8,logbsize=256k 0 2
noatime - вимикає оновлення часу останнього доступу до файлу при кожному читанні. Це значно зменшує кількість записів на диск, особливо при інтенсивному читанні. Альтернатива - relatime, яка оновлює atime лише якщо він старший за mtime.
data=ordered - забезпечує консистентність даних без суттєвих втрат у продуктивності. data=writeback швидший, але менш безпечний при раптовому вимкненні.
Налаштування журналювання XFS
# Розміщення журналу на швидкому накопичувачі (окремий диск/розділ) mkfs.xfs -l logdev=/dev/nvme0n1p1,size=2000b /dev/sdb1 # Монтування з зовнішнім журналом mount -o logdev=/dev/nvme0n1p1 /dev/sdb1 /data
Оптимізація мережевих з'єднань: NUMA та CPU affinity
На серверах із NUMA-архітектурою (Non-Uniform Memory Access) неправильний розподіл навантаження між NUMA-вузлами може суттєво збільшити латентність доступу до пам'яті.
Аналіз NUMA-топології
# Перегляд NUMA-топології numactl --hardware # Запуск процесу на конкретному NUMA-вузлі numactl --cpunodebind=0 --membind=0 ./your-application # Прив'язка мережевих переривань до CPU-ядер того ж NUMA-вузла, що і NIC cat /proc/interrupts | grep eth0
IRQ Affinity для мережевих адаптерів
# Автоматична балансування переривань (встановити irqbalance) systemctl enable --now irqbalance # Для тонкого налаштування — ручне призначення # Знайти переривання мережевого адаптера grep eth0 /proc/interrupts # Призначити переривання конкретному CPU echo 2 > /proc/irq/<IRQ_NUMBER>/smp_affinity_list
Для багатоядерних систем із 10GbE+ рекомендується використовувати RSS (Receive Side Scaling) та RPS (Receive Packet Steering), щоб розподілити обробку мережевого трафіку між кількома ядрами:
# Увімкнення RPS для всіх CPU echo ffff > /sys/class/net/eth0/queues/rx-0/rps_cpus
Профілювання та моніторинг продуктивності
Жодна оптимізація не має сенсу без вимірювання. Ось мінімальний набір інструментів для production-аналізу.
Системні утиліти
# Аналіз навантаження в реальному часі top -H -p <PID> # з потоками htop # інтерактивний vmstat 1 10 # пам'ять, swap, I/O, CPU кожну секунду iostat -xz 1 # детальна статистика I/O ss -s # статистика сокетів sar -n DEV 1 10 # мережева статистика
Perf для профілювання ядра
# Запис профілю CPU на 30 секунд perf record -g -p <PID> -- sleep 30 # Аналіз результатів perf report --stdio # Flame graph (за допомогою brendangregg/FlameGraph) perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
BPF/eBPF інструменти (bpftrace, bcc)
# Латентність системних викликів bpftrace -e 'tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; } tracepoint:syscalls:sys_exit_read { @latency = hist(nsecs - @start[tid]); }' # Аналіз блокуючих I/O операцій biolatency -D # розподіл латентності I/O за дисками
Практичний кейс: оптимізація під high-concurrency API-сервер
Розглянемо реальний сценарій: REST API сервер на базі Nginx + backend на Go, який обслуговує 50 000 RPS із p99 латентністю понад 200 мс при норматив 50 мс.
Діагностика:
# Перевірка черги з'єднань ss -lnt | grep :443 netstat -s | grep -i "failed\|overflow\|drop" # Виявлено: SYN drops через переповнену чергу # net.ipv4.tcp_max_syn_backlog було 128
Застосовані зміни:
- Збільшено net.core.somaxconn до 65535 та tcp_max_syn_backlog до 65535.
- Увімкнено BBR замість CUBIC.
- Збільшено LimitNOFILE для systemd unit Nginx до 1048576.
- Вимкнено THP (Transparent Huge Pages) - Go runtime погано взаємодіє з THP через фрагментацію.
- Прив'язано NIC interrupts до CPU ядер 0–7, а Go runtime налаштовано на GOMAXPROCS=8 із pinning на ядра 8–15.
Результат: p99 латентність знизилась з 210 мс до 38 мс. Пропускна здатність зросла з 50 000 до 78 000 RPS без зміни hardware.
Автоматизація та Infrastructure as Code
Ручне застосування sysctl-параметрів на кожному сервері - неприйнятний підхід у масштабованій інфраструктурі. Усі зміни повинні бути закодовані та версіоновані.
Ansible playbook для системного тюнінгу
--- - name: Linux production tuning hosts: production become: true tasks: - name: Apply sysctl parameters ansible.posix.sysctl: name: "{{ item.key }}" value: "{{ item.value }}" state: present reload: true sysctl_file: /etc/sysctl.d/99-production.conf loop: - { key: "net.core.somaxconn", value: "65535" } - { key: "net.ipv4.tcp_tw_reuse", value: "1" } - { key: "vm.swappiness", value: "10" } - { key: "net.ipv4.tcp_congestion_control", value: "bbr" } - { key: "net.core.default_qdisc", value: "fq" } - name: Set system-wide file descriptor limits pam_limits: domain: "*" limit_type: "{{ item.type }}" limit_item: nofile value: "1048576" loop: - { type: soft } - { type: hard } - name: Disable Transparent Huge Pages shell: | echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag changed_when: true - name: Persist THP settings via systemd copy: dest: /etc/systemd/system/disable-thp.service content: | [Unit] Description=Disable Transparent Huge Pages After=network.target [Service] Type=oneshot ExecStart=/bin/sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled" ExecStart=/bin/sh -c "echo never > /sys/kernel/mm/transparent_hugepage/defrag" RemainAfterExit=yes [Install] WantedBy=multi-user.target notify: Enable disable-thp service handlers: - name: Enable disable-thp service systemd: name: disable-thp enabled: true state: started daemon_reload: true
Висновки
Глибока оптимізація Linux-сервера під production - це не одноразова дія, а безперервний процес вимірювання, аналізу та корегування. Ключові принципи, які варто засвоїти:
Вимірюйте перед змінами. Baseline-метрики - обов'язковий крок. Без них неможливо оцінити ефективність оптимізації або виявити регресію.
Розуміння, а не копіювання. Кожен параметр має фізичний сенс. Сліпе копіювання «magic sysctl values» з інтернету без розуміння контексту може погіршити ситуацію.
Тестуйте в ізоляції. Змінюйте один параметр за раз і фіксуйте результат. Пакетне застосування змін унеможливлює визначення причинно-наслідкових зв'язків.
Автоматизуйте та версіонуйте. Усі оптимізації повинні бути в системі контролю версій і застосовуватися через IaC-інструменти. «Сніжинкові сервери» з ручними налаштуваннями - ворог надійної інфраструктури.
Контекст вирішує все. Оптимальний планувальник I/O для PostgreSQL на NVMe та для Elasticsearch на HDD - різні. Алгоритм TCP-конгестії для CDN і для внутрішнього мікросервісу - теж різні. Завжди враховуйте специфіку навантаження свого застосунку.
Наведені в статті налаштування є відправною точкою, а не остаточним рецептом. Production-система кожної компанії унікальна, і справжня оптимізація завжди вимагає глибокого розуміння власного навантаження.