Мониторинг и оптимизация производительности файлового архива: метрики, алерты и тюнинг Nginx в 2026 году | AdminWiki

Мониторинг и оптимизация производительности файлового архива: метрики, алерты и тюнинг Nginx в 2026 году

24 сентября 2026 13 мин. чтения
Содержание статьи

Узкое место файлового архива вычисляется за один замер, если заранее снять baseline по пяти величинам: пропускная способность отдачи, время отклика (p50 и p95), загрузка диска (await и %util), сетевой throughput, число одновременных соединений. Baseline на здоровом архиве становится точкой отсчёта. Отклонение больше 10% после смены конфига или деплоя сразу показывает, какой ресурс просел.

Типовой архив в 2026 году устроен так: Nginx 1.26 или новее отдаёт статику, файлы лежат на локальном NVMe или на сетевом хранилище, тяжёлое сжатие выполняется на этапе сборки, TLS завершается на том же узле. Prometheus 2.x с node_exporter 1.x собирает метрики с хоста, nginx-prometheus-exporter отдаёт статистику соединений. ОС: Linux 5.x или 6.x с systemd.

Настройка производительности не должна открывать доступ к служебным файлам. Держите autoindex выключенным, блокируйте .env и .git, проверяйте конфиг через nginx -t перед каждым reload.

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

Архив тормозит по четырём причинам, и все они измеряются стандартными утилитами Linux. Ниже отправные точки, с которых начинаем диагностику.

Четыре ресурса, которые чаще всего становятся bottleneck

РесурсСимптомКоманда первичной проверкиПорог внимания
Дискрастёт await, утилизация близка к пределуiostat -x 1await больше 20 мс, %util больше 80%
Сетьthroughput упирается в канал, появляются ретрансмитыsar -n DEV 1, ss -sканал занят больше чем на 70%
CPUрастут %sys и %softirq, nginx греет gzipmpstat -P ALL 1, topбольше 70% на ядро
Память и дескрипторы502 и 503, в логе too many open filesulimit -n, cat /proc/sys/fs/file-nrбольше 80% от лимита

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

Что считать нормой для файлового архива

МетрикаОриентирКогда это проблема
Время отклика статикименьше 100 мс в LAN, меньше 300 мс через WANp95 выше 500 мс
Пропускная способность файлового серверане ниже 70% от полосы каналападение ниже 50% от baseline
await на чтение10-20 мс для SSD, 20-40 мс для HDDстабильно выше 30 мс
%util дискаменьше 70%больше 85% в течение 10 минут
Одновременные соединенияне выше worker_connections x worker_processes с запасом 20-30%CurrEstab подошёл к лимиту

Эти цифры задают отправную точку, а не абсолют. Норма зависит от железа, файловой системы и профиля трафика, поэтому baseline снимается на вашем стенде или на проде в спокойные часы. Пошаговая база по раздаче файлов, MIME-типам и заголовкам собрана в статье про раздачу статического контента через Nginx.

Ключевые метрики файлового архива и как их снимать

Пять групп метрик закрывают почти все инциденты: пропускная способность, время отклика, нагрузка на диск, нагрузка на сеть и число одновременных соединений. Снимать их можно как утилитами командной строки, так и через node_exporter.

Пропускная способность и время отклика

Скорость отдачи одного файла и задержку измеряет curl. Флаг -w выводит обе величины сразу, включая время до первого байта.

curl -o /dev/null -s -w 'ttfb=%{time_starttransfer} time_total=%{time_total} speed=%{speed_download}\n' \
  https://archive.example/file.tar.gz

Нагрузочный тест на реальном профиле трафика даёт wrk, а для простых сценариев хватает ab.

wrk -t4 -c100 -d30s https://archive.example/file.tar.gz
ab -n 1000 -c 100 https://archive.example/file.tar.gz

Разложить задержку по этапам помогает расширенный формат лога Nginx. $request_time показывает полное время обработки запроса, $upstream_response_time нужен, если часть файлов приходит от бэкенда.

