Руководство по оптимизации производительности Linux
Аудит и тюнинг производительности Linux-сервера по методологии USE с командами, конфигами и планом проверки.
Пример запуска
Сгенерировано Claude Opus 5.5, 24 сентября 2026 г.
Значения переменных
- Дистрибутив linux
- Ubuntu Server 22.04.4 LTS
- Версия ядра
- Linux 5.15.0-102-generic
- Роль сервера
- Выделенный сервер БД PostgreSQL 14 для интернет-магазина с ~5000 одновременных подключений в пиковые часы, смешанная нагрузка OLTP (короткие транзакции чтения/записи, каталог товаров и заказы)
- Аппаратные характеристики
- CPU: 2x Intel Xeon Gold 6248R (48 ядер/96 потоков суммарно), RAM: 256 GB DDR4, Диски: 2x NVMe SSD 1.9TB в RAID1 (mdadm) под БД + 4x SAS HDD 4TB в RAID10 под архивные данные, Сеть: 2x 10GbE (bonding LACP, режим 802.3ad)
- Наблюдаемые симптомы
- С 18:00 до 23:00 (часы пиковой нагрузки) наблюдаются просадки latency у клиентских запросов, в логах PostgreSQL растёт число медленных запросов (>500ms), периодически срабатывают алерты на рост числа TIME_WAIT соединений, iowait в top держится на уровне 25-40%, память почти полностью занята под page cache, но приложение иногда падает в OOM
- Текущие метрики
- load average: 32, 28, 24 (при 48 физических ядрах), p50 latency запросов: 120 мс, p99 latency: 850 мс, throughput: ~3200 транзакций/сек в пике, iostat показывает %util диска с БД 85-95%, await до 45 мс, сеть: ~4.5 Gbps на bond0 из 20 Gbps доступных
- Цель оптимизации
- Снизить p99 latency запросов к БД с текущих 850 мс до целевых 150 мс без изменения схемы данных. SLA: доступность 99.9%, допустимое окно обслуживания — ночь с субботы на воскресенье с 02:00 до 04:00 МСК, простой не более 15 минут за одно окно. Бюджет на апгрейд железа отсутствует — только программный тюнинг.
Аудит производительности: PostgreSQL 14 на Ubuntu 22.04 (методология USE)
0. Предварительный диагноз
По имеющимся данным картина такая:
| Наблюдение | Что оно означает |
|---|---|
| 5000 прямых подключений к PostgreSQL | Каждое подключение — отдельный процесс. Это главный подозреваемый в OOM, в переключениях контекста, в конкуренции за LWLock/ProcArray и в TIME_WAIT. |
| Load avg 32 при 48 физ. ядрах и iowait 25–40% | В Linux load average учитывает процессы в состоянии D (ожидание I/O). CPU, скорее всего, не насыщен: load «надут» ожиданием диска. |
NVMe %util 85–95%, await до 45 мс |
Для NVMe %util ничего не говорит о насыщении, потому что у устройства десятки параллельных очередей. А вот await 45 мс для NVMe — это аномалия (норма 0.1–2 мс). Вероятные причины: пики записи при checkpoint, лавинный writeback грязных страниц, спилл временных файлов, накладные расходы mdadm (bitmap, resync). |
| Page cache занимает почти всю память, но случается OOM | Page cache освобождается ядром сам. OOM вызывает анонимная память бэкендов: грубо, work_mem × узлы плана × 5000. Кэш здесь не виноват. |
| Сеть 4.5 из 20 Gbps | Пропускная способность сети не узкое место. TIME_WAIT — следствие модели подключений, а не нехватки полосы. |
Рабочая гипотеза: доминирующий ресурс — дисковый I/O. Его усиливают неуправляемое число подключений (давление на память → вытеснение кэша → лишние чтения; OOM → crash-restart postmaster) и настройки ядра по умолчанию, рассчитанные не на 256 GB RAM.
⚠️ Честное ограничение. Цель p99 150 мс достижима только программным тюнингом, если основная часть латентности приходится на ожидания (I/O, блокировки, очередь подключений). Если в топе
pg_stat_statementsокажутся запросы, которые медленные сами по себе (seq scan по каталогу и т. п.), понадобится работа с запросами. Отдельно нужно согласовать, считается лиCREATE INDEX CONCURRENTLY«изменением схемы». Формально это не меняет модель данных, но это DDL.
Шаг 1. Базовая линия
1.1 Подготовка инструментов
# Набор инструментов
sudo apt install -y sysstat iotop-c htop bpfcc-tools linux-tools-$(uname -r) \
nvme-cli smartmontools numactl ethtool rasdaemon
# sysstat: включить сбор и увеличить частоту с 10 до 1 минуты
sudo sed -i 's/^ENABLED=.*/ENABLED="true"/' /etc/default/sysstat
sudo systemctl edit sysstat-collect.timer
# [Timer]
# OnCalendar=
# OnCalendar=*:*:00
sudo systemctl enable --now sysstat sysstat-collect.timer
Со стороны PostgreSQL:
-- track_io_timing меняется без рестарта
ALTER SYSTEM SET track_io_timing = on;
ALTER SYSTEM SET log_checkpoints = on;
ALTER SYSTEM SET log_temp_files = 0; -- логировать любые temp-файлы
ALTER SYSTEM SET log_lock_waits = on;
ALTER SYSTEM SET log_autovacuum_min_duration = '1s';
SELECT pg_reload_conf();
-- pg_stat_statements требует shared_preload_libraries, то есть рестарта (см. шаг 5).
-- Если расширение уже загружено: CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
1.2 Скрипт сбора базовой линии
Запускать в пик (например, в 19:00–21:00), три будних дня подряд плюс один «спокойный» день для сравнения.
#!/usr/bin/env bash
# /usr/local/bin/baseline.sh — сбор базовой линии, ~10 минут
OUT=/var/tmp/baseline-$(date +%F-%H%M); mkdir -p "$OUT"; cd "$OUT"
D=600 # длительность, с
# ---------- CPU ----------
mpstat -P ALL 5 $((D/5)) > mpstat.txt & # %usr/%sys/%iowait/%soft по ядрам; перекос на отдельные ядра = IRQ/softirq
vmstat -w 1 $D > vmstat.txt & # r (runnable) vs b (blocked in D), cs, in, si/so, wa
pidstat -u -w 5 $((D/5)) > pidstat_cpu.txt & # cswch/s, nvcswch/s по процессам postgres
sar -q 5 $((D/5)) > sar_q.txt & # runq-sz, plist-sz, blocked
# ---------- Память ----------
sar -r -B -S -W 5 $((D/5)) > sar_mem.txt & # kbcommit/%commit, pgscank/pgscand (reclaim), majflt, swap
( for i in $(seq 1 $((D/10))); do date +%T; cat /proc/pressure/{cpu,memory,io}; sleep 10; done ) > psi.txt &
# ---------- Диск ----------
iostat -dxmt 5 $((D/5)) > iostat.txt & # r/s w/s r_await w_await aqu-sz rareq-sz wareq-sz (для nvme*, md*, sd*)
pidstat -d 5 $((D/5)) > pidstat_io.txt & # какие процессы читают/пишут (checkpointer, bgwriter, autovacuum, backend)
timeout 120 /usr/sbin/biolatency-bpfcc -D 10 12 > biolatency.txt & # гистограмма латентности по устройствам
# ---------- Сеть ----------
sar -n DEV,EDEV,TCP,ETCP,SOCK 5 $((D/5)) > sar_net.txt & # throughput, ошибки, retrans/s, tcp-tw
( for i in $(seq 1 $((D/30))); do date +%T; ss -s; sleep 30; done ) > ss_summary.txt &
wait
# ---------- Снимки состояния (однократно) ----------
cat /proc/meminfo > meminfo.txt
cat /proc/mdstat > mdstat.txt
numastat -m > numastat.txt
cat /sys/kernel/mm/transparent_hugepage/{enabled,defrag} > thp.txt
sysctl -a 2>/dev/null > sysctl_all.txt
for d in /sys/block/{nvme*,md*,sd*}; do
echo "$d $(cat $d/queue/scheduler 2>/dev/null) ra=$(cat $d/queue/read_ahead_kb)"; done > block_queue.txt
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS > mounts.txt
cpupower frequency-info > cpufreq.txt 2>&1
cat /proc/net/bonding/bond0 > bond.txt
ss -tan state time-wait | awk 'NR>1{split($4,a,":"); print a[length(a)]}' | sort | uniq -c | sort -rn | head > tw_by_localport.txt
journalctl -k --since "-7d" | grep -iE -A25 "out of memory|oom-kill" > oom.txt
dmesg -T | grep -iE "nvme|md[0-9]|i/o error|timeout|mce|edac|link is down" > dmesg_err.txt
echo "done: $OUT"
1.3 Интерактивно в момент деградации
htop # F2 → включить колонки STATE, IO_RATE; много 'D' у postgres = ожидание I/O
sudo iotop-c -oPa # накопительно: кто больше всех пишет/читает
top -H -p $(head -1 /var/lib/postgresql/14/main/postmaster.pid) # нити postmaster
1.4 Метрики на стороне PostgreSQL (самое информативное)
-- (a) Выборка wait events раз в 1 секунду в течение 5 минут (psql: \watch 1).
-- Показывает, ЧЕГО ждут бэкенды: IO / LWLock / Lock / Client / CPU (NULL).
SELECT coalesce(wait_event_type,'CPU') wt, coalesce(wait_event,'-') we, count(*)
FROM pg_stat_activity WHERE state = 'active' AND backend_type = 'client backend'
GROUP BY 1,2 ORDER BY 3 DESC LIMIT 15;
-- (b) Сколько из 5000 подключений реально работают
SELECT state, count(*) FROM pg_stat_activity GROUP BY 1 ORDER BY 2 DESC;
-- (c) Checkpoint'ы: requested >> timed означает, что max_wal_size мал
SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time,
buffers_checkpoint, buffers_clean, maxwritten_clean, buffers_backend, buffers_backend_fsync
FROM pg_stat_bgwriter;
-- (d) Hit ratio и temp-файлы
SELECT datname, blks_hit*100.0/nullif(blks_hit+blks_read,0) AS hit_pct,
temp_files, pg_size_pretty(temp_bytes) temp, deadlocks, blk_read_time, blk_write_time
FROM pg_stat_database WHERE datname NOT LIKE 'template%';
-- (e) Размер БД против RAM
SELECT pg_size_pretty(sum(pg_database_size(datname))) FROM pg_database;
-- (f) Топ запросов по суммарному времени (после включения pg_stat_statements)
SELECT queryid, calls, round(total_exec_time) total_ms, round(mean_exec_time,1) mean_ms,
round(stddev_exec_time,1) sd_ms, shared_blks_read, temp_blks_written,
round(blk_read_time) io_read_ms, left(query,80)
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;
-- (g) Текущие параметры
SELECT name, setting, unit, source FROM pg_settings
WHERE name IN ('max_connections','shared_buffers','work_mem','maintenance_work_mem',
'effective_cache_size','max_wal_size','checkpoint_timeout','checkpoint_completion_target',
'wal_buffers','huge_pages','random_page_cost','effective_io_concurrency','jit',
'autovacuum_max_workers','autovacuum_vacuum_cost_limit','synchronous_commit','temp_tablespaces');
Шаг 2. Локализация узкого места
2.1 Матрица USE для этого сервера
| Ресурс | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | mpstat: %usr+%sys (без %iowait!) |
vmstat колонка r > числа ядер; PSI cpu some; runqlat-bpfcc |
dmesg MCE, rasdaemon |
| Память | MemAvailable, Committed_AS против CommitLimit |
sar -B: pgscand/s > 0 (direct reclaim), majflt/s, si/so; PSI memory |
OOM-kill в journalctl -k, EDAC |
| Диск | iostat r/s, w/s, MB/s в сравнении с паспортом NVMe (не %util) |
aqu-sz, r_await/w_await, PSI io full, biolatency |
nvme smart-log, /proc/mdstat, I/O errors в dmesg |
| Сеть | sar -n DEV rxkB/txkB по каждому слейву bond |
ss -lnt (Recv-Q на listen-сокете), netstat -s overflowed/dropped, softnet_stat |
ip -s link, ethtool -S (rx_missed, crc) |
| ПО (PG) | активные / max_connections | wait events Lock/LWLock, очередь в пулере |
OOM, crash-recovery в логе PG |
2.2 Как отличить одно от другого
load avg высокий
│
vmstat: r (runnable) против b (blocked)
┌───────────────┴────────────────┐
r ≫ ядер, b мал b велик, r < ядер
%usr+%sys > 80% %iowait высокий
PSI cpu some > 20% PSI io full > 10%
│ │
→ CPU-bound → I/O-bound ──┐
(perf top, pg wait=NULL, │
много LWLock при 5000 conn) iostat: w_await ≫ r_await?
├─ да → запись: checkpoint, writeback,
│ WAL fsync, mdadm bitmap
└─ нет → чтение: кэш не вмещает рабочий набор
или его вытеснили (память!)
sar -B pgscand > 0, majflt растёт, → нехватка памяти: reclaim давит на I/O
Committed_AS → CommitLimit, OOM в логе (порочный круг: память → чтения с диска)
ss -s TIME-WAIT растёт, но Recv-Q = 0, → это симптом модели подключений,
retrans < 0.1%, полоса < 50% а НЕ сетевой лимит
Интерпретация текущих цифр:
- iowait 25–40% при load 32 — классический I/O-bound. Если в
vmstatколонкаbокажется 15–30, аrменьше 20, гипотеза подтверждена. - await 45 мс на NVMe — сразу проверить
r_awaitиw_awaitпо отдельности, а также наmd0противnvme0n1/nvme1n1:- высокий
awaitнаmd0при низком наnvme*означает накладные расходы mdadm (bitmap, resync); - пилообразные всплески
w_await, совпадающие с checkpoint в логе PG, означают checkpoint или writeback-шторм; - высокий
r_awaitпри большомr/sозначает вытеснение рабочего набора из кэша.
- высокий
- OOM при полном page cache. Ядро не может превратить кэш в анонимную память быстрее, чем её запрашивают тысячи бэкендов, либо упирается в лимит (overcommit, cgroup). Грубая оценка: 5000 × (~5–10 MB собственной памяти +
work_mem× 2–3 узла) приwork_mem = 16MBдаёт ~200–300 GB в худшем случае, то есть больше физической RAM. Когда OOM-killer убивает бэкенд, postmaster перезапускает все подключения и проходит crash recovery. Это прямой удар и по p99, и по SLA 99.9%. - TIME_WAIT. Если он накапливается на серверном порту 5432, значит сервер (PG) активно закрывает соединения: клиенты подключаются и отключаются на каждый запрос или отваливаются по таймауту. Лечится пулингом, а не sysctl.
2.3 Что обязательно собрать до изменений
| # | Данные | Зачем |
|---|---|---|
| 1 | Какой процесс убивает OOM (oom.txt) и его RSS |
Понять, «приложение» — это бэкенд PG, pgbouncer или что-то стороннее |
| 2 | iostat -x с разбивкой r/w и aqu-sz для md0 и nvme* |
Отличить чтение от записи, mdadm от устройства |
| 3 | Корреляция пиков w_await с log_checkpoints |
Подтвердить гипотезу о checkpoint |
| 4 | Выборка wait events (1.4a) | Доля IO / LWLock / Lock / Client |
| 5 | pg_stat_activity по состояниям |
Сколько из 5000 реально активны (обычно < 5%) |
| 6 | Размер БД и hit ratio | Помещается ли рабочий набор в 256 GB |
| 7 | Текущий postgresql.conf (1.4g) |
Без него нельзя рассчитать параметры |
| 8 | Тип ФС и опции монтирования, discard |
Синхронный TRIM даёт латентность |
| 9 | /proc/mdstat в 18–23 ч, mdadm --detail (bitmap) |
Resync/check, write-intent bitmap |
| 10 | nvme smart-log (температура, critical_warning, percentage_used) |
Троттлинг и износ — это «Errors» по USE |
| 11 | cron/systemd-таймеры на 18–23 ч (бэкапы, pg_dump, отчёты, archive_command) |
Фоновые конкуренты за I/O |
| 12 | Есть ли swap; Committed_AS в пик; MemoryMax у юнита |
Причина OOM |
| 13 | Как клиенты подключаются: пулер на стороне приложения, время жизни соединений | Дизайн PgBouncer |
# Быстрые проверки по пунктам 8–12
findmnt -no FSTYPE,OPTIONS /var/lib/postgresql
sudo mdadm --detail /dev/md0 | grep -iE "state|bitmap|resync|check"
cat /sys/block/md0/md/sync_action # idle — хорошо; check/resync — фоновая нагрузка
sudo nvme smart-log /dev/nvme0n1 | grep -iE "warning|temperature|percentage_used|media_errors"
systemctl list-timers --all; ls /etc/cron.d; crontab -l -u postgres
systemctl show postgresql@14-main -p MemoryMax -p OOMScoreAdjust
grep -E "Committed_AS|CommitLimit|HugePages_|AnonHugePages" /proc/meminfo
cat /proc/$(head -1 /var/lib/postgresql/14/main/postmaster.pid)/oom_score_adj
Шаг 3. Оптимизации
Порядок важен: сначала устраняем причину (модель подключений и память), затем давление на I/O, в конце — второстепенное (CPU, сеть).
3.1 Пул подключений: PgBouncer (главная мера)
Эффект: 5000 клиентских соединений превращаются в 150–250 серверных. Результат:
- на порядок меньше анонимной памяти, OOM уходит;
- освободившаяся RAM возвращается в кэш, чтений с диска становится меньше;
- меньше переключений контекста и конкуренции за LWLock;
- соединения PgBouncer → PG через unix-сокет, поэтому TCP и TIME_WAIT между ними нет;
- клиентские TCP-сессии становятся долгоживущими.
# Нужен PgBouncer >= 1.21 (поддержка prepared statements в transaction mode).
# В репозитории Ubuntu 22.04 версия 1.16, поэтому ставим из PGDG.
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/pgdg.gpg
sudo apt update && sudo apt install -y pgbouncer
; /etc/pgbouncer/pgbouncer.ini
[databases]
; к PG через unix-сокет: без TCP, без TIME_WAIT
shop = host=/var/run/postgresql port=5432 dbname=shop
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
so_reuseport = 1 ; позволяет поднять 2–4 инстанса на одном порту (pgbouncer однопоточный)
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction ; ключевой режим для OLTP
max_client_conn = 6000
default_pool_size = 150 ; на пару (db,user); ориентир 2–4 × физ. ядер, подбирать по wait events
min_pool_size = 50
reserve_pool_size = 20
reserve_pool_timeout = 2
max_db_connections = 250 ; жёсткий потолок к PG
max_prepared_statements = 200 ; 1.21+: prepared statements в transaction mode
server_idle_timeout = 300
query_wait_timeout = 10 ; клиент не висит бесконечно в очереди
tcp_keepalive = 1
tcp_keepidle = 60
tcp_keepintvl = 10
tcp_keepcnt = 6
admin_users = pgbouncer_admin
stats_users = monitoring
# Лимит файловых дескрипторов для pgbouncer
sudo systemctl edit pgbouncer
# [Service]
# LimitNOFILE=65536
# Несколько инстансов через so_reuseport: шаблонный юнит pgbouncer@{1..3} с одним конфигом
# (при необходимости, если один процесс упирается в 100% одного ядра — видно в top)
⚠️ Совместимость transaction mode. Ломаются:
SETна уровне сессии (использоватьSET LOCALили параметры роли), advisory locks уровня сессии,LISTEN/NOTIFY, временные таблицы, живущие дольше транзакции,WITH HOLDкурсоры. Эти вещи нужно проверить в коде приложения. Для несовместимых клиентов заведите отдельный пул сpool_mode = session.
Внедрение без простоя: PgBouncer поднимается на порту 6432 рядом с PG, приложения переключаются поэтапно (канарейкой: 10% → 50% → 100%). Снижение max_connections выполняется только после полной миграции, в окне обслуживания (требует рестарта PG).
3.2 Память: PostgreSQL, OOM-защита, huge pages, THP, NUMA
3.2.1 Параметры памяти PostgreSQL (после внедрения пула)
# /etc/postgresql/14/main/conf.d/10-memory.conf
max_connections = 400 # было ~5000+; RESTART
shared_buffers = 64GB # ~25% RAM; RESTART
huge_pages = try # в первом окне try, после проверки on; RESTART
effective_cache_size = 176GB # подсказка планировщику, память не выделяет
work_mem = 32MB # безопасно при ~250 активных: 250 × 32MB × ~3 ≈ 24GB
maintenance_work_mem = 2GB
autovacuum_work_mem = 1GB
wal_buffers = 64MB # RESTART
Отдельные «тяжёлые» роли (отчёты) получают свой work_mem, а не глобальный:
ALTER ROLE reporting SET work_mem = '256MB';
3.2.2 Защита postmaster от OOM-killer
# Проверить текущее значение (pg_ctlcluster в Debian/Ubuntu может уже выставлять -900)
cat /proc/$(head -1 /var/lib/postgresql/14/main/postmaster.pid)/oom_score_adj
# Если не выставлено:
sudo systemctl edit postgresql@14-main
# [Service]
# OOMScoreAdjust=-900
# Дочерние бэкенды возвращают себе 0, чтобы в крайнем случае убивался бэкенд, а не postmaster.
# /etc/postgresql/14/main/environment
# PG_OOM_ADJUST_FILE = '/proc/self/oom_score_adj'
# PG_OOM_ADJUST_VALUE = '0'
3.2.3 Huge pages для shared_buffers
Эффект: таблицы страниц для 64 GB shared memory × сотни процессов сжимаются с гигабайт до мегабайт. Меньше TLB-промахов, меньше PageTables в /proc/meminfo. При 5000 процессах PageTables сейчас может занимать десятки GB — проверьте.
grep PageTables /proc/meminfo # до изменений
# Расчёт для PG14 (shared_memory_size_in_huge_pages появился только в PG15):
# после рестарта с новым shared_buffers и huge_pages=try
PID=$(head -1 /var/lib/postgresql/14/main/postmaster.pid)
grep ^VmPeak /proc/$PID/status # например 70000000 kB
grep Hugepagesize /proc/meminfo # 2048 kB
# nr_hugepages = VmPeak / 2048 + ~2% запас, например 70000000/2048 ≈ 34180 → 34900
# /etc/sysctl.d/60-hugepages.conf
vm.nr_hugepages = 34900
vm.hugetlb_shm_group = <gid группы postgres> # getent group postgres
⚠️ Выделение ~68 GB huge pages «на лету» может не получиться из-за фрагментации памяти (
HugePages_Totalокажется меньше запрошенного). Надёжнее выделять при загрузке (sysctl.d применяется рано) или сразу послеecho 3 > /proc/sys/vm/drop_cachesв окне обслуживания. Поэтому в первом окне ставимhuge_pages = try, аon— только после подтверждения.
3.2.4 THP, swappiness, overcommit, NUMA
# THP: для PG рекомендуется never (Ubuntu по умолчанию ставит madvise — это приемлемо, always — нет)
cat /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
# Постоянно: параметр ядра transparent_hugepage=never (см. 3.4), нужна перезагрузка
# /etc/sysctl.d/61-pg-memory.conf
vm.swappiness = 1 # не выталкивать анонимную память ради кэша (swap оставить как страховку)
vm.zone_reclaim_mode = 0 # 2 сокета: не форсировать локальный reclaim, использовать кэш удалённого узла
kernel.numa_balancing = 0 # миграция страниц NUMA даёт латентность на shared memory PG
vm.min_free_kbytes = 1048576 # 1 GB резерва: меньше direct reclaim и сбоев атомарных аллокаций (сеть)
kernel.sched_autogroup_enabled = 0
# Overcommit — ГЛУБОКОЕ изменение, вводится отдельно и только после расчёта:
# vm.overcommit_memory = 2
# vm.overcommit_ratio = <N>
# CommitLimit = Swap + (MemTotal − HugePages_Total×2MB) × ratio/100
# Пример: (256GB − 68GB) × 90% ≈ 169GB + swap. Условие: в пик Committed_AS
# должен оставаться хотя бы на 20% ниже CommitLimit, иначе PG получит ENOMEM на fork.
На ядре 5.15
kernel.sched_migration_cost_nsперенесён в debugfs, поэтому через sysctl его не выставить. Старые руководства устарели.
3.3 Диск и I/O
3.3.1 Writeback грязных страниц
По умолчанию dirty_background_ratio = 10, dirty_ratio = 20 — от доступной памяти. На 256 GB это ~25/50 GB грязных страниц, которые затем сбрасываются лавиной при fsync на checkpoint. Отсюда пики w_await до десятков мс.
# /etc/sysctl.d/62-pg-io.conf
vm.dirty_background_bytes = 536870912 # 512 MB: фоновый сброс начинается рано и идёт ровно
vm.dirty_bytes = 2147483648 # 2 GB: потолок, после которого пишущий процесс блокируется
vm.dirty_expire_centisecs = 1000 # 10 с (по умолчанию 30)
vm.dirty_writeback_centisecs = 100 # будить flusher раз в 1 с
# Примечание: установка *_bytes автоматически обнуляет *_ratio
3.3.2 Checkpoint, WAL, bgwriter (без рестарта)
ALTER SYSTEM SET max_wal_size = '64GB'; -- меньше вынужденных checkpoint (checkpoints_req → 0)
ALTER SYSTEM SET min_wal_size = '8GB';
ALTER SYSTEM SET checkpoint_timeout = '15min'; -- реже full page writes, меньше WAL
ALTER SYSTEM SET checkpoint_completion_target = 0.9; -- размазать запись на 90% интервала
ALTER SYSTEM SET wal_compression = on; -- PG14: pglz; сжимает FPW, меньше записи WAL ценой CPU (есть запас)
ALTER SYSTEM SET bgwriter_delay = '50ms';
ALTER SYSTEM SET bgwriter_lru_maxpages = 1000; -- бэкенды реже пишут грязные буферы сами
ALTER SYSTEM SET bgwriter_lru_multiplier = 4.0;
ALTER SYSTEM SET random_page_cost = 1.1; -- NVMe: планировщик охотнее выбирает index scan
ALTER SYSTEM SET effective_io_concurrency = 256; -- prefetch для bitmap heap scan
ALTER SYSTEM SET maintenance_io_concurrency = 256;
ALTER SYSTEM SET jit = off; -- в OLTP JIT часто добавляет 10–100+ мс к «средним» запросам
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60s'; -- висящие транзакции держат снапшоты и мешают vacuum
SELECT pg_reload_conf();
Место на диске: при
max_wal_size = 64GBвpg_walна 1.9 TB-массиве должно быть свободно не меньше 100 GB. Проверьте это до изменения.
3.3.3 Autovacuum: быстрее, но ровнее
Раздутые таблицы и индексы — это лишние чтения. Задача — чтобы autovacuum успевал, но не давал всплесков.
ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 2000; -- по умолчанию 200 (для NVMe очень мало)
ALTER SYSTEM SET autovacuum_vacuum_scale_factor = 0.05;
ALTER SYSTEM SET autovacuum_analyze_scale_factor = 0.02;
ALTER SYSTEM SET autovacuum_naptime = '15s';
SELECT pg_reload_conf();
-- autovacuum_max_workers = 6 требует RESTART (добавить в conf.d в окне)
-- Для горячих таблиц заказов — индивидуально:
ALTER TABLE orders SET (autovacuum_vacuum_scale_factor = 0.01);
REINDEX CONCURRENTLY для раздутых индексов схему не меняет. Проверить раздутость можно через расширение pgstattuple.
3.3.4 Блочный уровень: планировщик, readahead, ФС
# NVMe: планировщик none (в Ubuntu 22.04 обычно уже стоит). HDD архива: mq-deadline.
# /etc/udev/rules.d/60-io-scheduler.rules
ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*", ATTR{queue/scheduler}="none", ATTR{queue/rq_affinity}="2"
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"
# md-устройства — без планировщика (bio-based); readahead для OLTP 8K random умеренный:
ACTION=="add|change", KERNEL=="md0", ATTR{bdi/read_ahead_kb}="128"
sudo udevadm control --reload && sudo udevadm trigger --subsystem-match=block
blockdev --getra /dev/md0 # в секторах по 512 байт: 256 = 128 KB
# ФС: убрать синхронный discard, включить noatime. Барьеры НЕ отключать (nobarrier = риск потери данных).
# /etc/fstab (пример для ext4; для XFS — аналогично без discard)
# UUID=... /var/lib/postgresql ext4 defaults,noatime 0 2
sudo mount -o remount,noatime /var/lib/postgresql # без простоя
# Если в опциях есть discard — убрать из fstab (вступит при следующем монтировании)
# и перейти на периодический TRIM вне пика:
sudo systemctl enable --now fstrim.timer # по умолчанию раз в неделю; проверить, что не попадает на 18–23
3.3.5 mdadm RAID1
# 1) Проверка/resync не должны идти в пик
cat /sys/block/md0/md/sync_action
systemctl list-timers | grep -i mdcheck # Ubuntu 22.04: mdcheck_start/continue timers
# ограничить скорость фоновых проверок, чтобы не забирать полосу у PG
echo 50000 | sudo tee /proc/sys/dev/raid/speed_limit_max # KB/s (постоянно — через sysctl.d: dev.raid.speed_limit_max)
# 2) Write-intent bitmap: мелкий chunk = лишняя синхронная запись на каждый случайный write.
sudo mdadm --detail /dev/md0 | grep -i bitmap
sudo mdadm -X /dev/nvme0n1p1 2>/dev/null | grep -i chunk # текущий размер chunk
# ГЛУБОКОЕ изменение (выполнять в окне, при полном бэкапе и массиве в состоянии clean):
# sudo mdadm --grow /dev/md0 --bitmap=none
# sudo mdadm --grow /dev/md0 --bitmap=internal --bitmap-chunk=128M
# Эффект: реже обновления bitmap при случайной записи → ниже w_await.
# Цена: после аварийного отключения resync будет чуть дольше.
3.3.6 Изоляция от архивного массива и фоновых задач
temp_tablespacesиpg_walне должны лежать на SAS HDD RAID10 (задержки ×10–50). Проверьте через\db+иls -l $PGDATA/pg_wal.- Бэкапы (
pg_basebackup,pg_dump) иarchive_command, если они запускаются в 18–23 ч, нужно перенести или ограничить:ionice -c3,pg_basebackup --max-rate. - Выборочный
synchronous_commit = offдопустим только для некритичных записей (сессии, корзины, аналитика) черезSET LOCALили параметры отдельной роли. Для заказов оставитьon.
3.4 CPU (вторично, но дёшево)
# Частотный governor: performance вместо powersave/schedutil
cpupower frequency-info | grep -E "driver|governor"
sudo cpupower frequency-set -g performance
# постоянно:
echo 'GOVERNOR="performance"' | sudo tee /etc/default/cpufrequtils # либо systemd-юнит с cpupower
# Глубокие C-states добавляют десятки–сотни мкс на пробуждение ядра.
# Вариант без перезагрузки: tuned с профилем latency-performance (держит /dev/cpu_dma_latency)
sudo apt install -y tuned && sudo tuned-adm profile latency-performance
tuned-adm active
# Вариант через параметры ядра (ТРЕБУЕТ ПЕРЕЗАГРУЗКИ) — вместе с THP:
# /etc/default/grub
# GRUB_CMDLINE_LINUX_DEFAULT="... transparent_hugepage=never intel_idle.max_cstate=1 processor.max_cstate=1"
# sudo update-grub
В BIOS/iDRAC/iLO нужно выставить профиль энергопотребления «Performance» (это тоже требует перезагрузки).
3.5 Сетевой стек
Сеть не узкое место, поэтому меры здесь скорее гигиенические.
# /etc/sysctl.d/63-pg-net.conf
net.core.somaxconn = 4096 # backlog listen-сокета PgBouncer/PG
net.ipv4.tcp_max_syn_backlog = 8192
net.core.netdev_max_backlog = 16384 # очередь при всплесках softirq
net.ipv4.tcp_tw_reuse = 1 # влияет ТОЛЬКО на исходящие соединения (актуально, если PG/pgbouncer сам ходит наружу)
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_keepalive_time = 60 # быстрее находить мёртвых клиентов вместо висящих бэкендов
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 6
net.ipv4.tcp_slow_start_after_idle = 0
Мифы, которые не нужно применять:
tcp_fin_timeoutне сокращает TIME_WAIT (он управляет FIN_WAIT_2; TIME_WAIT в Linux жёстко 60 с).tcp_tw_recycleудалён из ядра ещё в 4.12. Серверный TIME_WAIT лечится долгоживущими соединениями через пулер.
# Bonding: равномерное распределение потоков по слейвам — проверить в /proc/net/bonding/bond0
# /etc/netplan/01-bond.yaml (фрагмент)
bonds:
bond0:
interfaces: [eno1, eno2]
parameters:
mode: 802.3ad
lacp-rate: fast
transmit-hash-policy: layer3+4 # по умолчанию layer2: весь трафик до одного шлюза уходит в один линк
mii-monitor-interval: 100
# Хеширование layer3+4 нужно настроить и на стороне коммутатора.
# Применять через `netplan try` (автооткат через 120 с при потере связи).
# Проверка перекоса и потерь
sar -n DEV 1 10 | grep -E "eno|bond" # трафик по слейвам
ethtool -S eno1 | grep -iE "drop|miss|err"
ethtool -g eno1 # увеличить RX-ring при rx_missed: ethtool -G eno1 rx 4096
cat /proc/net/softnet_stat # 2-я колонка (drops) и 3-я (time_squeeze) должны быть ~0
systemctl status irqbalance
3.6 Сводный postgresql.conf для окна обслуживания (параметры, требующие рестарта)
# /etc/postgresql/14/main/conf.d/20-restart.conf — применяется в окне
max_connections = 400
shared_buffers = 64GB
huge_pages = try # → on после проверки HugePages_Rsvd
wal_buffers = 64MB
autovacuum_max_workers = 6
shared_preload_libraries = 'pg_stat_statements,auto_explain'
pg_stat_statements.max = 10000
pg_stat_statements.track = top
auto_explain.log_min_duration = '500ms'
auto_explain.log_analyze = off # on даёт оверхед; включать точечно
max_parallel_workers_per_gather = 2 # OLTP: не давать одному запросу съедать ядра
Шаг 4. Проверка результата
4.1 Принципы
- Где тестировать. Синтетику (fio, stress-ng) — на стенде, восстановленном из бэкапа на аналогичном железе. На продуктиве fio запускается только в окне и только в файл на ФС, никогда в сырое
/dev/md0. - Как сравнивать. Одинаковые дни недели, одинаковый трафик (заказы в час), по возможности по одному изменению или одной группе изменений на итерацию.
- Откуда брать p99.
pg_stat_statementsдаёт только mean и stddev, перцентилей там нет. p99 измеряется на стороне клиента (APM или гистограммы Prometheus в приложении) либо в pgbench (--log,-P).
4.2 Синтетика дисковой подсистемы (fio)
# Стенд или окно обслуживания; каталог на той же ФС, что и PGDATA
mkdir -p /var/lib/postgresql/fio && cd /var/lib/postgresql/fio
# (a) Профиль OLTP: 8K случайные чтение/запись 70/30, прямой I/O
fio --name=oltp --filename=fio.dat --size=32G --direct=1 --ioengine=libaio \
--rw=randrw --rwmixread=70 --bs=8k --iodepth=32 --numjobs=8 --group_reporting \
--time_based --runtime=120 --percentile_list=50:99:99.9 --output-format=json > fio_oltp.json
# (b) Профиль WAL: последовательная запись 8K с fdatasync после каждой — латентность коммита
fio --name=wal --filename=wal.dat --size=4G --rw=write --bs=8k --ioengine=sync \
--fdatasync=1 --numjobs=1 --time_based --runtime=60 --percentile_list=50:99:99.9
# (c) Утилита PG: сколько fsync/с выдерживает устройство
/usr/lib/postgresql/14/bin/pg_test_fsync -f /var/lib/postgresql/fio/pgtf.dat -s 5
rm -rf /var/lib/postgresql/fio
Сравнить clat p99 до и после изменений mdadm, writeback и ФС. Для NVMe RAID1 ориентир: wal p99 < 1 мс, oltp p99 < 2–3 мс при разумной глубине очереди.
4.3 Нагрузка, приближенная к реальной (pgbench)
# Сценарий из топ-запросов pg_stat_statements (пример: карточка товара + оформление заказа)
cat > /tmp/shop.sql <<'EOF'
\set pid random(1, 2000000)
\set uid random(1, 5000000)
BEGIN;
SELECT * FROM products WHERE id = :pid;
SELECT * FROM stock WHERE product_id = :pid;
INSERT INTO orders(user_id, product_id, created_at) VALUES (:uid, :pid, now());
COMMIT;
EOF
# 5000 клиентов через PgBouncer (порт 6432) против прямого подключения (5432)
pgbench -h db -p 6432 -U app -n -f /tmp/shop.sql -c 5000 -j 64 -T 900 -P 10 \
--log --log-prefix=after --sampling-rate=0.1 -M prepared shop
# Перцентили из логов pgbench (3-я колонка — латентность в мкс)
cat after.* | awk '{print $3}' | sort -n | \
awk '{a[NR]=$1} END{print "p50="a[int(NR*.5)]/1000"ms p99="a[int(NR*.99)]/1000"ms"}'
Лучший вариант — воспроизведение реального трафика: утилита pgreplay по логу с log_min_duration_statement = 0, снятому за 15–30 минут пика (лог объёмный, собирать на короткий интервал).
4.4 Проверка защиты от OOM (только на стенде)
# Имитация давления памяти: убедиться, что OOM-killer выбирает не postmaster
stress-ng --vm 8 --vm-bytes 90% --vm-keep --timeout 120s &
watch -n1 'grep -E "MemAvailable|Committed_AS|CommitLimit" /proc/meminfo'
journalctl -k -f | grep -i oom
# Ожидание: убит stress-ng; postmaster жив; в логе PG нет "terminating any other active server processes"
4.5 Таблица сравнения «до/после» (заполнить)
| Метрика | Источник | До | Цель | После |
|---|---|---|---|---|
| p50 / p99 latency запросов | APM клиента / pgbench | 120 / 850 мс | ≤ 50 / ≤ 150 мс | |
| TPS в пик | pg_stat_database xact_commit/с |
~3200 | ≥ 3200 | |
| Соединения к PG (всего / active) | pg_stat_activity |
~5000 / ? | ≤ 250 / ≤ 150 | |
%iowait |
mpstat |
25–40% | < 5% | |
vmstat b (blocked) |
vmstat |
? | < 5 | |
PSI io full avg60 |
/proc/pressure/io |
? | < 2% | |
w_await / r_await md0 |
iostat -x |
до 45 мс | < 2 мс | |
checkpoints_req / timed |
pg_stat_bgwriter |
? | req ≈ 0 | |
buffers_backend доля |
pg_stat_bgwriter |
? | < 10% записанных буферов | |
| Hit ratio | pg_stat_database |
? | > 99% | |
temp_bytes за час |
pg_stat_database |
? | ↓ на порядок | |
pgscand/s (direct reclaim) |
sar -B |
? | ≈ 0 | |
PageTables |
/proc/meminfo |
? | < 1 GB | |
| OOM-события в неделю | journalctl -k |
> 0 | 0 | |
| TIME_WAIT на :5432 | ss -tan state time-wait |
алерты | ≈ 0 | |
| TCP retrans/с | sar -n ETCP |
? | < 0.1% сегментов |
Критерий приёмки: p99 ≤ 150 мс держится 5 рабочих дней подряд в окне 18:00–23:00 без OOM и без регрессии TPS.
Шаг 5. Риски, постоянство, откат
5.1 Что требует рестарта или перезагрузки
| Изменение | Применение | Простой | Постоянство | Откат |
|---|---|---|---|---|
| sysctl (vm.dirty_, swappiness, net., numa) | sysctl --system |
нет | /etc/sysctl.d/6x-*.conf |
удалить файл → sysctl -w старых значений (сохранены в sysctl_all.txt) |
THP runtime (never) |
echo в /sys |
нет | параметр ядра в GRUB | echo madvise |
| THP, C-states через GRUB | перезагрузка | 5–10 мин (POST сервера с 256 GB бывает долгим) | /etc/default/grub + update-grub |
вернуть строку, update-grub, перезагрузка; в меню GRUB — e для разовой правки |
| Governor / tuned | мгновенно | нет | tuned / systemd | tuned-adm profile balanced |
| udev (scheduler, readahead) | udevadm trigger |
нет | /etc/udev/rules.d/60-*.rules |
удалить правило, trigger |
noatime |
mount -o remount |
нет | /etc/fstab |
remount со старыми опциями |
Удаление discard из fstab |
при монтировании | при рестарте ФС | fstab | вернуть опцию |
| mdadm bitmap chunk | онлайн mdadm --grow |
нет, но риск | метаданные массива | --bitmap=none → --bitmap=internal со старым chunk |
| PG reload-параметры (checkpoint, bgwriter, jit, cost, work_mem) | pg_reload_conf() |
нет | postgresql.auto.conf |
ALTER SYSTEM RESET <param>; SELECT pg_reload_conf(); |
| PG restart-параметры (shared_buffers, huge_pages, max_connections, preload, wal_buffers, autovacuum_max_workers) | restart PG | 1–3 мин | conf.d/20-restart.conf |
переименовать файл в .disabled, restart |
vm.nr_hugepages |
sysctl (может не выделиться целиком) | нет | sysctl.d | vm.nr_hugepages=0 после перевода PG на huge_pages=try/off |
vm.overcommit_memory=2 |
sysctl | нет, но риск ENOMEM | sysctl.d | sysctl -w vm.overcommit_memory=0 |
| PgBouncer | новый сервис | нет | systemd + конфиг | вернуть приложениям port=5432 (до снижения max_connections!) |
| Netplan (hash policy) | netplan try |
секунды, риск потери связи | /etc/netplan |
автооткат netplan try через 120 с; нужен доступ через IPMI/iLO |
5.2 Основные риски и их снижение
- Снижение
max_connectionsдо полного перехода на PgBouncer приведёт к отказам в подключении. Порядок строго такой: пулер → миграция 100% клиентов → неделя наблюдения → снижение лимита. - Transaction mode ломает сессионную семантику. До переключения провести аудит кода на
SET, advisory locks,LISTEN, temp tables и прогнать регрессионные тесты приложения через PgBouncer на стенде. huge_pages = onпри нехватке страниц — PG не стартует и простой выходит за 15 минут. Поэтому в первом окнеtry, проверкаHugePages_Rsvd/HugePages_Free, и только потомon.overcommit_memory = 2при неверном расчёте — fork бэкендов и autovacuum падают с ENOMEM. Вводить отдельным шагом после двух недель мониторингаCommitted_AS.- Изменение mdadm bitmap — операция с метаданными массива. Выполнять только при состоянии
clean, свежем проверенном бэкапе и в окне. - Рост
max_wal_size— после сбоя восстановление дольше (до десятков GB WAL на NVMe — порядка 1–3 минут), плюс нужно место под WAL. Учесть в бюджете SLA. - Рестарт с 64 GB
shared_buffersи холодный кэш — первые 10–20 минут после старта латентность выше. Рестартовать только в ночном окне; опционально прогреть кэш черезpg_prewarm(расширение, autoprewarm черезshared_preload_libraries). - Длинный shutdown checkpoint — простой может превысить лимит. Перед рестартом вручную выполнить
CHECKPOINT;, тогда shutdown займёт секунды.
5.3 Процедура в окне обслуживания (сб → вс, 02:00–04:00, простой ≤ 15 мин)
# --- 02:00 подготовка (без простоя) ---
sudo etckeeper commit "before pg tuning window" || sudo tar czf /root/etc-$(date +%F).tgz /etc
sudo -u postgres psql -c "SELECT pg_switch_wal();" -c "CHECKPOINT;" # укоротить shutdown checkpoint
sync; echo 3 | sudo tee /proc/sys/vm/drop_caches # снизить фрагментацию перед выделением huge pages
sudo sysctl -p /etc/sysctl.d/60-hugepages.conf
grep HugePages_Total /proc/meminfo # убедиться, что выделено ≈ nr_hugepages
# --- 02:15 простой начинается ---
sudo -u postgres psql -c "CHECKPOINT;"
sudo cp /root/staging/20-restart.conf /etc/postgresql/14/main/conf.d/
sudo systemctl restart postgresql@14-main # обычно 30–90 с
# --- простой заканчивается ---
# --- проверка ---
sudo -u postgres psql -c "SHOW shared_buffers" -c "SHOW huge_pages" -c "SHOW max_connections"
grep -E "HugePages_(Total|Free|Rsvd)" /proc/meminfo # Rsvd > 0 — huge pages используются
tail -50 /var/log/postgresql/postgresql-14-main.log
# --- откат (если что-то не так; укладывается в 5 мин) ---
# sudo mv /etc/postgresql/14/main/conf.d/20-restart.conf{,.disabled}
# sudo systemctl restart postgresql@14-main
Перезагрузку ядра (GRUB: THP, C-states) и изменение mdadm bitmap лучше вынести в отдельное окно, чтобы не суммировать риски и простой в одну ночь.
Сводная таблица
| Узкое место | Причина | Действие | Ожидаемый эффект |
|---|---|---|---|
| Память → OOM, crash-restart PG | 5000 процессов-бэкендов × work_mem; OOM-killer бьёт по postmaster |
PgBouncer (transaction), max_connections = 400, OOMScoreAdjust, work_mem по ролям |
OOM = 0; −50…150 GB анонимной памяти; нет каскадных рестартов |
| Память → лишние чтения | Анонимная память вытесняет кэш; огромные PageTables |
Пулинг + huge pages + shared_buffers = 64GB, THP=never, swappiness = 1, NUMA-настройки |
Hit ratio > 99%, r/s на NVMe ↓, PageTables ↓ до < 1 GB |
| Диск: всплески записи | Checkpoint с малым max_wal_size; writeback 25/50 GB по умолчанию |
max_wal_size = 64GB, checkpoint_timeout = 15min, completion_target = 0.9, dirty_*_bytes, bgwriter |
w_await с 45 до < 2–3 мс, без «пилы»; p99 ↓ заметно |
| Диск: объём записи | FPW, temp-файлы, раздутые индексы | wal_compression, log_temp_files + work_mem, autovacuum cost, REINDEX CONCURRENTLY |
−20…40% объёма записи |
| Диск: накладные уровня md / ФС | Мелкий chunk bitmap, discard, resync в пик |
bitmap-chunk 128M, fstrim.timer, лимит speed_limit_max, noatime |
Стабильная латентность записи |
| CPU: латентность | Энергосбережение, глубокие C-states; JIT в OLTP | Governor performance, tuned latency-performance, jit = off |
−5…15% латентности коротких запросов, меньше хвостов |
| CPU: конкуренция | Тысячи процессов → переключения контекста, LWLock | Пулинг (150–250 активных) | cs/s ↓ в разы, ожидания LWLock ↓ |
| Сеть: TIME_WAIT | Короткоживущие клиентские соединения, PG закрывает первым | PgBouncer + keepalive; unix-сокет к PG | TIME_WAIT на :5432 ≈ 0, алерты прекращаются |
| Сеть: гигиена | Хеширование bond по layer2, малые backlog | layer3+4, somaxconn, netdev_max_backlog |
Равномерная загрузка слейвов, запас на всплески |
| Наблюдаемость | Нет данных по запросам, I/O timing | pg_stat_statements, auto_explain, track_io_timing, sysstat раз в минуту |
Точный таргетинг следующей итерации |
Чек-лист приоритетных действий
🟢 Быстрые победы (без простоя, в рабочее время, по одной группе в день с наблюдением в пик)
- Установить sysstat и bcc, запустить
baseline.shв пик 3 дня; собрать данные из раздела 2.3 - Выборка wait events и
pg_stat_activityпо состояниям — подтвердить гипотезу (IO + idle-подключения) - Найти процесс, убиваемый OOM; включить
OOMScoreAdjustдля postmaster - Проверить
/proc/mdstat, cron и таймеры на 18–23 ч; убрать бэкапы и проверки из пика -
vm.dirty_background_bytes/vm.dirty_bytes,swappiness = 1,zone_reclaim_mode = 0,numa_balancing = 0 - PG reload:
max_wal_size,checkpoint_timeout,checkpoint_completion_target,wal_compression, bgwriter,random_page_cost,effective_io_concurrency,jit = off, autovacuum cost, логирование - THP →
never(runtime), governor →performance/ tunedlatency-performance -
noatimeчерез remount; проверить отсутствиеdiscard, включитьfstrim.timer - Сетевые sysctl (backlog, keepalive); проверить хеширование bond
- Развернуть PgBouncer ≥ 1.21 на 6432 и перевести приложения канарейкой (10 → 50 → 100%)
🟡 Окно №1 (сб 02:00–04:00, рестарт PG ≈ 1–3 мин)
- После 100% миграции на PgBouncer:
max_connections = 400 -
shared_buffers = 64GB,wal_buffers = 64MB,vm.nr_hugepages,huge_pages = try -
shared_preload_libraries = pg_stat_statements, auto_explain;autovacuum_max_workers = 6 - Проверить
HugePages_Rsvdи логи; неделя наблюдения → в следующем окнеhuge_pages = on
🔴 Глубокие изменения (отдельные окна, после анализа результатов)
- GRUB:
transparent_hugepage=never,intel_idle.max_cstate=1(перезагрузка 5–10 мин); профиль BIOS — Performance - mdadm: увеличить bitmap-chunk до 128M (при clean-массиве и свежем бэкапе)
-
vm.overcommit_memory = 2с рассчитаннымovercommit_ratio— после двух недель мониторингаCommitted_AS - Работа с топом
pg_stat_statements: ANALYZE иdefault_statistics_targetдля перекошенных колонок,REINDEX CONCURRENTLYраздутых индексов, выборочныйsynchronous_commit = offдля некритичных записей - Согласовать с владельцами: допустим ли
CREATE INDEX CONCURRENTLYв рамках «без изменения схемы» — это может понадобиться, чтобы дойти до p99 150 мс, если причина в самих запросах
Итог. С высокой вероятностью основной выигрыш дадут три меры: пулинг подключений, укрощение checkpoint и writeback и huge pages вместе с корректным распределением памяти. Они снимают OOM, iowait и TIME_WAIT одновременно. Параметры в
postgresql.confи sysctl — это стартовые значения. Их нужно уточнить по фактическим данным из раздела 2.3, прежде всего по размеру БД, текущему конфигу и распределению wait events.
Похожие промпты
Ревью и рефакторинг PowerShell-скрипта
Разбирает существующий скрипт PowerShell: что сломается на реальных данных, где ошибки глушатся вместо обработки и что переписать в первую очередь, а что оставить.
Скрипт PowerShell под задачу
Пишет скрипт PowerShell с обработкой ошибок, логированием и режимом пробного прогона — так, чтобы его можно было запустить на рабочей машине и не бояться последствий.
Диагностика проблем VPN-подключения
Методично диагностирует проблемы VPN: соединение не устанавливается, скорость падает, утекает DNS — по одному шагу, с командами под вашу ОС.
Помощник по настройке WireGuard и OpenVPN
Пошагово разворачивает собственный VPN-сервер на WireGuard или OpenVPN: готовые команды и конфиги, firewall, клиентские профили и проверка утечек.