Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

IThub

Administrators
  • Joined

  • Last visited

  1. Девопс.mp3
  2. Катим в прод.mp3
  3. Удали ВПН.mp3
  4. Вчера Работало(1).mp3 Вчера Работало.mp3
  5. Деплой Ночью.mp3
  6. Код Баг Фикс.mp3
  7. Нельзя В Прод.mp3
  8. PHP Живой.mp3
  9. Айти воины.mp3
  10. Удоли ВПН.mp3
  11. Введение: Linux в продакшене — это не просто установить и забытьПоставить Ubuntu, запустить приложение и назвать это "продакшеном" — рецепт будущей катастрофы. Настоящий production Linux — это слоёный пирог из правильного выбора дистрибутива, hardening-а, тюнинга ядра, настроенного мониторинга и чётких процедур обслуживания. Статья написана для тех, кто уже умеет работать с Linux и хочет понять почему нужно делать именно так, а не просто скопировать команды. Часть 1: Выбор дистрибутиваКритерии для production-выбораПрежде чем смотреть на дистрибутивы, определитесь с приоритетами: Стабильность vs свежесть: старые проверенные пакеты или последние версии? Коммерческая поддержка: нужен SLA от вендора или достаточно community? Цикл жизни: как долго дистрибутив будет получать security-патчи? Экосистема: есть ли готовые пакеты для вашего стека? Команда: что знает ваша команда? Переучивание дорого стоит. Ubuntu LTS ServerЦикл поддержки: 5 лет standard, 10 лет ESM (Extended Security Maintenance). Текущий LTS: 24.04 (Noble Numbat), следующий — 26.04. Ubuntu — безусловный лидер по распространённости в cloud-среде. AWS, GCP, Azure — везде Ubuntu является первым выбором по умолчанию. Огромная документация, большинство DevOps-инструментов тестируются на Ubuntu в первую очередь. Подходит для: Startups и компании без выделенного Linux-администратора Kubernetes workers и cloud-native окружения Быстрое развёртывание новых стеков Осторожно: Canonical меняет политики (snap-пакеты вместо deb без предупреждения) Обновления между LTS требуют тщательного тестирования ESM платный для > 5 машин в организации # Проверка версии и EOL даты lsb_release -a ubuntu-advantage status # Статус ESM подписки # Отключить snap если не нужен (спорно, но часто делают) sudo systemctl disable snapd --now sudo apt purge snapd Debian StableЦикл поддержки: ~3 года основная поддержка + 2 года LTS. Текущий: Debian 12 "Bookworm" (до 2028). Debian — "бабушка" большинства дистрибутивов. Её репутация: консервативная, предсказуемая, надёжная. Пакеты в Stable могут быть на 1–2 года старше апстрима, но зато они досконально протестированы. Никаких сюрпризов в 3 часа ночи. Подходит для: Серверы с длинным жизненным циклом (БД, хранилища) Инфраструктура, где стабильность важнее новых фич Встраиваемые и промышленные серверы Осторожно: Старые пакеты могут не поддерживать новые возможности (TLS 1.3 и т.п. уже везде есть, но крайние версии — нет) community-поддержка без коммерческого SLA # Debian: правильные sources.list для продакшена # Только stable, никакого testing/sid! cat /etc/apt/sources.list # deb http://deb.debian.org/debian bookworm main contrib non-free-firmware # deb http://security.debian.org/debian-security bookworm-security main # deb http://deb.debian.org/debian bookworm-updates main # Backports только для конкретных пакетов, не массово # deb http://deb.debian.org/debian bookworm-backports main RHEL / Rocky Linux / AlmaLinuxRHEL (Red Hat Enterprise Linux) — корпоративный стандарт в Enterprise-сегменте, особенно там где есть compliance требования (PCI DSS, HIPAA, FedRAMP). Платная подписка, но включает полный enterprise support от Red Hat. Rocky Linux и AlmaLinux — бинарно-совместимые клоны RHEL, бесплатные. После смерти CentOS 8 (2021) это лучшая замена для тех, кто хочет RHEL-совместимость без платы. Цикл поддержки: RHEL 9 → до 2032 (10 лет!). Rocky/Alma — аналогично. Подходит для: Enterprise-окружения с compliance требованиями Компании с контрактами Red Hat (получают support + Satellite) Там где нужен SELinux из коробки в полном объёме Серверы баз данных Oracle (Oracle Linux — тоже RHEL-клон) # Rocky Linux: подписка на обновления безопасности (бесплатно) sudo dnf install epel-release sudo dnf update # Проверка SELinux статуса getenforce # Enforcing / Permissive / Disabled sestatus # RHEL-специфика: subscription-manager sudo subscription-manager status sudo subscription-manager repos --list-enabled Alpine LinuxЦикл: rolling release (stable branches с ~2 годами поддержки). Alpine — минималистичный дистрибутив: musl libc вместо glibc, busybox вместо GNU coreutils, базовый образ 5 МБ. Создан для контейнеров и встраиваемых систем. Подходит для: Docker-образы (но есть нюансы с musl!) Edge-узлы и шлюзы с ограниченными ресурсами Безопасные "голые" серверы с минимальной поверхностью атаки Осторожно: musl libc != glibc: некоторые Go/C программы ведут себя иначе Не для тех, кто не знает что делает: меньше инструментов отладки Не для продакшена с незнакомым стеком Итоговая таблица выбора Ubuntu LTS Debian Stable Rocky/Alma Alpine Cloud-native ★★★★★ ★★★★ ★★★ ★★★★ Enterprise ★★★ ★★★ ★★★★★ ★★ Стабильность пакетов ★★★★ ★★★★★ ★★★★★ ★★★ Свежесть пакетов ★★★★ ★★★ ★★★ ★★★★★ Безопасность (OOB) ★★★★ ★★★★ ★★★★★ ★★★★ Поддержка сообщества ★★★★★ ★★★★★ ★★★★ ★★★ Простота обслуживания ★★★★★ ★★★★ ★★★ ★★ Рекомендация: для большинства команд без специфических требований — Ubuntu 24.04 LTS или Debian 12. Для enterprise с compliance — Rocky Linux 9. Для контейнеров — Alpine или Distroless. Часть 2: Первичная настройка надёжностиБазовое обновление и автоматические security-патчи# === Ubuntu / Debian === sudo apt update && sudo apt upgrade -y # Автоматические security-обновления (unattended-upgrades) sudo apt install unattended-upgrades apt-listchanges sudo dpkg-reconfigure unattended-upgrades # Конфиг /etc/apt/apt.conf.d/50unattended-upgrades # Оставляем только security, НЕ автоустанавливаем всё подряд sudo tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null <<'EOF' Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; "${distro_id}ESMApps:${distro_codename}-apps-security"; "${distro_id}ESM:${distro_codename}-infra-security"; }; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::MinimalSteps "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; // НЕ перезагружаем автоматически! Unattended-Upgrade::Mail "ops@yourcompany.com"; EOF # === Rocky / AlmaLinux === sudo dnf install dnf-automatic sudo systemctl enable --now dnf-automatic-install.timer # /etc/dnf/automatic.conf: apply_updates = yes, upgrade_type = security Правильная настройка NTPВремя — фундамент для логов, TLS-сертификатов, Kerberos, баз данных. Неправильное время = необъяснимые баги. # Современный стандарт: systemd-timesyncd (простой) или chrony (сложные сети) # chronyd рекомендован для серверов — точнее и поддерживает аппаратные часы sudo apt install chrony # Ubuntu/Debian # или: sudo dnf install chrony # RHEL-based # /etc/chrony.conf sudo tee /etc/chrony.conf > /dev/null <<'EOF' # Используем несколько источников из разных пулов pool 0.ru.pool.ntp.org iburst pool 1.ru.pool.ntp.org iburst pool 2.europe.pool.ntp.org iburst pool 3.pool.ntp.org iburst # Внутренний NTP-сервер (если есть) — даём максимальный приоритет # server 192.168.1.1 prefer iburst # Разрешаем step при большом расхождении (только при старте) makestep 1.0 3 # Дрейф файла driftfile /var/lib/chrony/drift # Логирование logdir /var/log/chrony # Синхронизация RTC (hardware clock) rtcsync EOF sudo systemctl restart chrony chronyc tracking # Статус синхронизации chronyc sources -v # Источники времени systemd: сделать сервисы по-настоящему надёжнымиБольшинство используют systemd только для start/stop/enable. Но в нём есть мощные механизмы для production-надёжности. # /etc/systemd/system/myapp.service — production-grade unit [Unit] Description=My Production Application Documentation=https://docs.mycompany.com/myapp # Зависимости — стартуем ПОСЛЕ того как сеть полностью готова After=network-online.target postgresql.service redis.service Wants=network-online.target Requires=postgresql.service # Условие запуска — только если файл конфига существует ConditionPathExists=/etc/myapp/config.yaml [Service] Type=notify # Приложение сигнализирует о готовности через sd_notify NotifyAccess=main User=myapp Group=myapp WorkingDirectory=/opt/myapp # Переменные окружения из защищённого файла (права 600, владелец root) EnvironmentFile=/etc/myapp/environment ExecStartPre=/opt/myapp/bin/validate-config # Валидация конфига перед стартом ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml ExecReload=/bin/kill -HUP $MAINPID # Graceful reload по сигналу HUP # ── Restart политика ────────────────────────────────────────────────────────── Restart=on-failure # Перезапускаем только при сбое (не при systemctl stop) RestartSec=5s # Ждём 5 секунд перед перезапуском StartLimitIntervalSec=120s # Окно для подсчёта неудачных стартов StartLimitBurst=5 # Максимум 5 перезапусков за 120 секунд # После исчерпания — юнит в Failed. Оповестить алертинг! # ── Watchdog ───────────────────────────────────────────────────────────────── # Приложение ДОЛЖНО вызывать sd_notify("WATCHDOG=1") каждые WatchdogSec/2 WatchdogSec=30s # ── Таймауты ───────────────────────────────────────────────────────────────── TimeoutStartSec=60s # Максимальное время на инициализацию TimeoutStopSec=30s # Максимальное время на graceful shutdown KillMode=mixed # Сначала SIGTERM главному процессу, потом SIGKILL всей группе KillSignal=SIGTERM # ── Лимиты ресурсов ────────────────────────────────────────────────────────── LimitNOFILE=65536 # Максимум открытых файлов LimitNPROC=4096 # Максимум процессов/потоков LimitMEMLOCK=infinity # Для приложений с mlock (JVM, некоторые БД) # ── Безопасность (sandboxing) ───────────────────────────────────────────────── NoNewPrivileges=true # Запрет повышения привилегий PrivateTmp=true # Изолированный /tmp PrivateDevices=true # Запрет доступа к /dev (кроме basic) ProtectSystem=strict # /usr, /boot, /etc только на чтение ProtectHome=true # Нет доступа к /home, /root ReadWritePaths=/var/lib/myapp /var/log/myapp # Разрешаем запись только сюда CapabilityBoundingSet=CAP_NET_BIND_SERVICE # Только если нужен порт < 1024 # ── Логирование ────────────────────────────────────────────────────────────── StandardOutput=journal StandardError=journal SyslogIdentifier=myapp [Install] WantedBy=multi-user.target # Анализ конфигурации unit (покажет ошибки и предупреждения) systemd-analyze verify /etc/systemd/system/myapp.service # Просмотр "дерева" зависимостей systemd-analyze critical-chain myapp.service # Лимиты запущенного сервиса cat /proc/$(systemctl show -p MainPID --value myapp)/limits # История перезапусков journalctl -u myapp --since "7 days ago" | grep -E "Started|Stopped|Failed" Настройка ulimits и системных лимитов# /etc/security/limits.conf — лимиты для пользователей # Для высоконагруженного приложения (например, веб-сервер, БД) myapp soft nofile 65536 myapp hard nofile 65536 myapp soft nproc 65536 myapp hard nproc 65536 # Для Elasticsearch / Kafka / других Java-приложений elasticsearch soft memlock unlimited elasticsearch hard memlock unlimited elasticsearch soft nofile 65536 elasticsearch hard nofile 65536 # /etc/systemd/system.conf — глобальные лимиты для systemd-процессов # DefaultLimitNOFILE=65536 # DefaultLimitNPROC=65536 # Проверка текущих лимитов процесса cat /proc/$(pgrep myapp | head -1)/limits Часть 3: Тюнинг ядра для продакшенаПараметры sysctl: сетьЭто самое важное для highload-серверов. По умолчанию Linux оптимизирован для десктопа, не для сервера. # /etc/sysctl.d/99-production.conf sudo tee /etc/sysctl.d/99-production.conf > /dev/null <<'EOF' # ════════════════════════════════════════════════════════════════════ # СЕТЬ — TCP/IP стек # ════════════════════════════════════════════════════════════════════ # Буферы сокетов (receive/send) # Для highload: 128 МБ (default ~212 КБ — катастрофически мало!) net.core.rmem_max = 134217728 # 128 МБ net.core.wmem_max = 134217728 net.core.rmem_default = 31457280 # 30 МБ net.core.wmem_default = 31457280 # TCP-специфичные буферы (min, default, max) net.ipv4.tcp_rmem = 4096 87380 134217728 net.ipv4.tcp_wmem = 4096 65536 134217728 # Очередь подключений (backlog) # Должна совпадать с параметром backlog в listen() вашего приложения net.core.somaxconn = 65535 net.core.netdev_max_backlog = 65535 # SYN-очередь (защита от SYN flood + highload accept) net.ipv4.tcp_max_syn_backlog = 65535 # TIME_WAIT: ускоряем переиспользование сокетов net.ipv4.tcp_tw_reuse = 1 # Переиспользовать TIME_WAIT сокеты (безопасно) net.ipv4.tcp_fin_timeout = 15 # Сократить FIN_WAIT таймаут (default 60 сек) # Диапазон эфемерных портов (для outbound соединений) net.ipv4.ip_local_port_range = 1024 65535 # Keepalive — обнаружение мёртвых соединений net.ipv4.tcp_keepalive_time = 120 # Начинаем проверку после 120 сек простоя net.ipv4.tcp_keepalive_intvl = 10 # Интервал между пробами net.ipv4.tcp_keepalive_probes = 6 # Количество проб # Алгоритм управления перегрузкой (congestion control) # BBR — современный алгоритм Google, значительно лучше для WAN и highload net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr # Быстрый открытый TCP (TFO) — уменьшает latency на 1 RTT # (убедитесь что клиенты поддерживают) net.ipv4.tcp_fastopen = 3 # 1=клиент, 2=сервер, 3=оба # Защита от мусорных RST пакетов net.ipv4.tcp_rfc1337 = 1 # ════════════════════════════════════════════════════════════════════ # ПАМЯТЬ # ════════════════════════════════════════════════════════════════════ # vm.swappiness: насколько агрессивно использовать swap # 0 = swap только при крайней необходимости (для БД и latency-критичных) # 10 = рекомендуется для production-серверов # 60 = default (слишком агрессивный для сервера) vm.swappiness = 10 # Dirty pages: когда сбрасывать на диск # Уменьшаем для предсказуемой latency (не даём накопиться большому flush) vm.dirty_ratio = 10 # Начинаем sync при 10% RAM dirty (default 20%) vm.dirty_background_ratio = 3 # Фоновый flush начинается при 3% (default 10%) vm.dirty_writeback_centisecs = 500 # Интервал фонового flush (5 сек, default 5 сек) vm.dirty_expire_centisecs = 3000 # Страницы считаются "старыми" через 30 сек # Overcommit (для Java/JVM и других приложений с большими heap) # 0 = эвристика ядра (default) # 1 = всегда overcommit (опасно, но нужно для некоторых БД) # 2 = строгий лимит (CommitLimit = swap + vm.overcommit_ratio% от RAM) vm.overcommit_memory = 0 # OOM Killer: штраф для критических процессов # Устанавливается через /proc или systemd OOMPolicy # (ниже — пример для PostgreSQL) # ════════════════════════════════════════════════════════════════════ # ФАЙЛОВАЯ СИСТЕМА # ════════════════════════════════════════════════════════════════════ # Максимум открытых файлов в системе (не путать с per-process ulimit) fs.file-max = 2097152 # Inotify: для приложений, следящих за файлами (Docker, K8s, IDE) fs.inotify.max_user_watches = 524288 fs.inotify.max_user_instances = 512 # AIO: асинхронный ввод-вывод (PostgreSQL, некоторые БД) fs.aio-max-nr = 1048576 # ════════════════════════════════════════════════════════════════════ # БЕЗОПАСНОСТЬ # ════════════════════════════════════════════════════════════════════ # Запрет разыменования символических ссылок в /tmp (защита от атак) fs.protected_symlinks = 1 fs.protected_hardlinks = 1 # Запрет записи в память выполняемых файлов fs.protected_regular = 2 fs.protected_fifos = 2 # ASLR (Address Space Layout Randomization) kernel.randomize_va_space = 2 # Полная рандомизация # Запрет SysRq (кроме sync/reboot — оставляем для экстренного случая) kernel.sysrq = 16 # 16 = только sync # Защита от Spectre/Meltdown через ptrace kernel.yama.ptrace_scope = 1 # Core dumps только в определённую директорию kernel.core_pattern = /var/crash/core.%e.%p.%t kernel.core_uses_pid = 1 EOF # Применить без перезагрузки sudo sysctl --system # Проверить конкретный параметр sysctl net.ipv4.tcp_congestion_control Hugepages: критично для баз данныхHugePages уменьшают нагрузку на TLB (Translation Lookaside Buffer) при работе с большими объёмами памяти. PostgreSQL, Oracle, MySQL InnoDB, Elasticsearch, Redis — все выигрывают от HugePages. # Проверить текущее состояние cat /proc/meminfo | grep -i huge # HugePages_Total: 0 ← не настроены # Hugepagesize: 2048 kB ← 2 МБ каждая страница # Рассчитать сколько нужно: # Для PostgreSQL: shared_buffers + прочая SHM / 2 МБ # Например: 16 ГБ shared_buffers → 16384 МБ / 2 МБ = 8192 страниц # /etc/sysctl.d/99-hugepages.conf echo "vm.nr_hugepages = 8192" | sudo tee /etc/sysctl.d/99-hugepages.conf # Или прозрачные HugePages (THP) — автоматически, но с latency spikes! # Для БД (особенно Redis) — ОТКЛЮЧИТЬ THP: echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag # Сделать постоянным через rc.local или systemd service: sudo tee /etc/systemd/system/disable-thp.service > /dev/null <<'EOF' [Unit] Description=Disable Transparent Huge Pages DefaultDependencies=false After=sysinit.target local-fs.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 EOF sudo systemctl enable --now disable-thp Планировщик I/O: под задачу# Посмотреть текущий планировщик для диска cat /sys/block/sda/queue/scheduler # [mq-deadline] kyber bfq none # Выбор планировщика: # none (noop) — для NVMe SSD и виртуальных дисков (SSD сами управляют очередью) # mq-deadline — универсальный, хорош для смешанных нагрузок # bfq — для десктопов и интерактивных задач (не для продакшена) # kyber — для NVMe с очень низкой latency # Для NVMe SSD — none: echo none | sudo tee /sys/block/nvme0n1/queue/scheduler # Для SATA SSD — mq-deadline: echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler # Через udev (постоянно): sudo tee /etc/udev/rules.d/60-ioschedulers.rules > /dev/null <<'EOF' # NVMe ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none" # SATA/SAS SSD (по ROTATIONAL=0) ACTION=="add|change", KERNEL=="sd[a-z]|xvd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline" # HDD (по ROTATIONAL=1) ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq" EOF # Дополнительные параметры очереди диска: # Глубина очереди запросов (для NVMe можно больше) cat /sys/block/nvme0n1/queue/nr_requests # default 64 echo 1024 | sudo tee /sys/block/nvme0n1/queue/nr_requests # Read-ahead (предварительное чтение) # Для последовательного чтения (логи, backup): увеличить # Для random I/O (OLTP БД): уменьшить blockdev --setra 256 /dev/sda # 128 КБ (256 × 512 байт) OOM Killer: кого убивать в последнюю очередь# OOM killer убивает процессы когда RAM исчерпана. # Значение oom_score_adj: от -1000 (никогда не убивать) до +1000 (убить первым) # Защитить критические процессы (PostgreSQL, основное приложение): echo -1000 | sudo tee /proc/$(pgrep postgres | head -1)/oom_score_adj # В systemd unit — правильный способ: # [Service] # OOMScoreAdjust=-900 # -1000 зарезервирован для ядра # Для throaway-процессов (worker, task runner) — пусть убивают первыми: # OOMScoreAdjust=500 # Включить расширенное логирование OOM kill: echo 1 | sudo tee /proc/sys/vm/oom_dump_tasks # Посмотреть oom_score всех процессов for p in /proc/[0-9]*/; do pid=$(basename $p) comm=$(cat $p/comm 2>/dev/null) score=$(cat $p/oom_score 2>/dev/null) adj=$(cat $p/oom_score_adj 2>/dev/null) [ -n "$score" ] && echo "$score $adj $pid $comm" done | sort -rn | head -20 Часть 4: Файловые системы и хранилищеВыбор файловой системыext4 — проверенный стандарт, но без поддержки checksums данных (только метаданных). XFS — отличная производительность на больших файлах и параллельном I/O. Default в RHEL. Нельзя уменьшить (только увеличить). Btrfs — copy-on-write, встроенные checksums, снапшоты. Хорош для систем где нужны снапшоты без LVM. В продакшене требует RAID-конфигурации для надёжности. ZFS — самая надёжная ФС с end-to-end checksums, встроенным RAID-Z, дедупликацией. Требует отдельной установки на Linux. Рекомендуется для хранилищ данных. # Монтирование ext4 с оптимальными опциями для продакшена: # /etc/fstab: # /dev/sdb1 /data ext4 defaults,noatime,lazytime,errors=remount-ro 0 2 # noatime — не обновлять atime при каждом чтении (значительно снижает I/O) # lazytime — обновлять временны́е метки только при flush (компромисс) # errors=remount-ro — при ошибке ФС переходит в read-only вместо паники # XFS с journalling только на metadata (для производительности): # /dev/sdb1 /data xfs defaults,noatime,logbsize=256k 0 2 # Проверка здоровья файловой системы: sudo tune2fs -l /dev/sdb1 | grep -E "Last checked|Mount count|Max mount count" sudo xfs_info /data sudo xfs_repair -n /data # Проверка XFS без исправления LVM: гибкое управление томамиLVM (Logical Volume Manager) — обязателен для production-серверов. Позволяет расширять разделы без downtime, создавать снапшоты для бэкапов. # Создание LVM структуры: # 1. Physical Volumes (PV) sudo pvcreate /dev/sdb /dev/sdc # 2. Volume Group (VG) sudo vgcreate data_vg /dev/sdb /dev/sdc sudo vgs # Проверка # 3. Logical Volumes (LV) sudo lvcreate -L 100G -n postgres_lv data_vg # 100 ГБ для PostgreSQL sudo lvcreate -L 50G -n logs_lv data_vg # 50 ГБ для логов sudo lvcreate -l 100%FREE -n backup_lv data_vg # Остаток под бэкапы # Форматирование sudo mkfs.xfs /dev/data_vg/postgres_lv sudo mkfs.ext4 /dev/data_vg/logs_lv # Расширение без downtime (XFS умеет расти онлайн): sudo lvextend -L +50G /dev/data_vg/postgres_lv # Добавить 50 ГБ к LV sudo xfs_growfs /var/lib/postgresql # Расширить ФС # Снапшот для онлайн-бэкапа: sudo lvcreate -L 10G -s -n postgres_snap /dev/data_vg/postgres_lv sudo mount -o ro /dev/data_vg/postgres_snap /mnt/backup_snap # rsync с /mnt/backup_snap → бэкап без остановки БД sudo umount /mnt/backup_snap sudo lvremove -f /dev/data_vg/postgres_snap Часть 5: Мониторинг и observabilityВстроенные инструменты диагностики# ── Производительность системы ─────────────────────────────────────────────── # top на стероидах: htop sudo apt install htop # Статистика I/O по процессам sudo iotop -o # -o = только активные процессы # Сетевые соединения (замена netstat) ss -tunap # TCP/UDP, номера, приложения, pid ss -s # Сводная статистика ss 'state established' # Только ESTABLISHED # Дисковый I/O в реальном времени iostat -xz 1 # Расширенная статистика, без нулей, обновление 1с # Нагрузка на сеть по интерфейсам sar -n DEV 1 # Из пакета sysstat # Сколько памяти реально свободно free -h cat /proc/meminfo | grep -E "MemAvailable|MemFree|Cached|SwapUsed" # ── Анализ производительности ──────────────────────────────────────────────── # perf: профилировщик ядра (какие системные вызовы тормозят) sudo perf top -g # Онлайн profiling sudo perf stat -p $(pgrep myapp) sleep 10 # Статистика за 10 секунд # strace: что делает процесс (дорого, только для диагностики) sudo strace -p $(pgrep myapp) -e trace=network,file -T 2>&1 | head -50 # lsof: открытые файлы и сокеты sudo lsof -p $(pgrep myapp) | wc -l # Сколько открыто sudo lsof -i :8080 # Кто слушает порт 8080 # ── Диагностика сети ───────────────────────────────────────────────────────── # Статистика TCP ошибок (ретрансмиты, dropped пакеты) netstat -s | grep -E "retransmit|failed|overflow|listen" # Или через nstat (более детально): nstat -az | grep -i -E "retrans|drop|overflow|fail" # Потери пакетов на интерфейсе ip -s link show eth0 # RX errors/dropped/overrun — проблемы приёма # TX errors/dropped — проблемы передачи # Traceroute с временами (для диагностики latency) mtr --report --report-cycles 20 8.8.8.8 # TCP-дамп для анализа (осторожно с нагрузкой!) sudo tcpdump -i eth0 -n port 5432 -c 1000 -w /tmp/postgres.pcap # Анализ в Wireshark Настройка централизованного логирования# journald — настройка хранения логов sudo tee /etc/systemd/journald.conf.d/99-production.conf > /dev/null <<'EOF' [Journal] Storage=persistent # Хранить на диске (не только в RAM) Compress=yes # Сжатие SystemMaxUse=2G # Максимум 2 ГБ для системных логов SystemKeepFree=500M # Оставлять 500 МБ свободными MaxRetentionSec=30day # Хранить не дольше 30 дней MaxFileSec=1day # Ротация ежедневно ForwardToSyslog=no # Не дублировать в rsyslog (если не нужно) RateLimitBurst=1000 # Лимит: 1000 сообщений RateLimitIntervalSec=30s # за 30 секунд на единицу EOF sudo systemctl restart systemd-journald # Полезные запросы journalctl: journalctl --since "1 hour ago" -p err # Ошибки за последний час journalctl -u myapp -f --output=json # Поток логов в JSON journalctl --disk-usage # Сколько занимают логи journalctl --vacuum-time=7d # Удалить старше 7 дней # rsyslog → файлы (для совместимости с legacy-инструментами): # /etc/rsyslog.d/99-production.conf sudo tee /etc/rsyslog.d/99-production.conf > /dev/null <<'EOF' # Высокопроизводительный режим $ActionQueueType LinkedList $ActionQueueSize 10000 $ActionResumeRetryCount -1 # Бесконечный retry при недоступности цели # Отправка в centralized syslog (Loki, Graylog, Splunk) *.* @@syslog.internal.example.com:514 # @@ = TCP (надёжнее UDP) EOF Prometheus Node Exporter: метрики для Grafana# Установка Node Exporter (собирает 1000+ метрик системы) wget https://github.com/prometheus/node_exporter/releases/latest/download/node_exporter-*.linux-amd64.tar.gz tar xf node_exporter-*.tar.gz sudo mv node_exporter-*/node_exporter /usr/local/bin/ sudo chmod +x /usr/local/bin/node_exporter # systemd unit для Node Exporter sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<'EOF' [Unit] Description=Prometheus Node Exporter After=network.target [Service] Type=simple User=nobody ExecStart=/usr/local/bin/node_exporter \ --collector.systemd \ --collector.processes \ --collector.interrupts \ --collector.tcpstat \ --collector.diskstats \ --web.listen-address=:9100 \ --web.telemetry-path=/metrics Restart=on-failure RestartSec=5s NoNewPrivileges=true PrivateTmp=true [Install] WantedBy=multi-user.target EOF sudo systemctl enable --now node_exporter # Проверка curl -s http://localhost:9100/metrics | head -20 Ключевые метрики для алертов (Prometheus rules): # /etc/prometheus/rules/node-alerts.yml groups: - name: node-alerts rules: - alert: HighCPULoad expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85 for: 10m labels: severity: warning annotations: summary: "CPU > 85% на {{ $labels.instance }} в течение 10 мин" - alert: LowMemory expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100 < 10 for: 5m labels: severity: critical annotations: summary: "Доступно < 10% RAM на {{ $labels.instance }}" - alert: DiskSpaceCritical expr: node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs"} / node_filesystem_size_bytes * 100 < 10 for: 5m labels: severity: critical annotations: summary: "Диск {{ $labels.mountpoint }} заполнен на 90%+" - alert: HighDiskIOUtilization expr: rate(node_disk_io_time_seconds_total[5m]) * 100 > 80 for: 10m labels: severity: warning annotations: summary: "Диск {{ $labels.device }} загружен на 80%+" - alert: HighNetworkErrorRate expr: rate(node_network_receive_errs_total[5m]) + rate(node_network_transmit_errs_total[5m]) > 10 for: 5m labels: severity: warning - alert: SystemdServiceFailed expr: node_systemd_unit_state{state="failed"} == 1 for: 1m labels: severity: critical annotations: summary: "Сервис {{ $labels.name }} в состоянии Failed" Часть 6: Безопасность в продакшенеSSH: только так и никак иначе# /etc/ssh/sshd_config.d/99-hardening.conf sudo tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null <<'EOF' # Только ключи, никаких паролей PasswordAuthentication no ChallengeResponseAuthentication no KbdInteractiveAuthentication no PermitEmptyPasswords no # Запрет root-логина PermitRootLogin no # Только конкретные пользователи или группы AllowGroups sshusers sudo # Современные алгоритмы (убираем слабые) KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256 HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256 Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com # Таймаут неактивной сессии (30 минут) ClientAliveInterval 300 ClientAliveCountMax 6 # Защита от брутфорса: максимум 3 попытки аутентификации MaxAuthTries 3 MaxSessions 10 LoginGraceTime 30s # Отключить X11 forwarding и агент forwarding на серверах X11Forwarding no AllowAgentForwarding no # Логировать все подключения LogLevel VERBOSE EOF sudo sshd -t # Проверка синтаксиса sudo systemctl reload sshd UFW / nftables: файрвол# UFW — простой и читаемый интерфейс к iptables/nftables sudo apt install ufw # Политика по умолчанию: всё запрещено входящее, всё разрешено исходящее sudo ufw default deny incoming sudo ufw default allow outgoing # Разрешаем только нужное sudo ufw allow from 10.0.0.0/8 to any port 22 # SSH только из внутренней сети sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow from 10.0.0.0/8 to any port 9100 # Node Exporter только изнутри # Rate limiting для SSH (защита от брутфорса) sudo ufw limit ssh sudo ufw enable sudo ufw status verbose # Логирование отклонённых пакетов sudo ufw logging on fail2ban: автоматическая блокировка атакsudo apt install fail2ban # /etc/fail2ban/jail.local sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF' [DEFAULT] bantime = 3600 # Блокировка на 1 час findtime = 600 # Окно анализа: 10 минут maxretry = 5 # Максимум попыток # Уведомление по email destemail = ops@yourcompany.com sender = fail2ban@yourserver.example.com action = %(action_mwl)s # Ban + email с логами # Белый список (никогда не блокировать) ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16 [sshd] enabled = true port = ssh logpath = %(sshd_log)s maxretry = 3 # SSH: только 3 попытки [nginx-http-auth] enabled = true maxretry = 5 [nginx-limit-req] enabled = true maxretry = 10 [nginx-botsearch] enabled = true maxretry = 2 EOF sudo systemctl enable --now fail2ban sudo fail2ban-client status # Общий статус sudo fail2ban-client status sshd # Статус SSH jail auditd: аудит действий на сервереsudo apt install auditd # /etc/audit/rules.d/99-production.rules sudo tee /etc/audit/rules.d/99-production.rules > /dev/null <<'EOF' # Удаляем все текущие правила -D # Максимальный буфер (для highload) -b 8192 # Отказоустойчивость: при ошибке — продолжать (не паниковать) -f 1 # ── Критические файлы ──────────────────────────────────────────────────────── -w /etc/passwd -p wa -k identity -w /etc/shadow -p wa -k identity -w /etc/sudoers -p wa -k sudo_changes -w /etc/ssh/ -p wa -k ssh_config # Системные бинарники -w /usr/bin/sudo -p x -k privileged -w /usr/bin/su -p x -k privileged -w /bin/chmod -p x -k privileged -w /bin/chown -p x -k privileged # Изменения конфигурации cron -w /etc/cron.d/ -p wa -k cron -w /var/spool/cron/ -p wa -k cron # Загрузка/выгрузка модулей ядра -w /sbin/insmod -p x -k kernel_modules -w /sbin/rmmod -p x -k kernel_modules -a always,exit -F arch=b64 -S init_module -k kernel_modules # Сетевые изменения -a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale -w /etc/hosts -p wa -k hosts -w /etc/network/ -p wa -k network # Неудачные системные вызовы (попытки эскалации привилегий) -a always,exit -F arch=b64 -S open -F exit=-EACCES -k access_denied -a always,exit -F arch=b64 -S open -F exit=-EPERM -k access_denied # Мониторинг директории приложения -w /opt/myapp/bin/ -p x -k app_exec EOF sudo augenrules --load # Загрузить правила sudo auditctl -l # Проверить активные правила ausearch -k sudo_changes --start today # Посмотреть события sudo aureport --summary # Сводный отчёт Часть 7: Процедуры и автоматизация обслуживанияСкрипт проверки здоровья сервера#!/bin/bash # /usr/local/bin/health-check.sh — ежедневный health check # Запускать через cron: 0 7 * * * /usr/local/bin/health-check.sh set -euo pipefail REPORT_FILE="/var/log/health-check-$(date +%Y%m%d).log" ALERT_EMAIL="ops@yourcompany.com" ISSUES=0 log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$REPORT_FILE"; } warn() { log "⚠️ WARN: $*"; ((ISSUES++)); } err() { log "❌ ERROR: $*"; ((ISSUES++)); } ok() { log "✅ OK: $*"; } log "=== Health Check: $(hostname) === $(date) ===" # ── Свободное место на дисках ──────────────────────────────────────────────── log "--- Дисковое пространство ---" while IFS= read -r line; do usage=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') [ "$usage" -ge 90 ] && err "Диск $mount заполнен на $usage%" && continue [ "$usage" -ge 80 ] && warn "Диск $mount заполнен на $usage%" && continue ok "Диск $mount: $usage%" done < <(df -h --output=pcent,target -x tmpfs -x devtmpfs | tail -n +2) # ── Память ─────────────────────────────────────────────────────────────────── log "--- Память ---" available_mb=$(awk '/MemAvailable/ {printf "%.0f", $2/1024}' /proc/meminfo) total_mb=$(awk '/MemTotal/ {printf "%.0f", $2/1024}' /proc/meminfo) used_pct=$(( (total_mb - available_mb) * 100 / total_mb )) swap_used=$(awk '/SwapTotal/{t=$2} /SwapFree/{f=$2} END{printf "%.0f", (t-f)/1024}' /proc/meminfo) [ "$used_pct" -ge 90 ] && err "RAM: использовано $used_pct%" [ "$used_pct" -ge 80 ] && warn "RAM: использовано $used_pct%" ok "RAM: $used_pct% использовано ($available_mb МБ свободно)" [ "$swap_used" -gt 100 ] && warn "Swap: используется ${swap_used} МБ" # ── Failed сервисы ─────────────────────────────────────────────────────────── log "--- Systemd сервисы ---" failed=$(systemctl list-units --state=failed --no-legend --no-pager | awk '{print $1}') if [ -n "$failed" ]; then for svc in $failed; do err "Сервис в Failed: $svc" done else ok "Все сервисы работают" fi # ── Нагрузка ───────────────────────────────────────────────────────────────── log "--- CPU загрузка ---" cpu_cores=$(nproc) load_avg=$(cut -d' ' -f1 /proc/loadavg) load_int=$(echo "$load_avg" | cut -d. -f1) [ "$load_int" -ge "$((cpu_cores * 2))" ] && err "Load average $load_avg при $cpu_cores ядрах" [ "$load_int" -ge "$cpu_cores" ] && warn "Load average $load_avg при $cpu_cores ядрах" ok "Load average: $load_avg (ядер: $cpu_cores)" # ── SSL-сертификаты ────────────────────────────────────────────────────────── log "--- SSL сертификаты ---" for cert_file in /etc/letsencrypt/live/*/cert.pem /etc/ssl/certs/*.pem; do [ -f "$cert_file" ] || continue expiry=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null \ | cut -d= -f2) expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null || continue) days_left=$(( (expiry_epoch - $(date +%s)) / 86400 )) [ "$days_left" -le 7 ] && err "Сертификат $cert_file истекает через $days_left дней!" [ "$days_left" -le 30 ] && warn "Сертификат $cert_file истекает через $days_left дней" [ "$days_left" -gt 30 ] && ok "Сертификат $cert_file: осталось $days_left дней" done # ── Итог ───────────────────────────────────────────────────────────────────── log "" log "=== Итог: $ISSUES проблем(а) ===" if [ "$ISSUES" -gt 0 ]; then mail -s "⚠️ Health Check FAILED: $(hostname) — $ISSUES проблем" \ "$ALERT_EMAIL" < "$REPORT_FILE" fi exit $((ISSUES > 0 ? 1 : 0)) Автоматизированный бэкап с проверкой целостности#!/bin/bash # /usr/local/bin/backup.sh — ежедневный инкрементальный бэкап set -euo pipefail BACKUP_DIR="/var/backup" REMOTE="backup-user@backup-server.example.com:/backups/$(hostname)" DATE=$(date +%Y%m%d-%H%M) LOG="/var/log/backup.log" RETENTION_DAYS=30 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; } log "=== Бэкап начат: $DATE ===" # ── Конфигурация ───────────────────────────────────────────────────────────── rsync -avz --delete \ --exclude='*.tmp' \ --exclude='*.log' \ --exclude='/proc' \ --exclude='/sys' \ --exclude='/dev' \ --exclude='/run' \ --exclude='/tmp' \ --link-dest="$REMOTE/latest" \ / \ "$REMOTE/$DATE/" \ --log-file="$LOG" \ 2>&1 # Обновляем симлинк на последний бэкап ssh backup-user@backup-server.example.com \ "ln -sfn /backups/$(hostname)/$DATE /backups/$(hostname)/latest" # ── Проверка целостности (checksums) ───────────────────────────────────────── log "Генерация checksums..." find /etc /opt/myapp /var/lib -type f -newer /var/backup/.last_backup \ -exec sha256sum {} \; > "/var/backup/checksums-$DATE.txt" 2>/dev/null || true rsync -avz "/var/backup/checksums-$DATE.txt" \ "$REMOTE/checksums/$DATE.txt" touch /var/backup/.last_backup # ── Очистка старых бэкапов ─────────────────────────────────────────────────── log "Очистка бэкапов старше $RETENTION_DAYS дней..." ssh backup-user@backup-server.example.com \ "find /backups/$(hostname) -maxdepth 1 -type d -mtime +$RETENTION_DAYS \ ! -name 'latest' -exec rm -rf {} + 2>/dev/null; echo done" log "=== Бэкап завершён успешно ===" Быстрая шпаргалка: что сделать на каждом новом сервере#!/bin/bash # Минимальный чеклист для нового production-сервера echo "=== 1. Обновить систему ===" apt update && apt upgrade -y # или dnf update -y echo "=== 2. Создать admin-пользователя ===" adduser deploy usermod -aG sudo deploy mkdir -p /home/deploy/.ssh cp ~/.ssh/authorized_keys /home/deploy/.ssh/ chown -R deploy:deploy /home/deploy/.ssh chmod 700 /home/deploy/.ssh chmod 600 /home/deploy/.ssh/authorized_keys echo "=== 3. Настроить SSH ===" sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config systemctl reload sshd echo "=== 4. Файрвол ===" ufw --force enable ufw default deny incoming ufw allow from 10.0.0.0/8 to any port 22 ufw allow 80 443 echo "=== 5. NTP ===" apt install -y chrony systemctl enable --now chrony echo "=== 6. Применить sysctl тюнинг ===" # (скопировать /etc/sysctl.d/99-production.conf из шаблона) sysctl --system echo "=== 7. Node Exporter ===" # (установить и включить — см. раздел выше) echo "=== 8. fail2ban ===" apt install -y fail2ban systemctl enable --now fail2ban echo "=== 9. Unattended upgrades ===" apt install -y unattended-upgrades dpkg-reconfigure -f noninteractive unattended-upgrades echo "=== 10. Базовый мониторинг диска ===" # Добавить в cron: 0 7 * * * /usr/local/bin/health-check.sh echo "✅ Базовая настройка завершена. Не забыть:" echo " - Настроить бэкап" echo " - Подключить к Prometheus/Grafana" echo " - Добавить в систему конфигурационного управления (Ansible/Salt)" echo " - Задокументировать сервер в CMDB" ЗаключениеProduction Linux — это не дистрибутив, это дисциплина. Правильный выбор дистрибутива даёт фундамент. Тюнинг ядра и sysctl — производительность. systemd с правильными политиками — надёжность. Мониторинг — видимость. Безопасность — защиту. А автоматизация обслуживания — предсказуемость. Самые частые провалы в продакшене: Не настроены лимиты (ulimit, systemd LimitNOFILE) — приложение падает при нагрузке Нет мониторинга диска — о заполнении узнают по жалобам пользователей SSH доступен по паролям — вопрос не "взломают ли", а "когда" Не тестируются бэкапы — они есть, но не работают когда нужны Нет процедуры обновления — серверы не обновляются годами Используйте Ansible или Terraform для воспроизводимости: каждая настройка из этой статьи должна быть в коде, а не только в голове у одного администратора. Сервер должен разворачиваться автоматически — это единственная гарантия того, что в 3 часа ночи вы сможете его поднять заново.
  12. С чего начинается тюнингNginx в дефолтной конфигурации — это как спортивный автомобиль с заводскими настройками для езды по бездорожью: едет, но не так быстро, как мог бы. Хорошая новость: большинство важных оптимизаций достигается правкой конфига, а не покупкой более мощного железа. Плохая новость: многие "гайды по тюнингу" в интернете — копипаста десятилетней давности, без понимания что и зачем. Параметры для Nginx 1.8 на 2-ядерном сервере копируют на 32-ядерный продакшен под highload — и удивляются что не помогает или становится хуже. Этот материал — о том, как думать о тюнинге Nginx: что делает каждый параметр, какие компромиссы он несёт, и как проверить что оптимизация действительно работает. Версии в статье: Nginx 1.24+ / 1.25+ (mainline). Большинство конфигов работают с 1.18+. Диагностика перед тюнингом: что измерятьТюнинг без метрик — гадание на кофейной гуще. Сначала измеряем, потом меняем, потом снова измеряем. Текущее состояние Nginx# Версия и скомпилированные модули nginx -V 2>&1 | tr ' ' '\n' | grep -E 'version|with-|without-' # Активная конфигурация (проверка синтаксиса) nginx -t # Рабочие процессы и их нагрузка ps aux | grep nginx top -p $(pgrep -d',' nginx) # Открытые соединения ss -s ss -tnp | grep nginx | wc -l # Статус (если включён stub_status) curl -s http://127.0.0.1/nginx_status # Active connections: 847 # server accepts handled requests # 12340582 12340582 28473910 # Reading: 12 Writing: 84 Waiting: 751 # Лимиты файловых дескрипторов cat /proc/$(pgrep -f 'nginx: master')/limits | grep 'open files' ulimit -n Нагрузочное тестирование# wrk — современный HTTP benchmarker # Установка: apt install wrk / brew install wrk wrk -t12 -c400 -d30s --latency http://your-server/api/endpoint # Параметры: # -t12 — 12 потоков (= число ядер) # -c400 — 400 конкурентных соединений # -d30s — 30 секунд # --latency — показать перцентили задержек # Вывод: # Running 30s test @ http://your-server/ # 12 threads and 400 connections # Thread Stats Avg Stdev Max +/- Stdev # Latency 23.45ms 8.12ms 890ms 92.34% # Req/Sec 1.45k 312.45 2.10k 68.23% # Latency Distribution # 50% 21.23ms # 75% 28.45ms # 90% 35.67ms # 99% 78.90ms ← 99-й перцентиль важнее среднего! # 521245 requests in 30.00s, 2.34GB read # Requests/sec: 17374.83 # Transfer/sec: 79.92MB # ab (Apache Benchmark) — встроен везде, но хуже wrk ab -n 10000 -c 100 http://your-server/ # hey — ещё один вариант (Go) hey -n 50000 -c 200 http://your-server/ Мониторинг в реальном времени# Топ запросов по времени ответа (из access_log) awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -20 # Количество запросов в секунду (live) tail -f /var/log/nginx/access.log | pv -l -i 1 > /dev/null # Распределение кодов ответов за последний час awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" \ '$4 ~ d {print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn # Топ IP по количеству запросов awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 Уровень 1: Системные настройки LinuxNginx ограничен операционной системой. Без правильной настройки Linux все оптимизации Nginx упрутся в системный потолок. Файловые дескрипторы# /etc/security/limits.conf nginx soft nofile 65535 nginx hard nofile 65535 root soft nofile 65535 root hard nofile 65535 # Для systemd (приоритет над limits.conf): # /etc/systemd/system/nginx.service.d/override.conf [Service] LimitNOFILE=65535 sudo systemctl daemon-reload sudo systemctl restart nginx # Проверка: cat /proc/$(cat /var/run/nginx.pid)/limits | grep 'open files' Сетевой стек (sysctl)# /etc/sysctl.d/99-nginx.conf # ===== TCP буферы ===== # Увеличиваем буферы приёма/передачи net.core.rmem_default = 262144 net.core.wmem_default = 262144 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 # ===== Очередь соединений ===== # Размер очереди для accept() — важно при всплесках трафика net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 # ===== TCP оптимизации ===== # Быстрое переиспользование TIME_WAIT соединений net.ipv4.tcp_tw_reuse = 1 # Алгоритм управления перегрузкой # BBR — лучший выбор для большинства продакшен-сценариев (ядро 4.9+) net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr # Уменьшаем время FIN_WAIT2 (2 минуты по умолчанию — слишком долго) net.ipv4.tcp_fin_timeout = 15 # Максимальное число открытых TCP соединений net.ipv4.tcp_max_tw_buckets = 1440000 # Разрешаем bind на порт без TIME_WAIT net.ipv4.tcp_timestamps = 1 # ===== Очередь обработки пакетов ===== net.core.netdev_max_backlog = 65535 # ===== Локальный диапазон портов ===== # Для upstream keepalive нужно много эфемерных портов net.ipv4.ip_local_port_range = 1024 65535 # Применить: sudo sysctl -p /etc/sysctl.d/99-nginx.conf # Проверить BBR: sysctl net.ipv4.tcp_congestion_control # должно быть: net.ipv4.tcp_congestion_control = bbr Прозрачные hugepages и планировщик I/O# Для высоконагруженных серверов — отключить transparent hugepages # (могут вызывать latency spikes) echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag # В /etc/rc.local для постоянства: echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.local echo 'echo never > /sys/kernel/mm/transparent_hugepage/defrag' >> /etc/rc.local # Планировщик I/O для SSD (none или mq-deadline быстрее cfq) echo mq-deadline > /sys/block/sda/queue/scheduler # Проверить: cat /sys/block/sda/queue/scheduler Уровень 2: Базовый конфиг Nginx — Worker и Events# /etc/nginx/nginx.conf # ===== WORKER PROCESSES ===== # Правило: 1 воркер на 1 физическое ядро CPU # auto — Nginx сам определяет количество ядер (рекомендуется) worker_processes auto; # Привязка воркеров к ядрам (CPU affinity) # Уменьшает cache miss у процессора, повышает производительность ~5-10% # Для 4 ядер: # worker_cpu_affinity 0001 0010 0100 1000; # Для auto (Nginx 1.9+): worker_cpu_affinity auto; # Приоритет процесса (от -20 до 20, меньше = выше приоритет) # -5 даёт небольшое преимущество без ущерба системе worker_priority -5; # Файловые дескрипторы на воркер (должно совпадать с ulimit -n) worker_rlimit_nofile 65535; # PID файл pid /var/run/nginx.pid; # ===== ERROR LOG ===== # warn в продакшене (info/debug — слишком verbose для highload) error_log /var/log/nginx/error.log warn; # ===== EVENTS ===== events { # Максимум соединений на воркер # Итого соединений = worker_processes × worker_connections # Не ставьте больше 65535 — ограничение Linux # Реально для highload: 4096-16384 worker_connections 10240; # epoll — единственный правильный выбор на Linux # (Nginx выбирает автоматически, но явно лучше) use epoll; # Принимать все ожидающие соединения за один вызов accept() # ОБЯЗАТЕЛЬНО для highload! Без этого воркер обрабатывает по 1 соединению multi_accept on; # Принимать мьютекс для accept() (устарело в современных ядрах, # но оставляем для совместимости) # accept_mutex off; # Можно отключить на ядрах 3.9+ } Уровень 3: HTTP блок — основные оптимизацииhttp { # ===== БАЗОВЫЕ MIME ТИПЫ ===== include /etc/nginx/mime.types; default_type application/octet-stream; # ===== SENDFILE: ZERO-COPY ПЕРЕДАЧА ФАЙЛОВ ===== # Передаёт файлы напрямую из файловой системы в сокет # минуя user space — экономит копирование данных в памяти # ОБЯЗАТЕЛЬНО для статики! sendfile on; # Отправлять заголовки и начало файла в одном TCP-пакете # Работает только совместно с sendfile on tcp_nopush on; # Отключить алгоритм Nagle — не буферизировать маленькие пакеты # Уменьшает latency для интерактивных запросов # tcp_nopush и tcp_nodelay вместе: сначала накапливаем (nopush), # потом сразу отправляем последний пакет (nodelay) tcp_nodelay on; # ===== KEEPALIVE ===== # Время жизни keepalive соединения с клиентом # 65s стандарт, 75s — максимум до таймаута прокси (Cloudflare = 90s) keepalive_timeout 65; # Максимум запросов через одно keepalive соединение # После этого — закрываем и открываем новое # 1000 — хороший баланс между переиспользованием и памятью keepalive_requests 1000; # ===== БУФЕРЫ ===== # Эти настройки критически важны для производительности proxy! # Размер хэш-таблицы имён серверов server_names_hash_bucket_size 128; server_names_hash_max_size 1024; # Буфер для чтения заголовков запроса клиента # 16k достаточно для большинства запросов, включая большие cookie client_header_buffer_size 16k; large_client_header_buffers 4 32k; # Максимальный размер тела запроса (загрузка файлов) # 0 — отключить ограничение (не рекомендуется!) client_max_body_size 64m; # Таймаут на чтение тела запроса client_body_timeout 30s; # Таймаут на чтение заголовков запроса client_header_timeout 15s; # Таймаут на отправку ответа клиенту # (между двумя последовательными операциями send) send_timeout 30s; # Буфер для тела запроса в памяти (если больше — пишем на диск) client_body_buffer_size 256k; # ===== ТИПЫ ХЭШЕЙ ===== types_hash_max_size 4096; types_hash_bucket_size 128; # ===== БЕЗОПАСНОСТЬ: УБИРАЕМ ВЕРСИЮ NGINX ===== server_tokens off; # ===== ЛОГИ ===== # Формат логов с временем обработки запроса — важно для анализа! log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'rt=$request_time uct=$upstream_connect_time ' 'uht=$upstream_header_time urt=$upstream_response_time'; # Расширенный формат для детальной диагностики: log_format detailed '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' 'rt=$request_time ' 'urt="$upstream_response_time" ' 'uct="$upstream_connect_time" ' 'uht="$upstream_header_time" ' 'cs=$upstream_cache_status ' 'host=$host ' 'xff="$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main buffer=64k flush=5s; # buffer=64k — буферизация логов (не пишем на диск каждую строку) # flush=5s — сбрасываем буфер каждые 5 секунд # Для максимальной производительности — отключить access_log на статике # (настраивается в location блоках) # ===== ВКЛЮЧАЕМ ПОДКОНФИГИ ===== include /etc/nginx/conf.d/*.conf; } Уровень 4: Gzip и Brotli — сжатие ответовhttp { # ===== GZIP ===== gzip on; # Не сжимать ответы для IE6 (исторический артефакт, можно убрать) gzip_disable "msie6"; # Сжимать ответы для всех клиентов, в т.ч. через прокси # any — сжимать независимо от заголовка Via gzip_proxied any; # Уровень сжатия: 1-9 # 1 — быстро, мало сжатие (~60%) # 6 — баланс (рекомендуется для продакшена ~70%) # 9 — максимум, но тратит значительно больше CPU (~72%, но в 3-5 раз медленнее 6) gzip_comp_level 6; # Минимальный размер для сжатия (не сжимать маленькие файлы — смысла нет) gzip_min_length 1024; # Буферы для сжатия gzip_buffers 16 8k; # HTTP версия (сжимать и для HTTP/1.0 клиентов — редко нужно) gzip_http_version 1.1; # Типы контента для сжатия # text/html сжимается всегда (по умолчанию) gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml application/xml+rss application/x-javascript application/x-font-ttf application/vnd.ms-fontobject font/opentype image/svg+xml image/x-icon; # Добавить заголовок Vary: Accept-Encoding # Указывает прокси-серверам хранить сжатые и несжатые версии отдельно gzip_vary on; # ===== BROTLI (требует модуль ngx_brotli) ===== # Brotli на 15-25% эффективнее gzip при тех же CPU-затратах # Поддерживается всеми современными браузерами # Установка модуля (Ubuntu/Debian): # apt install libnginx-mod-http-brotli-filter # Или из исходников: https://github.com/google/ngx_brotli # load_module modules/ngx_http_brotli_filter_module.so; # load_module modules/ngx_http_brotli_static_module.so; brotli on; brotli_comp_level 6; # 0-11, 6 — хороший баланс brotli_min_length 1024; brotli_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml application/xml+rss image/svg+xml; # Brotli static — отдавать предкомпрессированные .br файлы # Нужно сгенерировать: find /var/www -name "*.js" | xargs -I{} brotli {} brotli_static on; # ===== GZIP STATIC — предкомпрессированные .gz файлы ===== # Если файл app.js.gz существует — отдаём его без CPU на сжатие gzip_static on; } Предварительное сжатие статики (экономит CPU)#!/bin/bash # Скрипт для предкомпрессии статических файлов STATIC_DIR="/var/www/html" find "$STATIC_DIR" \ \( -name "*.js" -o -name "*.css" -o -name "*.html" -o -name "*.json" \ -o -name "*.xml" -o -name "*.svg" \) \ -type f | while read file; do # gzip (только если .gz не существует или файл новее) if [ ! -f "${file}.gz" ] || [ "$file" -nt "${file}.gz" ]; then gzip -9 -k "$file" fi # brotli if command -v brotli &> /dev/null; then if [ ! -f "${file}.br" ] || [ "$file" -nt "${file}.br" ]; then brotli -q 11 -k "$file" fi fi done echo "Предкомпрессия завершена: $(find $STATIC_DIR -name '*.gz' | wc -l) gz файлов" Уровень 5: Кэширование — proxy_cache и FastCGI cacheProxy Cache (для проксирования на upstream)http { # ===== ЗОНА КЭША ===== # keys_zone=cache_name:10m — зона памяти для хранения ключей # levels=1:2 — структура директорий (более эффективный поиск) # inactive=60m — удалять неиспользуемые файлы через 60 минут # max_size=10g — максимальный размер кэша на диске # use_temp_path=off — не использовать временный путь (быстрее) proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:50m inactive=60m max_size=10g use_temp_path=off; server { location /api/ { proxy_pass http://backend; # ===== PROXY БУФЕРЫ ===== # Буферизировать ответ от upstream в памяти # Важно: без буферизации Nginx держит соединение с upstream # пока клиент не скачает весь ответ (медленные клиенты = занятые воркеры) proxy_buffering on; # Количество и размер буферов для тела ответа # proxy_buffers × proxy_buffer_size = RAM на соединение # 32 × 16k = 512k на соединение proxy_buffers 32 16k; proxy_buffer_size 16k; # Для заголовков ответа # Если ответ не помещается в proxy_buffers — пишем во временный файл proxy_max_temp_file_size 0; # 0 = отключить (пишем всё в память) # или установить лимит: proxy_max_temp_file_size 1024m; # Буфер для занятых соединений (busy = клиент читает медленно) proxy_busy_buffers_size 64k; # ===== PROXY ТАЙМАУТЫ ===== # Таймаут установки соединения с upstream proxy_connect_timeout 5s; # Таймаут между двумя последовательными операциями чтения от upstream proxy_read_timeout 60s; # Таймаут передачи данных к upstream proxy_send_timeout 60s; # ===== ЗАГОЛОВКИ К UPSTREAM ===== proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; # HTTP/1.1 для keepalive! proxy_set_header Connection ""; # Убрать заголовок Connection для keepalive # ===== КЭШИРОВАНИЕ ===== proxy_cache proxy_cache; proxy_cache_valid 200 302 10m; # Кэшировать 200/302 на 10 минут proxy_cache_valid 404 1m; # 404 — на 1 минуту proxy_cache_valid any 30s; # Остальное — 30 секунд # Ключ кэша (что уникально идентифицирует запрос) proxy_cache_key "$scheme$request_method$host$request_uri"; # Методы для кэширования (по умолчанию только GET и HEAD) proxy_cache_methods GET HEAD; # Кэшировать ответы с заголовком Set-Cookie # (осторожно — персональные данные!) # proxy_ignore_headers Set-Cookie; # Stale cache — отдавать устаревший кэш пока upstream недоступен # Спасает от "пятистотки" при временных проблемах с бэкендом proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; # Блокировка одновременных запросов к upstream (coalescing) # Один запрос идёт к upstream, остальные ждут результата в кэше proxy_cache_lock on; proxy_cache_lock_timeout 5s; proxy_cache_lock_age 5s; # Фоновое обновление кэша (stale-while-revalidate) # Отдаём устаревший кэш и одновременно запускаем фоновое обновление proxy_cache_background_update on; # Добавляем заголовок X-Cache-Status для отладки add_header X-Cache-Status $upstream_cache_status always; } # ===== СТАТИКА: МАКСИМАЛЬНЫЙ КЭШ ===== location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000"; access_log off; # Не логировать статику (экономит I/O) tcp_nodelay off; # Для больших файлов nopush важнее nodelay sendfile on; aio on; # Асинхронный I/O для больших файлов } location ~* \.(js|css)$ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000"; access_log off; } location ~* \.(html|htm)$ { expires 1h; add_header Cache-Control "public, max-age=3600, must-revalidate"; } } } FastCGI Cache (для PHP-FPM)http { # Зона FastCGI кэша fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fastcgi_cache:20m inactive=60m max_size=5g use_temp_path=off; # Глобальная переменная для определения статуса кэша map $request_method $no_cache_method { default 0; POST 1; PUT 1; DELETE 1; PATCH 1; } server { set $skip_cache 0; # Не кэшировать авторизованных пользователей (например, WordPress) if ($http_cookie ~* "wordpress_logged_in|woocommerce_cart") { set $skip_cache 1; } # Не кэшировать POST запросы if ($request_method = POST) { set $skip_cache 1; } # Не кэшировать URL с query string (можно убрать если кэш по полному URL) # if ($query_string != "") { # set $skip_cache 1; # } # Не кэшировать admin/личный кабинет if ($request_uri ~* "^/admin|^/wp-admin|^/login") { set $skip_cache 1; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # ===== FastCGI БУФЕРЫ ===== fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; fastcgi_busy_buffers_size 64k; # Таймауты fastcgi_connect_timeout 5s; fastcgi_read_timeout 60s; fastcgi_send_timeout 60s; # ===== КЭШ ===== fastcgi_cache fastcgi_cache; fastcgi_cache_key "$scheme$request_method$host$request_uri"; fastcgi_cache_valid 200 5m; fastcgi_cache_valid 301 302 1m; fastcgi_cache_valid 404 30s; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_use_stale error timeout updating http_500 http_503; fastcgi_cache_lock on; fastcgi_cache_background_update on; add_header X-FastCGI-Cache $upstream_cache_status; } } } Инвалидация кэша# Очистить весь кэш proxy find /var/cache/nginx/proxy -type f -delete # Очистить конкретный URL (через модуль ngx_cache_purge): # location ~ /purge(/.*) { # fastcgi_cache_purge fastcgi_cache "$scheme$request_method$host$1"; # } # curl -X PURGE http://your-server/api/products/123 # Мониторинг кэша: # X-Cache-Status значения: # HIT — ответ из кэша ✅ # MISS — кэш не нашёл, запрос к upstream # BYPASS — кэш пропущен (skip_cache = 1) # EXPIRED — кэш устарел, запрошен свежий ответ # STALE — отдан устаревший кэш (upstream недоступен) # UPDATING — отдан устаревший кэш пока фоновое обновление # REVALIDATED — кэш подтверждён (304 от upstream) Уровень 6: Upstream Keepalive и балансировкаhttp { # ===== UPSTREAM С KEEPALIVE ===== upstream backend { # Алгоритм балансировки # least_conn — меньше соединений = меньше задержка (лучше для long-poll) # ip_hash — один клиент всегда на один сервер (сессии) # По умолчанию: round-robin least_conn; server 10.0.0.10:8080 weight=3; # Тройной вес (мощнее) server 10.0.0.11:8080 weight=1; server 10.0.0.12:8080 weight=1 backup; # Резервный (включается при падении основных) # Параметры health check (требует nginx plus или upstream_check_module): # server 10.0.0.10:8080 max_fails=3 fail_timeout=30s; # ===== KEEPALIVE ПУЛА К UPSTREAM ===== # Количество keepalive соединений в пуле (на воркер!) # НЕ максимальное число соединений — это пул переиспользуемых! # Правило: (RPS / worker_count) × avg_response_time_sec × 1.5 # При 10000 RPS, 4 воркерах, 20мс ответе: 10000/4 × 0.02 × 1.5 = 75 keepalive 128; # Таймаут keepalive соединения с upstream keepalive_timeout 60s; # Максимум запросов через одно keepalive соединение к upstream keepalive_requests 10000; } server { location / { proxy_pass http://backend; # КРИТИЧЕСКИ ВАЖНО для upstream keepalive! # HTTP/1.1 поддерживает keepalive (1.0 — нет) proxy_http_version 1.1; # Убираем заголовок Connection: close (стандарт для HTTP/1.0 прокси) proxy_set_header Connection ""; } } } Уровень 7: SSL/TLS — производительность без потери безопасностиhttp { # ===== SSL СЕССИИ ===== # Кэш SSL сессий (повторное использование TLS handshake) # 1m ≈ 4000 сессий. Для highload: 50m-100m ssl_session_cache shared:SSL:50m; # Время жизни кэшированной SSL сессии ssl_session_timeout 1d; # 24 часа — максимум рекомендуемый # SSL Session Tickets (альтернатива session cache, статeful у клиента) # Для идеальной forward secrecy — отключить # Для максимальной производительности — включить ssl_session_tickets off; # Безопаснее, но чуть медленнее # ===== ПРОТОКОЛЫ И ШИФРЫ ===== # Только TLS 1.2 и 1.3 (1.0 и 1.1 — уязвимы и устарели) ssl_protocols TLSv1.2 TLSv1.3; # Шифры (Mozilla Modern конфигурация) ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; # Сервер выбирает шифр (не клиент) — важно для безопасности ssl_prefer_server_ciphers off; # off для TLS 1.3 (там нет выбора шифра) # ===== ECDH КРИВАЯ ===== ssl_ecdh_curve X25519:prime256v1:secp384r1; # ===== STAPLING ===== # OCSP Stapling: сервер сам проверяет сертификат и включает ответ в TLS handshake # Клиенту не нужно делать отдельный запрос к CA — быстрее! ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 1.1.1.1 valid=300s; resolver_timeout 5s; # ===== DH параметры (для DHE шифров) ===== # Генерировать: openssl dhparam -out /etc/nginx/dhparam.pem 2048 # ssl_dhparam /etc/nginx/dhparam.pem; server { listen 443 ssl; # ===== HTTP/2 ===== # Мультиплексирование запросов — один TCP для множества запросов # Сжатие заголовков (HPACK) — экономит трафик # Server Push — отправка ресурсов до запроса (редко нужен) listen 443 ssl http2; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; # ===== HSTS ===== # Браузер не будет делать HTTP запросы — сразу HTTPS # Включать только когда уверены что HTTPS работает корректно! add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # ===== SECURITY ЗАГОЛОВКИ ===== add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=()" always; } # Редирект HTTP → HTTPS server { listen 80; server_name example.com www.example.com; # 301 для всего кроме .well-known (Let's Encrypt) location /.well-known/acme-challenge/ { root /var/www/html; } location / { return 301 https://$host$request_uri; } } } Измерение времени TLS handshake# Проверка SSL без кэша (первое соединение) curl -w "\n=== Timing ===\nDNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \ --no-keepalive -s -o /dev/null https://your-server/ # С session resumption (второе соединение) curl -w "TLS Resumption Total: %{time_total}s\n" \ --no-keepalive -s -o /dev/null https://your-server/ \ --tls-session-info # Проверка OCSP Stapling: echo QUIT | openssl s_client -connect your-server:443 -status 2>/dev/null | \ grep -A 17 'OCSP response' Уровень 8: Rate Limiting — защита и QoShttp { # ===== ЗОНЫ RATE LIMITING ===== # Ограничение по IP адресу # zone=name:10m — 10МБ памяти (~160 000 IP адресов) # rate=100r/s — 100 запросов в секунду с одного IP limit_req_zone $binary_remote_addr zone=api_limit:20m rate=100r/s; # Ограничение для авторизации (жёстче!) limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m; # Ограничение по URL + IP (для конкретных эндпоинтов) limit_req_zone "$binary_remote_addr$uri" zone=upload_limit:10m rate=5r/m; # Ограничение одновременных соединений limit_conn_zone $binary_remote_addr zone=conn_limit:10m; # Лог уровень для rejected запросов (warn — не засорять error.log) limit_req_log_level warn; # Код ответа при превышении лимита (429 = Too Many Requests) limit_req_status 429; limit_conn_status 429; server { # ===== API ENDPOINT ===== location /api/ { # burst=200 — разрешить всплески до 200 запросов сверх лимита # nodelay — не задерживать burst запросы, обрабатывать немедленно # (без nodelay — запросы ставятся в очередь и задерживаются) limit_req zone=api_limit burst=200 nodelay; # Максимум 100 одновременных соединений с одного IP limit_conn conn_limit 100; proxy_pass http://backend; } # ===== АВТОРИЗАЦИЯ: СТРОГИЙ ЛИМИТ ===== location /api/auth/ { limit_req zone=auth_limit burst=10 nodelay; limit_conn conn_limit 10; proxy_pass http://backend; } # ===== ЗАГРУЗКА ФАЙЛОВ ===== location /upload/ { limit_req zone=upload_limit burst=2 nodelay; limit_conn conn_limit 5; client_max_body_size 100m; proxy_pass http://backend; } } } Белые списки для rate limitinghttp { # Карта: 0 = применять limit, 1 = пропустить geo $limit { default 1; # Ограничивать всех 10.0.0.0/8 0; # Доверенная внутренняя сеть — без ограничений 192.168.0.0/16 0; # Локальная сеть — без ограничений 1.2.3.4 0; # Конкретный IP (мониторинг, партнёры) } # Если $limit = 0 — пустой ключ, limit_req не применяется map $limit $limit_key { 0 ""; 1 $binary_remote_addr; } limit_req_zone $limit_key zone=api_limit:20m rate=100r/s; } Уровень 9: Open File Cache и другие деталиhttp { # ===== OPEN FILE CACHE ===== # Кэшировать информацию об открытых файлах: # файловые дескрипторы, размеры, время модификации, ошибки # Особенно важно при большом количестве файлов статики! # max=10000 — максимум 10000 записей в кэше # inactive=30s — удалять если не обращались 30 секунд open_file_cache max=10000 inactive=30s; # Сколько раз файл должен быть запрошен за inactive период # чтобы остаться в кэше open_file_cache_min_uses 2; # Проверять актуальность кэша каждые 60 секунд open_file_cache_valid 60s; # Кэшировать ошибки (файл не найден, нет прав) open_file_cache_errors on; # ===== SENDFILE + AIO для больших файлов ===== # Для файлов > 8MB — асинхронный I/O эффективнее aio threads; # AIO через thread pool (Nginx 1.7.11+) # или aio on; # POSIX AIO (старый вариант, хуже) directio 8m; # Файлы > 8MB: читать напрямую, минуя page cache # Полезно для больших видеофайлов которые не нужно кэшировать # ===== OUTPUT BUFFERS ===== # Размер буфера вывода (используется с sendfile) output_buffers 2 512k; # ===== ПЕРЕМЕННЫЕ ===== # Кэш переменных (для complex map и geo директив) variables_hash_max_size 2048; variables_hash_bucket_size 128; # ===== MAP HASH ===== map_hash_max_size 2048; map_hash_bucket_size 128; } Уровень 10: Полный production конфиг сервера# /etc/nginx/conf.d/example.com.conf server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com www.example.com; root /var/www/html; index index.html index.php; # SSL ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # Логи с детальным форматом access_log /var/log/nginx/example.com.access.log detailed buffer=64k flush=5s; error_log /var/log/nginx/example.com.error.log warn; # Скрываем .git, .env и другие служебные файлы location ~ /\. { deny all; access_log off; log_not_found off; } location ~* \.(env|log|sh|sql|conf|config|bak|backup|swp|tmp)$ { deny all; } # ===== СТАТИКА: МАКСИМАЛЬНАЯ ОТДАЧА ===== location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|avif|woff|woff2|ttf|eot|otf)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Vary Accept-Encoding; access_log off; log_not_found off; gzip_static on; brotli_static on; } location ~* \.(js|css|map)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; gzip_static on; brotli_static on; } # ===== FAVICON И ROBOTS ===== location = /favicon.ico { access_log off; log_not_found off; expires 1y; } location = /robots.txt { access_log off; log_not_found off; } # ===== API ===== location /api/ { limit_req zone=api_limit burst=200 nodelay; limit_conn conn_limit 100; proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache proxy_cache; proxy_cache_valid 200 5m; proxy_cache_valid 404 30s; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_lock on; proxy_cache_background_update on; add_header X-Cache-Status $upstream_cache_status always; } # ===== PHP ===== location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; fastcgi_read_timeout 60s; fastcgi_cache fastcgi_cache; fastcgi_cache_valid 200 5m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_use_stale error timeout updating http_500 http_503; fastcgi_cache_lock on; add_header X-FastCGI-Cache $upstream_cache_status; } # ===== КОРЕНЬ ===== location / { try_files $uri $uri/ /index.php?$query_string; } } Диагностика и мониторинг в продакшенеStub Status модульserver { listen 127.0.0.1:8080; location /nginx_status { stub_status; allow 127.0.0.1; allow 10.0.0.0/8; # Сеть мониторинга deny all; } } # Парсинг nginx_status: curl -s http://127.0.0.1:8080/nginx_status # Active connections: 1247 # server accepts handled requests # 87354291 87354291 245912847 # Reading: 12 Writing: 847 Waiting: 388 # Интерпретация: # Active = Reading + Writing + Waiting # Waiting = keepalive соединения (ждут следующего запроса) # Writing = активно пишем ответ клиенту # Reading = читаем запрос от клиента # Если Waiting >> Writing — много keepalive соединений, это нормально # Если Reading >> 0 постоянно — клиенты медленно отправляют запросы # Если Writing = worker_processes × worker_connections — всё занято! # accepts == handled — нет dropped connections. Если differs — проблема! Анализ логов# Медленные запросы (> 1 секунды) awk '$NF > 1' /var/log/nginx/access.log | \ awk '{print $NF, $7}' | sort -rn | head -20 # HIT rate кэша grep -o 'cs=[A-Z]*' /var/log/nginx/access.log | \ sort | uniq -c | sort -rn # 94521 cs=HIT ← 87% HIT rate — хорошо! # 12305 cs=MISS # 1823 cs=BYPASS # 289 cs=EXPIRED # Ошибки upstream grep 'upstream timed out\|connect() failed\|upstream prematurely' \ /var/log/nginx/error.log | tail -50 # Топ 5xx ошибок awk '$9 ~ /^5/' /var/log/nginx/access.log | \ awk '{print $9, $7}' | sort | uniq -c | sort -rn | head -20 Чеклист тюнинга: финальная проверка#!/bin/bash # Быстрая проверка ключевых параметров echo "=== NGINX PERFORMANCE CHECKLIST ===" # 1. Worker processes WP=$(nginx -T 2>/dev/null | grep 'worker_processes' | tail -1 | awk '{print $2}') CORES=$(nproc) echo "Worker processes: $WP (cores: $CORES)" # 2. Worker connections WC=$(nginx -T 2>/dev/null | grep 'worker_connections' | tail -1 | awk '{print $2}') echo "Worker connections: $WC" # 3. File descriptors FD=$(cat /proc/$(cat /var/run/nginx.pid 2>/dev/null || echo 1)/limits \ 2>/dev/null | grep 'open files' | awk '{print $4}') echo "File descriptors limit: $FD" # 4. sendfile SF=$(nginx -T 2>/dev/null | grep -E '^\s+sendfile' | tail -1) echo "Sendfile: $SF" # 5. gzip GZ=$(nginx -T 2>/dev/null | grep -E '^\s+gzip ' | tail -1) echo "Gzip: $GZ" # 6. SSL session cache SSL=$(nginx -T 2>/dev/null | grep 'ssl_session_cache' | tail -1) echo "SSL session cache: $SSL" # 7. BBR echo "TCP congestion: $(sysctl -n net.ipv4.tcp_congestion_control)" # 8. Тест конфигурации nginx -t && echo "Config: OK" || echo "Config: ERROR!" echo "" echo "=== ТЕКУЩАЯ НАГРУЗКА ===" curl -s http://127.0.0.1:8080/nginx_status 2>/dev/null || echo "stub_status недоступен" Типичные ошибки и мифыМиф 1: "worker_processes 4096 увеличит производительность" Нет. Оптимум — по одному воркеру на ядро. Больше воркеров = больше переключений контекста = хуже. Миф 2: "worker_connections 65535 — максимум соединений" Нет. Это максимум на один воркер. Итого: worker_processes × worker_connections. При 4 воркерах и 10240 соединениях = 40960 одновременных соединений. Миф 3: "keepalive_timeout 0 ускорит сервер" Наоборот. Keepalive экономит TLS handshake и TCP установку соединения. Отключение keepalive нагрузит сервер больше. Миф 4: "gzip_comp_level 9 — лучше" Нет. Разница в размере между уровнями 6 и 9 — 1-3%. Разница в CPU — в 3-5 раз. Используйте gzip_comp_level 6. Ошибка: proxy_cache без proxy_cache_use_stale При недоступности upstream без use_stale клиенты получат 502. С use_stale error timeout — получат устаревший кэш пока upstream восстанавливается. Всегда включайте! Ошибка: не настроен upstream keepalive Без keepalive в блоке upstream каждый запрос создаёт новое TCP соединение к backend. При 1000 RPS — 1000 новых TCP handshake в секунду. С keepalive 64 — переиспользуются 64 соединения. ЗаключениеТюнинг Nginx — это итеративный процесс. Хороший порядок: Системный уровень: sysctl, ulimit, BBR — без этого упрётесь в ОС Workers и events: worker_processes auto, multi_accept on, epoll Буферы и таймауты: адаптируйте под характер трафика (размер ответов, скорость клиентов) Кэш: proxy_cache или fastcgi_cache — самый большой прирост производительности Upstream keepalive: критично для высоких RPS SSL оптимизация: session cache + stapling + HTTP/2 Сжатие: gzip + brotli_static для предкомпрессии Rate limiting: защита без ущерба для легитимного трафика Измеряйте до и после каждого изменения. Доверяйте цифрам, а не интуиции. И помните: лучший тюнинг — тот, который решает вашу конкретную проблему, а не скопированный из статьи.
  13. Введение: PostgreSQL в продакшене — другой зверьПоднять PostgreSQL локально — просто. Запустить его в продакшене под реальной нагрузкой так, чтобы он не падал, не тормозил и не раздувался до потери диска — это уже инженерия. PostgreSQL 16, 17 и 18 принесли серьёзные улучшения производительности: логическая репликация стала намного мощнее, параллельные запросы умнее, планировщик научился большему. Но дефолтная конфигурация по-прежнему рассчитана на «запустить на ноутбуке с 256 МБ RAM», а не на production-сервер с 128 ГБ памяти. Эта статья — системный разбор всего, что нужно сделать, чтобы PostgreSQL работал быстро, надёжно и предсказуемо. Никакой воды: только параметры, SQL, реальные кейсы. Глава 1. Конфигурация: postgresql.conf с нуляПамять: самые важные параметры# postgresql.conf # shared_buffers — основной кэш PostgreSQL в памяти. # Правило: 25-40% от RAM сервера. # На 64 ГБ RAM: 16 ГБ shared_buffers = 16GB # effective_cache_size — подсказка планировщику, сколько памяти # доступно для кэширования (shared_buffers + OS page cache). # Правило: 50-75% от RAM. # На 64 ГБ RAM: 48 ГБ effective_cache_size = 48GB # work_mem — память для одной операции сортировки/хэширования # ВНИМАНИЕ: умножается на число параллельных запросов × число операций в плане! # На сервере с 500 соединениями и work_mem=256MB → потенциально 128 ГБ! # Разумно: 4-64 МБ для OLTP, 256 МБ-1 ГБ для аналитики work_mem = 32MB # maintenance_work_mem — для VACUUM, CREATE INDEX, ALTER TABLE # Больше = быстрее индексы и вакуум. Безопасно давать больше, чем work_mem. maintenance_work_mem = 2GB # huge_pages — используем hugepages Linux для shared_buffers # Обязательно для shared_buffers > 8 ГБ huge_pages = on WAL и checkpoint: баланс между скоростью и надёжностью# wal_level — минимальный уровень для репликации # replica — для физической репликации # logical — для логической репликации (больше overhead) wal_level = replica # Размер WAL буфера (с PostgreSQL 16 wal_buffers=auto работает хорошо) wal_buffers = 64MB # checkpoint_completion_target — размазываем запись checkpoint во времени # 0.9 означает: записать грязные страницы за 90% интервала между checkpoint checkpoint_completion_target = 0.9 # max_wal_size — максимальный объём WAL между checkpoint # При большой нагрузке на запись увеличьте до 4-16 ГБ # Это НЕ размер хранилища WAL, а порог для инициации checkpoint max_wal_size = 4GB # min_wal_size — минимальный резерв WAL файлов min_wal_size = 1GB # wal_compression — сжатие WAL (PostgreSQL 15+: поддержка lz4, zstd) # Снижает I/O, небольшой CPU overhead wal_compression = lz4 Параллелизм (PostgreSQL 16+)# max_worker_processes — общий пул фоновых процессов max_worker_processes = 16 # max_parallel_workers_per_gather — параллельные воркеры на один запрос # Правило: не более числа физических ядер / 2 max_parallel_workers_per_gather = 4 # max_parallel_workers — суммарно параллельных воркеров max_parallel_workers = 8 # max_parallel_maintenance_workers — для CREATE INDEX CONCURRENTLY, VACUUM max_parallel_maintenance_workers = 4 # parallel_tuple_cost, parallel_setup_cost — влияют на решение планировщика # использовать параллельность. Снизить если параллельные планы не строятся. parallel_tuple_cost = 0.1 parallel_setup_cost = 100 Соединения# max_connections — ОСТОРОЖНО! Каждое соединение ≈ 5-10 МБ памяти. # При PgBouncer: достаточно 100-200 серверных соединений. # Без пула: реальное число ≤ 200-300 max_connections = 200 # superuser_reserved_connections — резерв для DBA superuser_reserved_connections = 5 Планировщик: тонкая настройка# random_page_cost — стоимость случайного чтения страницы. # Для SSD: 1.1-1.5 (против дефолта 4.0 для HDD). # Занижение → планировщик чаще выбирает Index Scan. random_page_cost = 1.1 # seq_page_cost — стоимость последовательного чтения (база = 1.0) seq_page_cost = 1.0 # effective_io_concurrency — параллельных I/O для Bitmap Heap Scan # SSD: 200-300, HDD: 2-4, NVMe: 500+ effective_io_concurrency = 200 # default_statistics_target — точность статистики для планировщика # Дефолт 100. Для колонок с высокой кардинальностью — до 500. default_statistics_target = 200 # enable_partitionwise_join — важно для партиционированных таблиц enable_partitionwise_join = on enable_partitionwise_aggregate = on Глава 2. Индексная стратегияПравильные индексы — половина успеха. Неправильные — гарантированный bloat и тормоза на INSERT/UPDATE. Типы индексов: когда что использоватьB-Tree — дефолт, для равенства и диапазонов: -- Стандартный случай CREATE INDEX idx_orders_user_id ON orders(user_id); -- Частичный индекс — только активные записи -- Занимает меньше места, быстрее обновляется CREATE INDEX idx_orders_active ON orders(created_at) WHERE status = 'active'; -- Покрывающий индекс (INCLUDE) — избегаем обращения к таблице -- PostgreSQL 11+, активно улучшен в 16/17 CREATE INDEX idx_orders_cover ON orders(user_id) INCLUDE (total_amount, status, created_at); -- Составной: порядок имеет значение! -- Ставьте впереди колонки с высокой кардинальностью -- и те, по которым фильтрация точнее CREATE INDEX idx_orders_composite ON orders(user_id, status, created_at); GIN — для массивов, JSONB, полнотекстового поиска: -- JSONB поиск CREATE INDEX idx_products_attrs ON products USING GIN(attributes); -- Полнотекстовый поиск CREATE INDEX idx_articles_fts ON articles USING GIN(to_tsvector('russian', title || ' ' || body)); -- Поиск в массивах CREATE INDEX idx_tags ON posts USING GIN(tags); BRIN — для очень больших таблиц с естественной сортировкой: -- Для таблиц логов, временных рядов — экономия места 99%+ -- BRIN не хранит каждое значение, только мин/макс по блокам CREATE INDEX idx_events_time_brin ON events USING BRIN(created_at) WITH (pages_per_range = 128); -- PostgreSQL 14+: bloom filter в BRIN CREATE INDEX idx_events_bloom ON events USING BRIN(device_id, created_at) WITH (pages_per_range = 64); Hash — только для точного равенства, быстрее B-Tree: CREATE INDEX idx_sessions_token ON sessions USING HASH(session_token); Найти неиспользуемые и дублирующие индексы-- Неиспользуемые индексы (кандидаты на удаление) SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(indexrelid)) AS index_size, idx_scan, idx_tup_read, idx_tup_fetch FROM pg_stat_user_indexes WHERE idx_scan = 0 AND indexrelname NOT LIKE 'pg_%' ORDER BY pg_relation_size(indexrelid) DESC; -- Дублирующие индексы SELECT indrelid::regclass AS table_name, array_agg(indexrelid::regclass) AS indexes, array_agg(indkey) AS index_keys FROM pg_index GROUP BY indrelid, indkey HAVING count(*) > 1; -- Индексы vs размер таблицы: раздутые индексы SELECT t.tablename, pg_size_pretty(pg_total_relation_size(t.tablename::regclass)) AS total, pg_size_pretty(pg_relation_size(t.tablename::regclass)) AS table_size, pg_size_pretty( pg_total_relation_size(t.tablename::regclass) - pg_relation_size(t.tablename::regclass) ) AS indexes_size, round( (pg_total_relation_size(t.tablename::regclass) - pg_relation_size(t.tablename::regclass))::numeric / nullif(pg_total_relation_size(t.tablename::regclass), 0) * 100, 1 ) AS index_ratio_pct FROM pg_tables t WHERE t.schemaname = 'public' ORDER BY pg_total_relation_size(t.tablename::regclass) DESC LIMIT 30; Глава 3. EXPLAIN ANALYZE: читаем план запроса как профессионалEXPLAIN ANALYZE — главный инструмент оптимизации. Без него — гадание на кофейной гуще. -- Всегда используйте все опции EXPLAIN ( ANALYZE, -- Реально выполнить и показать время BUFFERS, -- Показать попадания/промахи кэша FORMAT TEXT, -- или JSON для авто-анализа TIMING ON, -- Время каждого узла SETTINGS ON, -- Показать изменённые параметры WAL ON -- PostgreSQL 13+: WAL активность ) SELECT ...; Анатомия плана: на что смотретьEXPLAIN (ANALYZE, BUFFERS) SELECT o.id, o.total, u.email FROM orders o JOIN users u ON u.id = o.user_id WHERE o.created_at > NOW() - INTERVAL '7 days' AND o.status = 'completed'; -- Типичный вывод: -- QUERY PLAN -- Hash Join (cost=1250.00..8934.21 rows=1523 width=48) (actual time=45.231..189.443 rows=1287 loops=1) -- Buffers: shared hit=4521 read=2341 ← read > 0 = данных нет в кэше -- Hash Cond: (o.user_id = u.id) -- -> Bitmap Heap Scan on orders o (cost=87.3..7512.4 rows=1523 width=32) -- (actual time=2.341..145.231 rows=1287 loops=1) -- Recheck Cond: (created_at > (now() - '7 days'::interval)) -- Filter: (status = 'completed') -- Rows Removed by Filter: 4521 ← КРАСНЫЙ ФЛАГ: фильтруем 4521 строк! -- Heap Blocks: exact=1823 -- Buffers: shared hit=123 read=1823 -- -> Bitmap Index Scan on idx_orders_created_at -- Index Cond: (created_at > (now() - '7 days'::interval)) -- -> Hash (cost=890.00..890.00 rows=21000 width=24) (actual time=42.3..42.3 rows=21000 loops=1) -- Buckets: 32768 Batches: 1 Memory Usage: 1856kB -- Buffers: shared hit=4398 read=518 -- -> Seq Scan on users u (cost=0.00..890.00 rows=21000 width=24) -- Planning Time: 1.234 ms -- Execution Time: 190.123 ms ← Реальное время! Красные флаги в плане: Признак Проблема Решение Rows Removed by Filter >> возвращаемых строк Индекс не покрывает все условия Добавить колонку status в индекс actual rows >> estimated rows (×10+) Устаревшая статистика ANALYZE table или повысить default_statistics_target Seq Scan на большой таблице Нет подходящего индекса Создать индекс Batches: N (N > 1) в Hash Join Хэш-таблица не помещается в work_mem Увеличить work_mem или оптимизировать запрос loops=N при N×cost = огромно Вложенный цикл на большом наборе Рассмотреть Hash Join / Merge Join shared read >> shared hit Данные не в кэше Увеличить shared_buffers или прогреть кэш Автоматический поиск медленных запросов-- pg_stat_statements: топ-20 самых дорогих запросов -- Требует: shared_preload_libraries = 'pg_stat_statements' -- postgresql.conf: pg_stat_statements.track = all SELECT round(total_exec_time::numeric, 2) AS total_ms, calls, round(mean_exec_time::numeric, 2) AS mean_ms, round(stddev_exec_time::numeric, 2) AS stddev_ms, round((total_exec_time / sum(total_exec_time) OVER () * 100)::numeric, 2) AS pct_total, round(rows::numeric / calls, 1) AS avg_rows, -- Соотношение кэш-попаданий round( 100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0), 2 ) AS cache_hit_pct, -- Нормализованный текст запроса (без значений параметров) left(query, 100) AS query_snippet FROM pg_stat_statements WHERE calls > 10 ORDER BY total_exec_time DESC LIMIT 20; -- Запросы с самым высоким среднем временем (не суммарным!) SELECT calls, round(mean_exec_time::numeric, 2) AS mean_ms, round(max_exec_time::numeric, 2) AS max_ms, round(stddev_exec_time::numeric, 2) AS stddev_ms, left(query, 120) AS query FROM pg_stat_statements WHERE calls > 5 AND mean_exec_time > 100 -- Больше 100 мс в среднем ORDER BY mean_exec_time DESC LIMIT 20; -- Запросы с плохим cache hit ratio (много disk reads) SELECT calls, round(mean_exec_time::numeric, 2) AS mean_ms, shared_blks_read, shared_blks_hit, round(100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0), 2) AS cache_hit_pct, left(query, 120) AS query FROM pg_stat_statements WHERE calls > 10 AND (shared_blks_hit + shared_blks_read) > 0 AND shared_blks_read > shared_blks_hit -- Больше промахов чем попаданий ORDER BY shared_blks_read DESC LIMIT 20; Глава 4. Autovacuum: настройка, а не молитваAutovacuum — не враг, а друг. Но дефолтные настройки рассчитаны на небольшие таблицы. На больших таблицах он либо не успевает, либо тормозит рабочую нагрузку. Понять текущее состояние vacuum-- Таблицы с наибольшим dead tuple bloat SELECT schemaname, relname AS tablename, n_live_tup, n_dead_tup, round(n_dead_tup::numeric / nullif(n_live_tup + n_dead_tup, 0) * 100, 2) AS dead_pct, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze, autovacuum_count, pg_size_pretty(pg_total_relation_size(schemaname||'.'||relname)) AS total_size FROM pg_stat_user_tables WHERE n_dead_tup > 1000 ORDER BY n_dead_tup DESC LIMIT 20; -- Таблицы, которым скоро нужен vacuum (по счётчику транзакций) -- age() показывает сколько транзакций прошло с последнего freeze SELECT schemaname, relname, pg_size_pretty(pg_total_relation_size(oid)) AS size, age(relfrozenxid) AS xid_age, round(age(relfrozenxid)::numeric / 2000000000 * 100, 2) AS freeze_pct, -- Когда автовакуум сделает freeze (по умолчанию при 150M транзакций) (200000000 - age(relfrozenxid)) AS txids_until_freeze FROM pg_class JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace WHERE relkind = 'r' AND nspname NOT IN ('pg_catalog', 'information_schema') ORDER BY age(relfrozenxid) DESC LIMIT 20; -- Текущие процессы autovacuum SELECT pid, now() - xact_start AS duration, query, state, wait_event_type, wait_event FROM pg_stat_activity WHERE query LIKE 'autovacuum:%' ORDER BY duration DESC; Оптимальная настройка autovacuum# postgresql.conf — глобальные настройки autovacuum # Число процессов autovacuum autovacuum_max_workers = 6 # Дефолт 3; на активном сервере — 4-8 # Стоимостной лимит для autovacuum (throttling) # Дефолт 200 — очень агрессивное ограничение скорости. # На SSD можно поднять до 800-2000. autovacuum_vacuum_cost_limit = 800 # Задержка между "порциями" vacuum (cooldown) # При cost_limit=800 и delay=2ms → ~400 МБ/с максимальная скорость vacuum autovacuum_vacuum_cost_delay = 2ms # Порог запуска VACUUM: n_dead_tup > autovacuum_vacuum_threshold + n_live_tup * scale_factor autovacuum_vacuum_threshold = 50 autovacuum_vacuum_scale_factor = 0.02 # 2% от таблицы (дефолт 20%) # Порог запуска ANALYZE autovacuum_analyze_threshold = 50 autovacuum_analyze_scale_factor = 0.01 # 1% (дефолт 20%) # Для больших таблиц scale_factor делает vacuum очень редким: # Таблица 100M строк × 0.02 = 2M dead tuples до запуска vacuum — МНОГО Настройка per-table (лучше глобальных для горячих таблиц):-- Для высокоактивных таблиц: vacuum чаще, агрессивнее ALTER TABLE orders SET ( autovacuum_vacuum_scale_factor = 0.005, -- Запуск при 0.5% dead tuples autovacuum_analyze_scale_factor = 0.002, -- Analyze при 0.2% autovacuum_vacuum_cost_limit = 1600, -- Более высокий лимит I/O autovacuum_vacuum_cost_delay = 1 -- Меньше пауз ); -- Для append-only таблиц (логи, временные ряды): -- Vacuum не нужен часто, но freeze — важен ALTER TABLE event_log SET ( autovacuum_vacuum_scale_factor = 0.2, -- Редкий vacuum (мало UPDATE/DELETE) autovacuum_freeze_max_age = 500000000, -- Freeze через 500M транзакций autovacuum_vacuum_cost_limit = 2000 -- Быстрый когда запустился ); -- Проверить что настройки применились: SELECT relname, reloptions FROM pg_class WHERE relname IN ('orders', 'event_log'); Обнаружение table bloat (раздутых таблиц)-- Скрипт оценки bloat (не требует сторонних расширений) WITH constants AS ( SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 8 AS ma ), columns_per_table AS ( SELECT att.attrelid, count(*) AS cols, -- Байт nullmap на строку (count(*) + 7) / 8 AS nullhdr FROM pg_attribute att WHERE att.attnum > 0 AND NOT att.attisdropped GROUP BY 1 ), rows_estimate AS ( SELECT c.oid, CASE WHEN c.reltuples < 0 THEN 0 ELSE c.reltuples END AS est_rows, c.relpages, c.relname, n.nspname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema') ) SELECT re.nspname || '.' || re.relname AS table_name, re.est_rows, re.relpages AS current_pages, pg_size_pretty(re.relpages * 8192) AS current_size, -- Оценочный реальный размер pg_size_pretty( ceil(re.est_rows * 30 / 8192.0)::bigint * 8192 ) AS estimated_real_size, round( 100.0 * (re.relpages - ceil(re.est_rows * 30 / 8192.0)) / nullif(re.relpages, 0), 1 ) AS bloat_pct FROM rows_estimate re WHERE re.relpages > 100 ORDER BY (re.relpages - ceil(re.est_rows * 30 / 8192.0)) DESC LIMIT 20; -- Для точного bloat используйте расширение pgstattuple: -- CREATE EXTENSION pgstattuple; SELECT * FROM pgstattuple('orders'); -- Поля: table_len, live_tuple_count, dead_tuple_count, dead_tuple_percent, free_space Глава 5. Connection Pooling с PgBouncerКаждое соединение с PostgreSQL — это отдельный процесс (~5 МБ памяти + overhead планировщика). 1000 соединений = 5 ГБ памяти только на процессы. PgBouncer решает эту проблему. Режимы PgBouncerРежим Как работает Подходит для Ограничения session 1 клиент = 1 серверное соединение на всю сессию Совместимость Нет экономии transaction Серверное соединение занято только на время транзакции OLTP, большинство приложений SET, LISTEN, prepared statements statement Одно серверное соединение на один SQL-оператор Агрессивная экономия Нет транзакций! Конфигурация PgBouncer# /etc/pgbouncer/pgbouncer.ini [databases] # Синтаксис: alias = host=... dbname=... port=... user=... myapp = host=127.0.0.1 port=5432 dbname=myapp_db # Для чтения — отдельный пул на реплику myapp_ro = host=replica.internal port=5432 dbname=myapp_db [pgbouncer] # Режим пула pool_mode = transaction # Адрес и порт PgBouncer listen_addr = 0.0.0.0 listen_port = 5432 # Максимум клиентских соединений (к PgBouncer) max_client_conn = 2000 # Размер серверного пула на базу (к PostgreSQL) # PostgreSQL: max_connections = 200 # PgBouncer: default_pool_size = 80 (на каждую базу) default_pool_size = 80 # Минимальный пул (держим готовые соединения) min_pool_size = 10 # Резерв для суперпользователя (аналог reserved_connections) reserve_pool_size = 5 reserve_pool_timeout = 3 # Таймауты server_idle_timeout = 600 # Закрыть серверное соединение через 10 мин idle client_idle_timeout = 0 # Не закрывать клиентские (0 = infinite) server_connect_timeout = 5 # Таймаут подключения к PostgreSQL query_timeout = 0 # 0 = нет лимита на запрос (лучше ставить в app) query_wait_timeout = 120 # Ждать свободного соединения до 120 с # Проверка соединений server_check_query = select 1 server_check_delay = 30 # Аутентификация (scram-sha-256 — стандарт PG 14+) auth_type = scram-sha-256 auth_file = /etc/pgbouncer/userlist.txt # Логирование (не слишком подробное — влияет на производительность) log_connections = 0 log_disconnections = 0 log_pooler_errors = 1 # Admin интерфейс admin_users = pgbouncer_admin stats_users = monitoring_user # Производительность tcp_keepalive = 1 tcp_keepidle = 60 tcp_keepintvl = 10 tcp_keepcnt = 5 Мониторинг PgBouncer-- Подключиться к admin БД PgBouncer: -- psql -h localhost -p 5432 -U pgbouncer_admin pgbouncer -- Состояние пулов SHOW POOLS; -- cl_active — клиентов с активным серверным соединением -- cl_waiting — клиентов в очереди (ждут свободного соединения!) -- sv_active — серверных соединений в работе -- sv_idle — серверных соединений в ожидании (пул) -- sv_used — только что освобождённые (не проверены ещё) -- maxwait — максимальное время ожидания клиента (критический параметр!) -- Статистика SHOW STATS; -- total_query_time — суммарное время выполнения запросов -- avg_query_time — среднее время запроса -- total_wait_time — суммарное время ожидания в очереди -- Список клиентов SHOW CLIENTS; -- Перезагрузить конфиг без перезапуска RELOAD; -- Сбросить статистику RESET STATS; Интеграция с PostgreSQL 17: встроенный connection shardPostgreSQL 17 улучшил max_connections по производительности и добавил механизм connection_obeys_lc_messages — мелочь, но полезная. Работа над встроенным пулингом (connection pooling) ведётся активно, следите за PostgreSQL 18. Глава 6. Партиционирование: когда таблица растёт до сотен ГБПартиционирование делит одну логическую таблицу на несколько физических. PostgreSQL 16/17 значительно улучшили работу с партициями: умный pruning, параллельные операции, partition-wise joins. RANGE партиционирование (самое частое — по дате)-- Создание партиционированной таблицы CREATE TABLE events ( id BIGSERIAL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), device_id INT NOT NULL, event_type TEXT NOT NULL, payload JSONB, PRIMARY KEY (id, created_at) -- created_at обязательна в PK для партиций! ) PARTITION BY RANGE (created_at); -- Создание партиций (вручную или автоматически) CREATE TABLE events_2024_01 PARTITION OF events FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); CREATE TABLE events_2024_02 PARTITION OF events FOR VALUES FROM ('2024-02-01') TO ('2024-03-01'); -- DEFAULT партиция для данных вне диапазона CREATE TABLE events_default PARTITION OF events DEFAULT; -- Индексы создаются на каждой партиции отдельно -- (или глобально через CREATE INDEX на родительской — PG 11+) CREATE INDEX idx_events_device ON events(device_id, created_at); -- Автоматически создаст индекс на каждой партиции! Автоматическое создание партиций (pg_partman)Ручное создание партиций — путь к ошибкам. Используйте pg_partman: -- Установка pg_partman -- Добавить в postgresql.conf: shared_preload_libraries = 'pg_partman_bgw' -- Настройка автоматического управления партициями SELECT partman.create_parent( p_parent_table => 'public.events', p_control => 'created_at', p_interval => 'monthly', -- или 'weekly', 'daily', 'yearly' p_premake => 3, -- Создавать 3 будущих партиции заранее p_start_partition => '2024-01-01' ); -- Настройка retention (удаление старых партиций) UPDATE partman.part_config SET retention = '12 months', -- Хранить 12 месяцев retention_keep_table = false, -- Удалять партицию физически retention_keep_index = false -- Удалять и индексы WHERE parent_table = 'public.events'; -- Запуск обслуживания вручную (обычно pg_partman_bgw делает это сам) CALL partman.run_maintenance_proc(); Partition Pruning: проверяем что планировщик умный-- Планировщик должен сканировать только нужные партиции EXPLAIN SELECT count(*) FROM events WHERE created_at BETWEEN '2024-03-01' AND '2024-03-31'; -- Ищем в плане: "Partitions: events_2024_03" -- НЕ должно быть: "Append (всех партиций)" -- Если pruning не работает — проверьте что условие на колонку партиционирования -- и её тип совпадают (нет неявных каст) -- Partition pruning во время выполнения (runtime pruning, PG 11+) -- Работает даже для параметров ($1, $2) если enable_partition_pruning = on SET enable_partition_pruning = on; -- Дефолт on в PG 16+ LIST партиционирование (по типу/региону)CREATE TABLE orders ( id BIGSERIAL, region TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), total DECIMAL(10,2) ) PARTITION BY LIST (region); CREATE TABLE orders_eu PARTITION OF orders FOR VALUES IN ('DE', 'FR', 'NL', 'PL'); CREATE TABLE orders_us PARTITION OF orders FOR VALUES IN ('US', 'CA', 'MX'); CREATE TABLE orders_asia PARTITION OF orders FOR VALUES IN ('CN', 'JP', 'KR', 'IN'); CREATE TABLE orders_rest PARTITION OF orders DEFAULT; HASH партиционирование (равномерное распределение)-- Для таблиц без естественного ключа партиционирования -- Гарантирует примерно равный размер партиций CREATE TABLE sessions ( id UUID DEFAULT gen_random_uuid(), user_id INT NOT NULL, data JSONB ) PARTITION BY HASH (user_id); -- Создаём N партиций (степень двойки — хорошая практика) CREATE TABLE sessions_0 PARTITION OF sessions FOR VALUES WITH (MODULUS 8, REMAINDER 0); CREATE TABLE sessions_1 PARTITION OF sessions FOR VALUES WITH (MODULUS 8, REMAINDER 1); -- ... и т.д. до sessions_7 Глава 7. Репликация: PostgreSQL 16/17/18Физическая репликация (Streaming Replication)# На Primary: postgresql.conf wal_level = replica max_wal_senders = 10 # Максимум одновременных реплик wal_keep_size = 1GB # Буфер WAL для реплик (PG 13+, заменил wal_keep_segments) hot_standby = on # Разрешить запросы на реплике hot_standby_feedback = on # Реплика сообщает Primary о своих транзакциях # pg_hba.conf на Primary — разрешаем репликацию с адреса реплики: # host replication replicator 10.0.0.2/32 scram-sha-256 # На Standby: создание базовой копии pg_basebackup \ -h primary.host \ -U replicator \ -D /var/lib/postgresql/17/main \ -P \ --wal-method=stream \ --checkpoint=fast \ --write-recovery-conf # Создаёт standby.signal и postgresql.auto.conf # postgresql.auto.conf на Standby (создаётся pg_basebackup): # primary_conninfo = 'host=primary.host port=5432 user=replicator password=...' -- Мониторинг репликации на Primary: SELECT client_addr, usename, application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, -- Лаг в байтах pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes, -- Лаг во времени (PG 10+) write_lag, flush_lag, replay_lag, sync_state FROM pg_stat_replication ORDER BY replay_lag DESC; -- На Standby — проверка своего лага: SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag, pg_is_in_recovery() AS is_replica, pg_last_wal_receive_lsn() AS received_lsn, pg_last_wal_replay_lsn() AS replayed_lsn; Логическая репликация (PostgreSQL 16/17: серьёзно улучшена)Логическая репликация в PostgreSQL 16 получила: Двунаправленная (bidirectional) репликация — обе стороны могут принимать запись Streaming больших транзакций в реальном времени (без ожидания COMMIT) Параллельное применение изменений на подписчике -- На Publisher (источник): -- postgresql.conf: wal_level = logical -- Создание публикации CREATE PUBLICATION my_pub FOR TABLE orders, users, products WITH (publish = 'insert, update, delete', publish_via_partition_root = true); -- Для всех таблиц: CREATE PUBLICATION all_tables FOR ALL TABLES; -- На Subscriber (назначение): -- Создание подписки CREATE SUBSCRIPTION my_sub CONNECTION 'host=primary.host port=5432 dbname=mydb user=replicator password=secret' PUBLICATION my_pub WITH ( connect = true, slot_name = 'my_sub_slot', synchronous_commit = 'off', -- Более быстрая репликация streaming = on -- PG 14+: stream больших транзакций ); -- Мониторинг логической репликации на Publisher: SELECT slot_name, plugin, slot_type, database, active, active_pid, -- КРИТИЧНО: wal_status = 'lost' означает что слот отстал и WAL удалён wal_status, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS subscriber_lag FROM pg_replication_slots; -- ОПАСНОСТЬ: неактивный logical slot держит WAL! Диск кончится. -- Если слот не используется > 24ч — проверить и при необходимости удалить: -- SELECT pg_drop_replication_slot('my_sub_slot'); Глава 8. Мониторинг: что смотреть в продакшенеСистемные вьюшки — источник правды-- ===== АКТИВНЫЕ ЗАПРОСЫ И БЛОКИРОВКИ ===== -- Запросы дольше 30 секунд — потенциальные проблемы SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state, wait_event_type, wait_event, client_addr, usename, application_name FROM pg_stat_activity WHERE (now() - pg_stat_activity.query_start) > INTERVAL '30 seconds' AND state != 'idle' ORDER BY duration DESC; -- Граф блокировок: кто кого блокирует WITH RECURSIVE lock_graph AS ( -- Базовый случай: запросы, ожидающие блокировку SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query, blocked.query_start AS blocked_start, blocker.pid AS blocker_pid, blocker.query AS blocker_query, 0 AS depth FROM pg_stat_activity blocked JOIN pg_stat_activity blocker ON blocker.pid = ANY(pg_blocking_pids(blocked.pid)) WHERE blocked.wait_event_type = 'Lock' UNION ALL -- Рекурсивный случай: цепочки блокировок SELECT lg.blocked_pid, lg.blocked_query, lg.blocked_start, blocker.pid, blocker.query, lg.depth + 1 FROM lock_graph lg JOIN pg_stat_activity blocker ON blocker.pid = ANY(pg_blocking_pids(lg.blocker_pid)) WHERE lg.depth < 10 ) SELECT blocked_pid, left(blocked_query, 80) AS blocked_query, now() - blocked_start AS wait_duration, blocker_pid, left(blocker_query, 80) AS blocker_query, depth FROM lock_graph ORDER BY wait_duration DESC; -- Если нужно убить зависший запрос (мягко): -- SELECT pg_cancel_backend(pid); -- Отмена запроса, транзакция откатывается -- Если не реагирует (жёстко): -- SELECT pg_terminate_backend(pid); -- Завершение процесса -- ===== КЭШ И I/O ===== -- Cache hit ratio (цель: > 99%) SELECT sum(heap_blks_hit) AS heap_hit, sum(heap_blks_read) AS heap_read, round( sum(heap_blks_hit)::numeric / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) * 100, 3 ) AS cache_hit_ratio FROM pg_statio_user_tables; -- По каждой таблице: SELECT relname AS table_name, heap_blks_hit, heap_blks_read, round(heap_blks_hit::numeric / nullif(heap_blks_hit + heap_blks_read, 0) * 100, 2) AS cache_hit_pct, idx_blks_hit, idx_blks_read, round(idx_blks_hit::numeric / nullif(idx_blks_hit + idx_blks_read, 0) * 100, 2) AS idx_cache_hit_pct FROM pg_statio_user_tables WHERE heap_blks_read + heap_blks_hit > 0 ORDER BY heap_blks_read DESC LIMIT 20; -- ===== CHECKPOINT СТАТИСТИКА ===== SELECT checkpoints_timed, checkpoints_req, -- Если часто req >> timed: увеличить max_wal_size checkpoint_write_time / 1000 AS write_sec, checkpoint_sync_time / 1000 AS sync_sec, buffers_checkpoint, buffers_clean, buffers_backend, -- Если много: bgwriter не успевает → тюнинг bgwriter buffers_backend_fsync, -- НЕ ноль = ПРОБЛЕМА: backend делает fsync сам buffers_alloc FROM pg_stat_bgwriter; -- Если buffers_backend > 0 — увеличить bgwriter_lru_maxpages: -- bgwriter_lru_maxpages = 200 (дефолт 100) -- bgwriter_lru_multiplier = 4.0 (дефолт 2.0) -- bgwriter_delay = 50ms (дефолт 200ms) Скрипт ежедневного health-check-- Сохранить как daily_healthcheck.sql и запускать через cron \echo '=== PostgreSQL Daily Health Check ===' \echo '' \echo '--- Database Sizes ---' SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size FROM pg_database WHERE datname NOT IN ('postgres', 'template0', 'template1') ORDER BY pg_database_size(datname) DESC; \echo '' \echo '--- Top 10 Largest Tables ---' SELECT schemaname || '.' || tablename AS table, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC LIMIT 10; \echo '' \echo '--- Tables with High Dead Tuple Ratio (> 10%) ---' SELECT relname, n_live_tup, n_dead_tup, round(n_dead_tup::numeric / nullif(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_pct, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup::numeric / nullif(n_live_tup + n_dead_tup, 0) > 0.1 AND n_live_tup > 1000 ORDER BY dead_pct DESC; \echo '' \echo '--- Replication Lag ---' SELECT application_name, replay_lag, sync_state FROM pg_stat_replication; \echo '' \echo '--- Long-Running Transactions (> 1 hour) ---' SELECT pid, usename, now() - xact_start AS duration, left(query, 100) AS query FROM pg_stat_activity WHERE xact_start IS NOT NULL AND now() - xact_start > INTERVAL '1 hour' AND pid != pg_backend_pid() ORDER BY duration DESC; \echo '' \echo '--- Unused Indexes (0 scans) ---' SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(indexrelid)) AS size FROM pg_stat_user_indexes WHERE idx_scan = 0 AND pg_relation_size(indexrelid) > 10 * 1024 * 1024 -- > 10 МБ ORDER BY pg_relation_size(indexrelid) DESC LIMIT 10; Глава 9. Новинки PostgreSQL 16/17/18PostgreSQL 16 (2023)Логическая репликация от standby — теперь можно публиковать изменения не только с primary, разгружая мастер. Параллельный COPY — загрузка данных через COPY стала параллельной. Улучшения планировщика для GROUP BY с параллелизмом. pg_stat_io — новая системная вьюшка для детальной статистики I/O: -- PostgreSQL 16+: детальная I/O статистика SELECT backend_type, object, context, reads, writes, extends, op_bytes, hits, evictions, reuses, fsyncs, read_time, write_time FROM pg_stat_io ORDER BY reads + writes DESC; -- Особенно полезно: сравнить hits vs reads для разных backend_type COPY FROM ... WHERE — фильтрация при загрузке данных: -- Загружаем только нужные строки COPY orders FROM '/tmp/orders.csv' CSV HEADER WHERE status = 'completed' AND total > 100; PostgreSQL 17 (2024)MERGE стал намного мощнее — поддержка RETURNING, DO NOTHING: -- PostgreSQL 17: MERGE с RETURNING MERGE INTO inventory AS target USING incoming_stock AS source ON target.product_id = source.product_id WHEN MATCHED THEN UPDATE SET quantity = target.quantity + source.quantity WHEN NOT MATCHED THEN INSERT (product_id, quantity) VALUES (source.product_id, source.quantity) RETURNING target.product_id, target.quantity, merge_action(); -- merge_action() → 'INSERT' или 'UPDATE' Incremental sorting улучшен — быстрее для DISTINCT и ORDER BY. pg_stat_statements получил toplevel — разделение top-level vs вложенных запросов. Vacuum improvements — улучшена скорость заморозки (freeze), меньше I/O. VACUUM (SKIP_DATABASE_STATS) — ускорение вакуума многих мелких таблиц. Размер WAL записей уменьшен — меньше I/O при интенсивной записи. PostgreSQL 18 (2025, в разработке / ранние беты)Встроенный асинхронный I/O (io_method = io_uring) — огромный прирост для NVMe SSD, особенно при высоком параллелизме: # postgresql.conf (PostgreSQL 18 при использовании Linux io_uring) io_method = io_uring # Дефолт: sync; альтернатива: worker Планировщик с ML-hints — работа над улучшением кардинальности оценок. GRANT/REVOKE для роли по умолчанию — улучшена система безопасности. Глава 10. Практические кейсы: реальные проблемы и их решенияКейс 1: «Запросы стали медленнее после VACUUM»-- Симптом: autovacuum отработал, но запросы стали медленнее. -- Причина: устаревшая статистика. VACUUM не обновляет статистику! -- Решение 1: Принудительный ANALYZE ANALYZE VERBOSE orders; -- Или для всей БД: -- vacuumdb --analyze-only --all -- Решение 2: Увеличить точность статистики для проблемных колонок ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500, ALTER COLUMN region SET STATISTICS 500; ANALYZE orders; -- Проверить статистику после: SELECT attname, n_distinct, correlation FROM pg_stats WHERE tablename = 'orders' AND attname IN ('status', 'region', 'created_at'); Кейс 2: «Диск заполнился WAL файлами»-- Причина 1: Зависший логический слот репликации SELECT slot_name, active, wal_status, pg_size_pretty( pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) ) AS retained_wal FROM pg_replication_slots WHERE wal_status != 'reserved'; -- Если слот неактивен и держит WAL — удалить после согласования с командой: SELECT pg_drop_replication_slot('stale_slot_name'); -- Причина 2: archive_command не успевает -- Проверить: SELECT last_archived_wal, last_failed_wal, last_failed_time FROM pg_stat_archiver; -- Временная мера: уменьшить max_wal_size -- Постоянная: починить archive_command или увеличить место -- Причина 3: Слишком агрессивные checkpoint -- Уменьшить wal_keep_size если репликация живая Кейс 3: «Connection pool переполнен, приложение не может подключиться»-- Диагноз: смотрим pg_stat_activity SELECT state, count(*), left(query, 50) AS sample_query FROM pg_stat_activity WHERE datname = 'myapp_db' GROUP BY state, left(query, 50) ORDER BY count(*) DESC; -- Частая причина: idle in transaction (транзакция открыта и забыта) SELECT pid, now() - xact_start AS idle_duration, query FROM pg_stat_activity WHERE state = 'idle in transaction' AND now() - xact_start > INTERVAL '5 minutes' ORDER BY idle_duration DESC; -- Быстрое решение: убить зависшие idle in transaction SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND now() - xact_start > INTERVAL '10 minutes'; -- Постоянное решение: idle_in_transaction_session_timeout -- postgresql.conf: -- idle_in_transaction_session_timeout = 5min -- idle_session_timeout = 30min (PG 14+) Кейс 4: «Таблица растёт несмотря на DELETE»-- Table bloat: место от удалённых строк не возвращается OS. -- PostgreSQL помечает строки как "мёртвые", VACUUM освобождает их -- для ПОВТОРНОГО ИСПОЛЬЗОВАНИЯ, но не возвращает OS (кроме pg_toast). -- Проверить bloat: SELECT relname, pg_size_pretty(pg_total_relation_size(oid)) AS total_size, n_dead_tup, n_live_tup FROM pg_stat_user_tables JOIN pg_class USING (relid) WHERE relname = 'your_table'; -- Решение 1: VACUUM FULL (блокирует таблицу! Используйте в окно обслуживания) VACUUM FULL ANALYZE your_table; -- Решение 2: pg_repack (без блокировки!) -- Устанавливается отдельно: https://github.com/reorg/pg_repack -- pg_repack -d mydb -t your_table -- Решение 3: для партиционированных таблиц — просто удалить старую партицию -- ALTER TABLE events DETACH PARTITION events_2022_01; -- DROP TABLE events_2022_01; -- Мгновенное освобождение места! Заключение: чеклист production PostgreSQLКОНФИГУРАЦИЯ □ shared_buffers = 25-40% RAM □ effective_cache_size = 50-75% RAM □ work_mem настроен с учётом max_connections × parallel_workers □ random_page_cost = 1.1-1.5 для SSD/NVMe □ huge_pages = on (при shared_buffers > 8 ГБ, настроен в Linux) □ max_wal_size = 2-8 ГБ (зависит от нагрузки) □ wal_compression = lz4 (PG 15+) □ idle_in_transaction_session_timeout = 5min □ statement_timeout = установлен разумный лимит МОНИТОРИНГ □ pg_stat_statements включён и регулярно анализируется □ Алерт на cache hit ratio < 95% □ Алерт на replication lag > 60s □ Алерт на bloat > 30% для критичных таблиц □ Алерт на неактивные replication slots □ Ежедневный health check запрос AUTOVACUUM □ autovacuum_max_workers = 4-6 □ autovacuum_vacuum_cost_delay = 2ms (SSD) □ autovacuum_vacuum_cost_limit = 800-2000 □ Scale factor снижен для горячих таблиц □ Мониторинг n_dead_tup и xid_age СОЕДИНЕНИЯ □ PgBouncer в transaction mode □ max_connections ≤ 300 (больше — через пул) □ Настроен pool_size в PgBouncer □ Мониторинг cl_waiting в PgBouncer ИНДЕКСЫ □ Аудит неиспользуемых индексов (pg_stat_user_indexes) □ Составные индексы с правильным порядком колонок □ INCLUDE для покрывающих индексов □ BRIN для append-only больших таблиц РЕПЛИКАЦИЯ □ Мониторинг replay_lag □ Мониторинг pg_replication_slots на утечку WAL □ Проверка wal_status всех слотов □ hot_standby_feedback = on на репликах БЕЗОПАСНОСТЬ □ scram-sha-256 в pg_hba.conf □ Минимальные привилегии для каждого пользователя □ ssl = on + проверка сертификатов □ log_connections/log_disconnections для аудита PostgreSQL — невероятно мощная система, которая «из коробки» даёт примерно 20% своего потенциала. Правильная конфигурация, индексная стратегия и мониторинг превращают её в продукт, который выдерживает тысячи транзакций в секунду на десятках терабайт данных — без дорогостоящих «облачных» альтернатив.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.