log_format archive '$remote_addr $request $status '
                    'request_time=$request_time '
                    'upstream_time=$upstream_response_time '
                    'bytes=$bytes_sent';

В node_exporter пропускную способность дают счётчики node_network_receive_bytes_total и node_network_transmit_bytes_total, а также node_disk_read_bytes_total для чтения с диска. Как отличить медленный диск от медленной сети: если speed_download низкий, а iostat показывает await в норме и %util низкий, упор в канал или в клиента. Если await высокий и растёт очередь чтения, виноват диск.

Нагрузка на диск: IOPS, await, %util

Для архива важны чтение и задержка чтения, запись почти не участвует. Смотрим r/s, rkB/s, await и %util.

iostat -x 1
iotop -o
pidstat -d 1

Если %util держится выше 80% и await растёт, диск упирается в IOPS или в полосу. Показатель rkB/s на пределе паспортного значения подтверждает это. В node_exporter те же данные лежат в node_disk_read_bytes_total, node_disk_reads_completed_total и node_disk_read_time_seconds_total, а утилизация считается из node_disk_io_time_seconds_total.

Нагрузка на сеть и число одновременных соединений

Сетевую полосу и ошибки показывает sar, состояние сокетов берём из ss.

sar -n DEV 1
sar -n EDEV 1
ss -s
ss -lnt

Внутреннюю статистику Nginx отдаёт stub_status. Директиву включаем только для localhost или внутренней подсети.

location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}
curl -s http://127.0.0.1/nginx_status

Вывод содержит Active connections, Reading, Writing и Waiting. Рост Waiting означает, что соединения ждут ответа и держат keepalive-слоты: помогает увеличение worker_connections, open_file_cache или уменьшение времени ответа. node_exporter отдаёт node_netstat_Tcp_CurrEstab и node_sockstat_TCP_inuse, по ним удобно строить график занятости слотов.

Настройка мониторинга: Prometheus и node_exporter для файлового архива

Связка Prometheus и node_exporter даёт историю метрик и алерты раньше, чем приходят жалобы. Настройка занимает около получаса и не требует правок приложения.

Установка и настройка node_exporter

Распакуйте архив релиза node_exporter 1.x, положите бинарник в /usr/local/bin и создайте отдельного пользователя без прав на запись в архив.

useradd --no-create-home --shell /usr/sbin/nologin node_exporter
install -o node_exporter -g node_exporter -m 0755 node_exporter /usr/local/bin/node_exporter

Юнит systemd с нужными коллекторами выглядит так.

[Unit]
Description=Node Exporter
After=network.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter --collector.diskstats --collector.netdev --collector.sockstat --collector.filesystem

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now node_exporter
curl -s http://localhost:9100/metrics | grep -c '^node_disk'

Порт 9100 наружу не открываем: метрики содержат имена устройств, точки монтирования и топологию хоста. Доступ даём из внутренней сети или через reverse proxy с аутентификацией и TLS.

prometheus.yml: scrape_configs для архива

Подключаем хост архива и экспортер Nginx отдельными заданиями.

scrape_configs:
  - job_name: file_archive
    scrape_interval: 15s
    static_configs:
      - targets: ['archive-01:9100']

  - job_name: nginx
    scrape_interval: 15s
    static_configs:
      - targets: ['archive-01:9113']
promtool check config prometheus.yml

Интервал 15 секунд подходит для файлового архива: пики отдачи заметны и без перегрузки хранилища метрик. Переходите на 5 секунд, если разбираете короткие всплески или тестируете изменения и нужно видеть результат сразу. Базовые PromQL-запросы для панелей Grafana:

rate(node_network_transmit_bytes_total{instance="archive-01:9100",device="eth0"}[5m]) * 8
rate(node_disk_read_bytes_total{device="nvme0n1"}[5m])
rate(node_disk_read_time_seconds_total{device="nvme0n1"}[5m]) / rate(node_disk_reads_completed_total{device="nvme0n1"}[5m])
node_netstat_Tcp_CurrEstab

