Глибока оптимізація Linux-серверів під production-навантаження

event 04.03.2026 10:00
| category DevOps | person iron_will | comment 0 | visibility 133 | |

Запустити сервер в 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

Застосовані зміни:

  1. Збільшено net.core.somaxconn до 65535 та tcp_max_syn_backlog до 65535.
  2. Увімкнено BBR замість CUBIC.
  3. Збільшено LimitNOFILE для systemd unit Nginx до 1048576.
  4. Вимкнено THP (Transparent Huge Pages) - Go runtime погано взаємодіє з THP через фрагментацію.
  5. Прив'язано 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-система кожної компанії унікальна, і справжня оптимізація завжди вимагає глибокого розуміння власного навантаження.

Related posts

VPN, SSH та базова безпека інфраструктури

Вступ Сучасна IT-інфраструктура функціонує в умовах постійного зовнішнього впливу: сканування портів, автоматизовані брутфорс-атаки, експлуатація вразливостей сервісів та цільові кібератаки. Навіть невеликі системи без належного захисту можуть стати...

category Security person iron_will event 26/05/2026

Автоматизація деплою через GitHub Actions

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

category DevOps person iron_will event 26/05/2026

Kubernetes для новачків: базові концепції

Вступ Сучасна розробка програмного забезпечення дедалі більше орієнтується на мікросервісну архітектуру, контейнеризацію та автоматизацію інфраструктури. У центрі цієї трансформації знаходиться Kubernetes - одна з найпопулярніших платформ оркестраці...

category Kubernetes person iron_will event 17/05/2026

Що таке RAID: рівні RAID, принцип роботи та навіщо він потрібен

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

category System administration person iron_will event 10/05/2026

Docker: як оптимізувати розмір контейнера з 50 ГБ до керованого рівня

Вступ Контейнери давно стали стандартом де-факто для доставки застосунків у production. Проте з ростом складності систем часто виникає нетривіальна проблема - неконтрольоване збільшення розміру Docker-образів. Сценарій, коли образ досягає 30–50 ГБ,...

category DevOps person iron_will event 06/05/2026

Kubernetes: сучасна платформа оркестрації контейнерів для production-середовищ

Вступ Kubernetes став де-факто стандартом для запуску контейнеризованих застосунків у production-середовищах. Якщо Docker вирішив проблему пакування застосунку разом із залежностями, то Kubernetes вирішує значно складніше завдання - як масштабувати,...

category DevOps person iron_will event 19/04/2026
cookie
This website uses cookies to improve your experience. Learn more