DBA на минималках #14: checkpoint-тюнинг

По умолчанию checkpoint_timeout — 5 минут, а max_wal_size — 1 GB: PostgreSQL запускает checkpoint каждые 5 минут или раньше, если накопилось больше гигабайта WAL. На нагруженной базе второе условие срабатывает первым, и checkpoint случается заметно чаще, чем каждые 5 минут — сервер постоянно сбрасывает грязные буферы на диск, и это видно по скачкам latency в самые непредсказуемые моменты. Проверить, насколько часто это происходит, можно по логу: если включить checkpoint_warning (по умолчанию 30 секунд) и checkpoint’ы начинают случаться чаще этого порога, PostgreSQL сам пишет в лог предупреждение, что max_wal_size стоит увеличить. Частое появление такого сообщения — не повод для паники с первого раза, но если оно встречается регулярно, значит параметры checkpoint пора пересматривать. ...

October 21, 2026 · llexa

DBA #13: декларативное партиционирование таблиц

До десятой версии PostgreSQL партиционирование собирали вручную через наследование таблиц и триггеры на INSERT — рабочая, но хрупкая конструкция, которую легко сломать неосторожным DDL. Декларативное партиционирование сделало то же самое штатным синтаксисом: секции объявляются прямо при создании таблицы, а планировщик сам знает о структуре и использует её при построении плана запроса. Синтаксис для диапазонов по дате — обычное дело для таблиц с логами или метриками: CREATE TABLE measurement ( city_id int NOT NULL, logdate date NOT NULL, peaktemp int, unitsales int ) PARTITION BY RANGE (logdate); CREATE TABLE measurement_y2026m10 PARTITION OF measurement FOR VALUES FROM ('2026-10-01') TO ('2026-11-01'); Для PARTITION BY LIST значения перечисляются явно, что удобно для партиционирования по региону или арендатору (tenant) в мультитенантной схеме: ...

October 14, 2026 · llexa

DBA #12: логическая репликация — репликация на уровне строк

Обычная streaming-репликация в PostgreSQL копирует кластер байт-в-байт — WAL одинаковый на всех репликах, и реплика обязана быть той же мажорной версии, что и мастер. Логическая репликация работает иначе: она декодирует WAL в поток изменений строк (insert/update/delete) и применяет их через обычный SQL. Из-за этого можно реплицировать не весь кластер, а отдельные таблицы, и версии publisher/subscriber могут отличаться — это штатный способ мигрировать между мажорными релизами без pg_upgrade и почти без простоя. ...

October 7, 2026 · llexa

DBA #11: pg_stat_statements — находим медленные запросы без внешних инструментов

Разбирать log_min_duration_statement через pgBadger — рабочий, но окольный путь узнать, какие запросы реально тормозят продакшен. pg_stat_statements считает эту статистику сам, в реальном времени, без парсинга логов. ...

September 30, 2026 · llexa

DBA #10: transaction ID wraparound — почему PostgreSQL сам себя вакуумит

32-битный счётчик транзакций (XID) циклический: оборот — раз в 4 млрд, а 2 млрд — граница, после которой без vacuum’а старые строки рискуют оказаться “из будущего”. Отсюда суета вокруг wraparound. autovacuum_freeze_max_age (по умолчанию 200 млн) форсирует anti-wraparound autovacuum на старых таблицах, даже при выключенном autovacuum в конфиге — единственное исключение из настройки. Кто горит — видно запросом: SELECT c.oid::regclass AS table_name, greatest(age(c.relfrozenxid), age(t.relfrozenxid)) AS age FROM pg_class c LEFT JOIN pg_class t ON c.reltoastrelid = t.oid WHERE c.relkind IN ('r', 'm'); Warning в логах (“must be vacuumed within N transactions”) — за 40 млн до порога, запас ещё есть. Проигнорировать — база уходит в read-only при <3 млн до wraparound, выход один - вакуум от суперпользователя. ...

September 23, 2026 · llexa

DBA на минималках #8: VACUUM FULL и pg_repack

Обычный autovacuum помечает мёртвые строки как свободное место, но не возвращает его файловой системе — таблица физически не уменьшается. VACUUM FULL пересобирает таблицу с нуля и реально сжимает файл на диске, но берёт ACCESS EXCLUSIVE lock на всё время работы — для большой таблицы это часы простоя. pg_repack (расширение + CLI) делает то же без долгой блокировки: создаёт копию таблицы, применяет накопленные за время копирования изменения через триггер, и подменяет таблицу коротким ACCESS EXCLUSIVE только на финальный swap — секунды, а не часы. ...

September 9, 2026 · llexa

pgbouncer: пул соединений перед PostgreSQL

PostgreSQL на каждое соединение форкает отдельный backend-процесс — при сотнях коротких подключений (веб-запросы, serverless-функции) это съедает память и CPU на сам факт подключения, ещё до первого запроса. pgbouncer держит пул готовых соединений к базе и раздаёт их клиентам. [databases] mydb = host=127.0.0.1 port=5432 dbname=mydb [pgbouncer] listen_port = 6432 pool_mode = transaction max_client_conn = 1000 default_pool_size = 20 Режимы pool_mode: session — за клиентом до disconnect, совместимо со всем (advisory locks, prepared statements) transaction — на время транзакции, самый экономный, но ломает session-level фичи statement — на один запрос, только для read-only трафика psql -h 127.0.0.1 -p 6432 -U app mydb # подключение через pgbouncer, не напрямую в базу #postgresql #dba ...

September 2, 2026 · llexa

pgbouncer: пул соединений перед PostgreSQL

PostgreSQL на каждое соединение форкает отдельный backend-процесс — при сотнях коротких подключений (веб-запросы, serverless-функции) это съедает память и CPU на сам факт подключения, ещё до первого запроса. pgbouncer держит пул готовых соединений к базе и раздаёт их клиентам. [databases] mydb = host=127.0.0.1 port=5432 dbname=mydb [pgbouncer] listen_port = 6432 pool_mode = transaction max_client_conn = 1000 default_pool_size = 20 Режимы pool_mode: session — за клиентом до disconnect, совместимо со всем (advisory locks, prepared statements) transaction — на время транзакции, самый экономный, но ломает session-level фичи statement — на один запрос, только для read-only трафика psql -h 127.0.0.1 -p 6432 -U app mydb # подключение через pgbouncer, не напрямую в базу #postgresql #dba ...

August 4, 2026 · llexa

pg_locks: кто кого блокирует в PostgreSQL

Запрос висит, в логах тихо — почти всегда блокировка. pg_stat_activity покажет статус active, но не покажет, кто держит блокировку. Для этого — pg_locks. -- цепочка блокировок без ручных JOIN SELECT pid, pg_blocking_pids(pid) AS blocked_by, query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0; SELECT pid, locktype, relation::regclass, mode, granted FROM pg_locks WHERE NOT granted; granted = false — это и есть ожидающий процесс. Row-level локи (RowExclusiveLock от UPDATE) конфликтуют не со всеми — а AccessExclusiveLock (от ALTER TABLE, VACUUM FULL) блокирует вообще всё, включая SELECT. DDL в очереди на busy-таблице сам блокирует все следующие запросы, даже read-only. ...

July 29, 2026 · llexa