Первый запрос переводит байты в биты и показывает занятость канала. Третий считает среднее await на чтение, это самая полезная панель при разборе дисковых проблем.

Правила алертов: что и когда срабатывает

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

groups:
  - name: file_archive
    rules:
      - alert: HighDiskAwait
        expr: rate(node_disk_read_time_seconds_total[5m]) / rate(node_disk_reads_completed_total[5m]) > 0.03
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Задержка чтения на {{ $labels.device }} выше 30 мс"

      - alert: HighDiskUtil
        expr: rate(node_disk_io_time_seconds_total[10m]) > 0.85
        for: 10m
        labels:
          severity: warning

      - alert: LowThroughput
        expr: rate(node_network_transmit_bytes_total{device="eth0"}[10m]) < ...
        for: 10m
        labels:
          severity: warning

      - alert: HighConnections
        expr: node_netstat_Tcp_CurrEstab > 26000
        for: 5m
        labels:
          severity: warning

      - alert: HighLatency
        expr: histogram_quantile(0.95, sum(rate(nginx_http_request_duration_seconds_bucket[5m])) by (le)) > 0.5
        for: 5m
        labels:
          severity: critical

Порог HighConnections считайте от своих значений: для worker_processes auto на 8 ядрах и worker_connections 8192 запас заканчивается в районе 26 тысяч установленных соединений. Alertmanager маршрутизирует срабатывания в Telegram и Slack, группировка по alertname убирает шторм сообщений при массовой деградации.

Оптимизация: кэширование, сжатие, CDN и тюнинг Nginx

Четыре направления дают основной прирост: клиентский и прокси-кэш, предсжатые файлы, CDN для удалённой аудитории и настройка самого Nginx под статику. Меняем по одному пункту и после каждого снимаем замер, иначе эффект не отделить от шума.

Кэширование: Cache-Control, ETag, immutable

Файл с версией в имени не меняется никогда, поэтому ему выдаём длинный max-age и immutable. Такой ответ запрещает браузеру перепроверять файл даже при перезагрузке страницы.

location ~* \.(tar\.gz|zip|deb|rpm|iso)$ {
    add_header Cache-Control "public, max-age=2592000, immutable";
    access_log off;
}

location ~* \.html$ {
    add_header Cache-Control "public, max-age=3600, must-revalidate";
}
curl -I https://archive.example/file-2.4.1.tar.gz | grep -i cache-control

Директива expires сама формирует Cache-Control с одним лишь max-age, поэтому для immutable надёжнее задать заголовок явно. Файлам без версии в имени immutable не выдаём: клиенты перестанут видеть обновления. Подробная матрица заголовков, ETag, Last-Modified и версионирования разобрана в материале про кэширование статики: gzip, brotli, Cache-Control и ETag.

Сжатие файлов на сервере: gzip, brotli, precompressed

Сжатие файлов на сервере имеет смысл только для текста: HTML-гайды, JSON-метаданные, индексные страницы, CSS-описания. Для tar.gz, zip и образов повторное сжатие бесполезно, а CPU съедает.

gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

gzip_static on;
brotli_static on;

Директивы brotli требует модуль ngx_brotli; если он собран, добавляем brotli on с уровнем 5-6. Уровни выше 6 заметно греют CPU и дают выигрыш в доли процента. Предсжатые версии файлов через gzip_static и brotli_static снимают нагрузку полностью: Nginx отдаёт готовый .br, не тратя ядра на компрессию.

curl -H 'Accept-Encoding: br' -I https://archive.example/guide.html | grep -i content-encoding
mpstat -P ALL 1

Замер mpstat до и после перехода на предсжатые файлы показывает, сколько ядер освободилось. На текстовой нагрузке разница обычно кратная, на архивах её не будет вовсе.

CDN для файлового архива

CDN оправдан при географически распределённой аудитории, крупных файлах и пиковых нагрузках: origin перестаёт быть единственной точкой отдачи. Схема простая: origin pull, длинный TTL для версионированных архивов, сброс кэша по смене версии в имени.

curl -I https://archive.example/file-2.4.1.tar.gz | grep -iE 'x-cache|age|cf-cache-status'

Заголовки X-Cache, Age и CF-Cache-Status показывают попадание в кэш. Приватные файлы с токенами доступа через публичный CDN не отдаём, иначе ссылка утечёт через чужой edge-узел. Таймауты и работу с крупными файлами удобно сверить со статьёй про тонкую настройку Nginx для больших файлов.

Тюнинг Nginx под статику

Параметры ниже рассчитаны на отдачу статики с локального NVMe.

worker_processes auto;
worker_rlimit_nofile 65535;

worker_connections 8192;
multi_accept on;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;

open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

aio threads;
directio 8m;
output_buffers 4 256k;

worker_rlimit_nofile поднимает лимит дескрипторов на процесс, worker_connections задаёт потолок соединений. sendfile и tcp_nopush отдают файл без копирования в пользовательское пространство и собирают заголовки в один пакет, tcp_nodelay убирает задержку на мелких ответах. open_file_cache держит дескрипторы и метаданные горячих файлов. Пара aio threads и directio 8m отправляет файлы крупнее 8 МБ через асинхронный ввод-вывод мимо страничного кэша, чтобы один большой архив не вытеснял метаданные остальных.

nginx -t
systemctl reload nginx
wrk -t4 -c200 -d30s https://archive.example/file.tar.gz

aio и directio тестируйте на своём железе и файловой системе: на части конфигураций эти параметры дают регресс. Развёрнутый набор параметров ядра и Nginx для 10k-50k RPS есть в гайде по тюнингу Nginx для высоких нагрузок.

Типовые узкие места: примеры с диагностикой и исправлением

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

СимптомПричинаДействие
await выше 30 мс, %util выше 90%диск на пределе IOPSNVMe, open_file_cache, readahead, RAID
Throughput на пределе канала, ретрансмитыне хватает полосыCDN, сжатие текста, limit_rate
Высокий %sys, nginx в топе по CPUсжатие на летуgzip_static, brotli_static, уровень 5
502 и 503, ошибки в error.logлимиты соединений и дескрипторовworker_connections, worker_rlimit_nofile, sysctl

Кейс 1: диск, высокий await и %util

Симптом: iostat показывает await больше 30 мс, %util выше 90%, rkB/s упирается в паспортную полосу накопителя.

iostat -x 1
iotop -o
pidstat -d 1
lsblk -o NAME,ROTA,SIZE

Если чтение на пределе, вариантов два: поднять пропускную способность носителя или сократить число обращений к нему. Перенос архива на NVMe и включение open_file_cache закрывает большую часть случаев. Для последовательного чтения крупных файлов помогает увеличение readahead (значение в 512-байтных секторах, то есть 8192 - это 4 МБ).

blockdev --setra 8192 /dev/nvme0n1

Проверка результата: повторный iostat под нагрузкой и прогон wrk, сравнение rkB/s и await с baseline.

Кейс 2: сеть, канал забит и есть ретрансмиты

Симптом: sar показывает throughput на пределе канала, ss -s и ss -ti обнаруживают повторы передач и растущие очереди.

sar -n DEV 1
sar -n EDEV 1
ss -ti

Первое действие - снизить объём передаваемых данных: включить сжатие текстовых форматов и предсжатые файлы. Второе - разгрузить origin через CDN. Третье - ограничить скорость для некритичных клиентов, чтобы один качающий не съедал полосу у остальных.

location /downloads/ {
    limit_rate_after 10m;
    limit_rate 5m;
}

Проверка: sar -n DEV 1 после изменения и curl -w speed_download с внешней точки. Ограничение действует на соединение, поэтому его подбирают по реальному распределению клиентов.

Кейс 3: CPU просел на сжатии

Симптом: mpstat показывает высокий %sys и %softirq, top выводит nginx поверх остальных процессов, время ответа растёт при неизменной нагрузке.

mpstat -P ALL 1
perf top

Исправление: перейти на предсжатые файлы (gzip_static, brotli_static), снизить brotli до уровня 5, отключить сжатие для уже сжатых форматов. Проверка: mpstat до и после, а также curl с заголовком Accept-Encoding: br, чтобы убедиться, что brotli действительно отдаётся.

Кейс 4: лимиты соединений и файловых дескрипторов

Симптом: в nginx error.log появляются записи worker_connections are not enough и too many open files, клиенты получают 502 и 503.

ss -s
cat /proc/sys/fs/file-nr
ulimit -n
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog

Поднимаем лимиты на уровне Nginx и ядра. Lимит worker_connections считает и клиентские, и проксируемые соединения, поэтому запас в 20-30% обязателен.

fs.file-max = 200000
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 10240 65000
net.ipv4.tcp_fin_timeout = 15
sysctl -p
nginx -t
systemctl reload nginx
ss -lnt

Проверка: ab -n 5000 -c 500 без ошибок соединения и чистый error.log. Если 502 остаются, смотрите в сторону бэкенда, а не Nginx.

Безопасность при оптимизации: что нельзя ломать

Ускорение не оправдывает открытый доступ к служебным файлам. autoindex держите выключенным: листинг каталога раскрывает структуру архива целиком.

location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

location ~* \.(env|git|htaccess|sql|bak)$ {
    deny all;
}

Права на файлы 644, на каталоги 755, владелец - пользователь без прав на запись в каталоги с контентом. Immutable-кэш выдаём только файлам с хешем или версией в имени. CDN не используем для приватных материалов. Для HTML-гайдов добавляем базовые заголовки безопасности.

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:" always;
add_header X-Content-Type-Options "nosniff" always;
curl -I https://archive.example/.env

Ответ должен быть 403 или 404, а не содержимое файла. Этот тест стоит прогонять после каждой правки server-блока.

Чек-лист регулярной проверки производительности

Регулярные проверки удерживают архив быстрым без ручного контроля. Ниже распределение по периодичности с командами и ожидаемым результатом.

Ежедневные, еженедельные и ежемесячные проверки

ПериодичностьЧто проверяемКомандаОжидаемый результат
Ежедневногорящие алерты и ошибки в логеtail -n 200 error.log | grep -E '502|503|too many'пустой вывод
Ежедневносостояние соединенийcurl -s http://127.0.0.1/nginx_statusWaiting без устойчивого роста
Еженедельнодиск и сеть против baselineiostat -x 1, sar -n DEV 1, ss -sотклонение меньше 10%
Ежемесячнонагрузочный тест и валидация конфигаwrk -t4 -c200 -d60s, nginx -tp95 в пределах SLO
ЕжеквартальноSLO, кэш-политики, CDNпересмотр правил и TTLактуальные пороги и TTL

Как зафиксировать baseline и отслеживать регресс

Baseline снимается на здоровом архиве и хранится рядом с конфигами. Минимальный набор: пропускная способность отдачи, p50 и p95 времени отклика, средний await на чтение, %util, CurrEstab. Замеры удобно писать в CSV и складывать в тот же Grafana-дашборд.

wrk -t4 -c200 -d60s --latency https://archive.example/file.tar.gz | tee baseline-$(date +%F).txt
curl -o /dev/null -s -w '%{time_starttransfer},%{time_total},%{speed_download}\n' https://archive.example/file.tar.gz >> latency.csv

После каждого изменения прогоняем тот же набор и сравниваем. Регресс больше 10% - повод откатить правку до разбора причин. Baseline устаревает при смене железа, версии Nginx или профиля трафика, тогда его снимают заново.

Перед любой правкой сохраняйте конфиг: cp nginx.conf nginx.conf.bak-$(date +%F), затем nginx -t и только после успешной проверки systemctl reload nginx. Такой порядок оставляет рабочий архив на связи даже при ошибке в директиве, а мониторинг покажет, принесла ли правка ожидаемый прирост.

Поделиться:
Сохранить гайд? В закладки браузера