PostgreSQL Admin

PostgreSQL — объектно-реляционная СУБД. Администратор отвечает за конфигурацию сервера, управление ролями и правами, репликацию, производительность, обслуживание таблиц и контроль выполнения запросов.

Примеры рассчитаны на современные версии PostgreSQL. Перед изменениями в production проверяйте документацию установленной версии и готовьте план отката.

Содержание


Подключение и первичная диагностика

Подключение через psql

sudo -u postgres psql
psql -h 127.0.0.1 -p 5432 -U app_user -d app_db -W

Параметры можно задавать переменными окружения:

export PGHOST=127.0.0.1
export PGPORT=5432
export PGDATABASE=app_db
export PGUSER=app_user
psql

Для автоматизации лучше использовать ~/.pgpass, а не передавать пароль в командной строке:

hostname:port:database:username:password
127.0.0.1:5432:app_db:app_user:strong-password
chmod 600 ~/.pgpass

Полезные команды psql

\conninfo       -- текущее подключение
\l              -- базы данных
\c app_db       -- подключиться к базе
\dn             -- схемы
\dt             -- таблицы
\d+ orders      -- структура таблицы
\di             -- индексы
\du+            -- роли
\dp             -- права доступа
\x              -- расширенный вывод
\timing on      -- время выполнения
\q              -- выйти

Проверка состояния

SELECT version();
SELECT current_database(), current_user, current_schema();
SHOW server_version;
SHOW data_directory;
SHOW config_file;
SHOW hba_file;
SHOW port;

Активные подключения и ожидания:

SELECT pid, usename, datname, client_addr, application_name,
       state, wait_event_type, wait_event, query_start,
       LEFT(query, 160) AS query
FROM pg_stat_activity
ORDER BY query_start NULLS LAST;

Настройка postgresql.conf

postgresql.conf содержит сетевые параметры, настройки памяти, WAL, журналирования, планировщика и autovacuum.

Просмотр и изменение параметров

SELECT name, setting, unit, source, context, pending_restart
FROM pg_settings
WHERE name IN (
  'listen_addresses', 'port', 'max_connections',
  'shared_buffers', 'work_mem', 'maintenance_work_mem',
  'effective_cache_size', 'wal_level', 'max_wal_size',
  'shared_preload_libraries'
)
ORDER BY name;

Изменение на уровне сервера:

ALTER SYSTEM SET work_mem = '16MB';
ALTER SYSTEM SET log_min_duration_statement = '500ms';
SELECT pg_reload_conf();

ALTER SYSTEM записывает настройки в postgresql.auto.conf. Для отдельной базы или роли используются:

ALTER DATABASE app_db SET timezone = 'UTC';
ALTER ROLE app_user SET statement_timeout = '30s';

Параметры, требующие перезапуска:

SELECT name, setting, pending_restart
FROM pg_settings
WHERE pending_restart = true;

Сеть и подключения

listen_addresses = '127.0.0.1,10.10.0.5'
port = 5432
max_connections = 200

Не увеличивайте max_connections без расчёта памяти. Большое число клиентов часто лучше обслуживать через пул подключений, например PgBouncer.

Память

shared_buffers = 25% от RAM как отправная точка
work_mem = 16MB
maintenance_work_mem = 256MB
effective_cache_size = 50-75% от RAM как оценка кеша ОС и PostgreSQL

work_mem выделяется отдельно для каждой сортировки или hash-операции каждого процесса. Фактическое потребление может быть намного выше значения параметра, поэтому его нельзя механически умножать только на число подключений.

WAL и checkpoints

wal_level = replica
max_wal_size = 4GB
min_wal_size = 1GB
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9

WAL нужен для восстановления после сбоя и репликации. Слишком частые checkpoints вызывают всплески I/O. Подбирайте параметры по журналам checkpoints, нагрузке и скорости диска.

Логирование

logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_line_prefix = '%m [%p] %u@%d %r '
log_checkpoints = on
log_lock_waits = on
log_temp_files = 10MB
log_min_duration_statement = 500ms

log_statement = 'all' не следует постоянно включать на нагруженной production-базе: объём логов может резко вырасти. Для slow query обычно подходят log_min_duration_statement и pg_stat_statements.

Тайм-ауты

ALTER ROLE app_user SET statement_timeout = '30s';
ALTER ROLE app_user SET lock_timeout = '5s';
ALTER ROLE app_user SET idle_in_transaction_session_timeout = '60s';

Для миграций и ETL задавайте отдельные значения, если им требуется больше времени.

pg_stat_statements

Для статистики запросов добавьте расширение в shared_preload_libraries и перезапустите сервер:

shared_preload_libraries = 'pg_stat_statements'

В базе:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

Самые затратные запросы:

SELECT calls, total_exec_time, mean_exec_time, rows,
       shared_blks_hit, shared_blks_read,
       LEFT(query, 240) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

Расширение помогает выбрать запросы для детального анализа, но не заменяет EXPLAIN.


Пользователи, роли и права

Пользователь PostgreSQL — это роль с атрибутом LOGIN. Роль без LOGIN удобно использовать как группу прав.

Создание ролей

CREATE ROLE app_owner NOLOGIN;
CREATE ROLE app_runtime NOLOGIN;
CREATE ROLE app_migrations NOLOGIN;
CREATE ROLE app_user LOGIN PASSWORD 'from-secret-manager';

GRANT app_runtime TO app_user;

В production не храните пароли в Git, SQL-файлах или CI-логах. Пароль можно задать интерактивно:

\password app_user

Атрибуты роли:

ALTER ROLE app_user CONNECTION LIMIT 50;
ALTER ROLE app_user SET statement_timeout = '30s';
ALTER ROLE app_user NOLOGIN; -- запретить вход

Список ролей:

SELECT rolname, rolcanlogin, rolsuper, rolcreatedb,
       rolcreaterole, rolreplication, rolbypassrls, rolconnlimit
FROM pg_roles
ORDER BY rolname;

Принцип наименьших привилегий

Приложению обычно не нужен superuser. Выдайте только необходимые права:

GRANT CONNECT ON DATABASE app_db TO app_user;
GRANT USAGE ON SCHEMA app TO app_runtime;
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA app TO app_runtime;
GRANT USAGE, SELECT
ON ALL SEQUENCES IN SCHEMA app TO app_runtime;

Права для будущих объектов:

ALTER DEFAULT PRIVILEGES FOR ROLE app_migrations IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_runtime;

ALTER DEFAULT PRIVILEGES FOR ROLE app_migrations IN SCHEMA app
GRANT USAGE, SELECT ON SEQUENCES TO app_runtime;

Важно: ALTER DEFAULT PRIVILEGES действует для объектов, создаваемых указанной ролью.

Read-only роль:

CREATE ROLE app_readonly LOGIN PASSWORD 'from-secret-manager';
GRANT CONNECT ON DATABASE app_db TO app_readonly;
GRANT USAGE ON SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE app_migrations IN SCHEMA app
GRANT SELECT ON TABLES TO app_readonly;

Права можно проверить так:

SELECT grantee, table_schema, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee IN ('app_runtime', 'app_readonly')
ORDER BY grantee, table_schema, table_name;

Не выдавайте приложению SUPERUSER, CREATEDB, CREATEROLE, REPLICATION или BYPASSRLS, если это не требуется отдельной задаче.


Безопасность подключений: pg_hba.conf

pg_hba.conf определяет, кто и откуда может подключаться, к какой базе и каким способом аутентифицироваться.

# TYPE  DATABASE  USER          ADDRESS          METHOD
local   all       postgres                       peer
host    app_db    app_user      10.10.0.0/16     scram-sha-256
host    app_db    app_readonly  10.10.0.0/16     scram-sha-256
host    all       all           0.0.0.0/0        reject

Для новых конфигураций предпочтителен scram-sha-256. Порядок правил имеет значение: применяется первая подходящая строка.

После изменения:

SELECT pg_reload_conf();

SELECT line_number, type, database, user_name,
       address, auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;

Не открывайте порт PostgreSQL в интернет без необходимости. Ограничивайте доступ firewall, security groups, VPN или приватной сетью.


Репликация: базово

Репликация передаёт изменения с primary на standby. Она используется для высокой доступности и чтения с реплики, но не заменяет backup и point-in-time recovery.

Основные термины

Настройка primary

wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1GB

Разрешение в pg_hba.conf только для standby:

host replication replicator 10.10.0.20/32 scram-sha-256

Отдельная роль репликации:

CREATE ROLE replicator
WITH REPLICATION LOGIN PASSWORD 'from-secret-manager';

Создание standby через base backup

pg_basebackup \
  -h primary.internal \
  -U replicator \
  -D /var/lib/postgresql/data \
  -Fp \
  -Xs \
  -P \
  -R

-R создаёт настройки подключения к primary для standby. Пароль передавайте через .pgpass с правами 600 или через секрет-хранилище.

Состояние primary:

SELECT client_addr, application_name, state, sync_state,
       sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;

Состояние standby:

SELECT pg_is_in_recovery();

SELECT status, sender_host, sender_port,
       latest_end_lsn, latest_end_time
FROM pg_stat_wal_receiver;

Репликационный lag

SELECT client_addr, application_name, state,
       pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn))
       AS replay_lag
FROM pg_stat_replication;

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

Replication slots

SELECT slot_name, slot_type, active, restart_lsn,
       wal_status, safe_wal_size
FROM pg_replication_slots;

Неиспользуемый слот может заполнить диск WAL. Удаляйте его только после проверки:

SELECT pg_drop_replication_slot('old_slot');

Ошибочный DELETE обычно реплицируется на standby, поэтому реплика не заменяет backup.


Мониторинг производительности

Оценивайте latency, throughput, блокировки, cache hit ratio, I/O, checkpoints, vacuum, рост таблиц и число подключений вместе, а не по одному показателю.

Активные запросы и долгие транзакции

SELECT pid, usename, datname, state,
       wait_event_type, wait_event,
       now() - query_start AS duration,
       LEFT(query, 300) AS query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY duration DESC;
SELECT pid, usename, datname, xact_start,
       now() - xact_start AS xact_age,
       state, LEFT(query, 200) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;

Долгие транзакции удерживают старые версии строк и могут мешать vacuum.

Блокировки

SELECT blocked.pid AS blocked_pid,
       blocking.pid AS blocking_pid,
       now() - blocked.query_start AS blocked_for,
       LEFT(blocked.query, 200) AS blocked_query,
       LEFT(blocking.query, 200) AS blocking_query
FROM pg_stat_activity AS blocked
JOIN pg_locks AS blocked_lock
  ON blocked_lock.pid = blocked.pid
 AND NOT blocked_lock.granted
JOIN pg_locks AS blocking_lock
  ON blocking_lock.locktype = blocked_lock.locktype
 AND blocking_lock.database IS NOT DISTINCT FROM blocked_lock.database
 AND blocking_lock.relation IS NOT DISTINCT FROM blocked_lock.relation
 AND blocking_lock.page IS NOT DISTINCT FROM blocked_lock.page
 AND blocking_lock.tuple IS NOT DISTINCT FROM blocked_lock.tuple
 AND blocking_lock.virtualxid IS NOT DISTINCT FROM blocked_lock.virtualxid
 AND blocking_lock.transactionid IS NOT DISTINCT FROM blocked_lock.transactionid
 AND blocking_lock.classid IS NOT DISTINCT FROM blocked_lock.classid
 AND blocking_lock.objid IS NOT DISTINCT FROM blocked_lock.objid
 AND blocking_lock.objsubid IS NOT DISTINCT FROM blocked_lock.objsubid
 AND blocking_lock.granted
JOIN pg_stat_activity AS blocking
  ON blocking.pid = blocking_lock.pid
WHERE blocked.pid <> blocking.pid;

Для отдельного процесса:

SELECT pg_blocking_pids(<blocked_pid>);

Статистика таблиц

SELECT schemaname, relname, n_live_tup, n_dead_tup,
       n_mod_since_analyze, last_autovacuum,
       last_autoanalyze, autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;

Большое количество n_dead_tup может указывать на неэффективный autovacuum, долгие транзакции или большое число обновлений и удалений.

Использование индексов

SELECT schemaname, relname AS table_name,
       indexrelname AS index_name, idx_scan,
       pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;

Нулевой idx_scan не означает, что индекс можно немедленно удалить: статистика могла сброситься, а индекс может поддерживать уникальность или редкий важный запрос.

Cache hit ratio и размеры

SELECT datname, blks_hit, blks_read,
       round(100.0 * blks_hit / NULLIF(blks_hit + blks_read, 0), 2)
       AS cache_hit_percent
FROM pg_stat_database
WHERE datname IS NOT NULL;
SELECT schemaname, relname,
       pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
       pg_size_pretty(pg_table_size(relid)) AS table_size,
       pg_size_pretty(pg_indexes_size(relid)) AS indexes_size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 20;

Вакуумирование и обслуживание

PostgreSQL использует MVCC: после UPDATE или DELETE старые версии строк не всегда удаляются немедленно. Их очищает vacuum, когда они больше не нужны активным транзакциям.

VACUUM, VACUUM FULL, ANALYZE

VACUUM app.orders;
VACUUM (VERBOSE, ANALYZE) app.orders;
ANALYZE app.orders;

Обычный VACUUM возвращает место для повторного использования внутри таблицы и обычно не блокирует обычную работу надолго. Он не обязан уменьшать физический файл на диске.

VACUUM (FULL, VERBOSE, ANALYZE) app.orders;

VACUUM FULL переписывает таблицу, требует сильную блокировку и дополнительное место. Это не обычная ежедневная операция production. Для контролируемого уменьшения также применяют инструменты вроде pg_repack, проверив их ограничения.

ANALYZE собирает статистику для планировщика:

ALTER TABLE app.orders
ALTER COLUMN customer_id SET STATISTICS 500;
ANALYZE app.orders;

Autovacuum

SELECT name, setting, unit
FROM pg_settings
WHERE name LIKE 'autovacuum%'
ORDER BY name;

Пример отправных параметров:

autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min
autovacuum_vacuum_scale_factor = 0.2
autovacuum_analyze_scale_factor = 0.1

Для активно изменяемой таблицы настройки можно переопределить:

ALTER TABLE app.events SET (
    autovacuum_vacuum_scale_factor = 0.02,
    autovacuum_analyze_scale_factor = 0.01,
    autovacuum_vacuum_threshold = 5000,
    autovacuum_analyze_threshold = 5000
);

Слишком агрессивный autovacuum увеличивает I/O, а слишком редкий приводит к росту таблиц и устаревшей статистике. Подбирайте параметры по скорости накопления dead tuples.

Freeze и wraparound

Не отключайте autovacuum без отдельного плана обслуживания. Проверяйте возраст XID:

SELECT datname, age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;
SELECT schemaname, relname, age(relfrozenxid) AS xid_age,
       n_live_tup, n_dead_tup
FROM pg_stat_all_tables
ORDER BY age(relfrozenxid) DESC
LIMIT 20;

Обслуживание индексов

REINDEX INDEX CONCURRENTLY app.orders_customer_created_idx;
REINDEX TABLE CONCURRENTLY app.orders;

CONCURRENTLY уменьшает блокирование, но требует больше времени и места и не выполняется внутри транзакционного блока.


Продвинутая оптимизация индексов

Индекс ускоряет чтение, но занимает место и увеличивает стоимость INSERT, UPDATE и DELETE. Его нужно подбирать по реальным шаблонам запросов.

B-tree и составные индексы

CREATE INDEX orders_customer_created_idx
ON app.orders (customer_id, created_at DESC);

Порядок столбцов важен. Такой индекс подходит для фильтрации по customer_id и сортировки по created_at. Индекс (created_at, customer_id) может вести себя иначе для тех же условий.

Типы индексов

CREATE INDEX products_attributes_gin_idx
ON app.products USING gin (attributes);

CREATE INDEX reservations_period_gist_idx
ON app.reservations USING gist (reserved_period);

CREATE INDEX events_created_brin_idx
ON app.events USING brin (created_at);

Частичный индекс

CREATE INDEX users_active_email_idx
ON app.users (email)
WHERE deleted_at IS NULL;

Запрос должен содержать совместимое условие:

SELECT id, email
FROM app.users
WHERE deleted_at IS NULL
  AND email = 'user@example.com';

Индекс по выражению

CREATE INDEX users_lower_email_idx
ON app.users (lower(email));
SELECT *
FROM app.users
WHERE lower(email) = lower('User@example.com');

Covering index и INCLUDE

CREATE INDEX orders_customer_created_cover_idx
ON app.orders (customer_id, created_at DESC)
INCLUDE (status, total_amount);

INCLUDE может сделать возможным Index Only Scan, но это зависит от visibility map, стоимости плана и актуальности страниц.

Уникальность и ограничения

Для бизнес-правил используйте ограничения:

ALTER TABLE app.users
ADD CONSTRAINT users_email_unique UNIQUE (email);

Индекс ограничения нельзя удалять отдельно, не изменив само ограничение.

Keyset-pagination

Большой OFFSET может быть дорогим:

SELECT id, created_at
FROM app.events
ORDER BY created_at DESC, id DESC
LIMIT 50 OFFSET 100000;

Для keyset-pagination:

CREATE INDEX events_created_id_idx
ON app.events (created_at DESC, id DESC);
SELECT id, created_at
FROM app.events
WHERE (created_at, id) < ('2026-09-21 12:00:00+00', 5000)
ORDER BY created_at DESC, id DESC
LIMIT 50;

Нерациональные и дублирующиеся индексы

SELECT schemaname, relname AS table_name,
       indexrelname AS index_name, idx_scan,
       pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

Перед удалением проверьте период сбора статистики, ограничения, редкие запросы, отчёты, порядок колонок, WHERE, INCLUDE и тип индекса. Дублирующий индекс увеличивает стоимость записи без пользы для чтения.


EXPLAIN и EXPLAIN ANALYZE

EXPLAIN показывает выбранный план. EXPLAIN ANALYZE фактически выполняет запрос и добавляет измеренные значения.

EXPLAIN
SELECT *
FROM app.orders
WHERE customer_id = 42;
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, SETTINGS)
SELECT id, created_at, total_amount
FROM app.orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;

Условный план:

Limit  (cost=0.42..12.10 rows=20 width=32)
       (actual time=0.080..0.120 rows=20 loops=1)
  ->  Index Scan using orders_customer_created_idx on orders
      (cost=0.42..1200.00 rows=2000 width=32)
      (actual time=0.078..0.116 rows=20 loops=1)
      Index Cond: (customer_id = 42)
Planning Time: 0.400 ms
Execution Time: 0.150 ms

Основные узлы

Оценка строк и статистика

Сравнивайте rows и actual rows:

(actual time=10.000..50.000 rows=5 loops=1)

Сильное расхождение означает устаревшую статистику или сложное распределение данных:

ANALYZE app.orders;

ALTER TABLE app.orders
ALTER COLUMN customer_id SET STATISTICS 500;

ANALYZE app.orders;

Для связанных столбцов можно создать расширенную статистику:

CREATE STATISTICS orders_customer_status_stats
    (dependencies, ndistinct, mcv)
ON customer_id, status
FROM app.orders;

ANALYZE app.orders;

BUFFERS, WAL и временные файлы

EXPLAIN (ANALYZE, BUFFERS, WAL)
UPDATE app.orders
SET status = 'archived'
WHERE created_at < now() - interval '1 year';

Большой temp read или temp written может указывать на сортировку или hash-операцию, не поместившуюся в work_mem.

EXPLAIN ANALYZE для INSERT, UPDATE и DELETE изменяет данные. Для теста можно использовать транзакцию:

BEGIN;
EXPLAIN (ANALYZE, BUFFERS)
DELETE FROM app.orders WHERE id = 123;
ROLLBACK;

Даже внутри транзакции возможны блокировки и запуск триггеров, поэтому на production нужна осторожность.

Что проверять в плане

  1. Соответствует ли оценка rows фактическим строкам.
  2. Нет ли неожиданного полного сканирования большой таблицы.
  3. Нет ли сортировок с временными файлами.
  4. Подходят ли порядок и состав индексов.
  5. Не повторяется ли дорогой узел из-за большого loops.
  6. Нет ли неявного приведения типов.
  7. Актуальна ли статистика.
  8. Не ухудшил ли индекс запись и autovacuum.

Seq Scan не всегда означает проблему: для маленькой таблицы или чтения большей части данных он может быть оптимальным.


Диагностика медленного запроса

1. Найти запрос

SELECT calls, mean_exec_time, total_exec_time, rows,
       LEFT(query, 500) AS query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

2. Получить план на репрезентативных параметрах

EXPLAIN (ANALYZE, BUFFERS, SETTINGS)
SELECT id, status, created_at
FROM app.orders
WHERE customer_id = 42
  AND status = 'paid'
ORDER BY created_at DESC
LIMIT 100;

3. Проверить статистику

ANALYZE app.orders;

4. Внести одно изменение

CREATE INDEX CONCURRENTLY orders_paid_customer_created_idx
ON app.orders (customer_id, created_at DESC)
WHERE status = 'paid';

5. Сравнить результат

Сравнивайте не только latency, но и нагрузку на запись, размер индекса, I/O, autovacuum и планы похожих запросов на разных параметрах.

6. Зафиксировать решение

Храните исходный план, описание проблемы, изменение, новый план, дату и условия теста. Это помогает откатывать неудачные оптимизации.


Шпаргалка команд

-- Сервер и конфигурация
SELECT version();
SHOW ALL;
SELECT * FROM pg_settings WHERE name = 'shared_buffers';
SELECT pg_reload_conf();

-- Подключения и блокировки
SELECT * FROM pg_stat_activity;
SELECT pg_blocking_pids(<pid>);
SELECT * FROM pg_locks;

-- Размеры
SELECT pg_size_pretty(pg_database_size(current_database()));
SELECT pg_size_pretty(pg_total_relation_size('app.orders'));
SELECT pg_size_pretty(pg_relation_size('app.orders'));
SELECT pg_size_pretty(pg_indexes_size('app.orders'));

-- Обслуживание
VACUUM (ANALYZE) app.orders;
ANALYZE app.orders;
REINDEX INDEX CONCURRENTLY app.orders_customer_created_idx;

-- Диагностика
EXPLAIN (ANALYZE, BUFFERS, SETTINGS)
SELECT ...;

-- Репликация
SELECT pg_is_in_recovery();
SELECT * FROM pg_stat_replication;
SELECT * FROM pg_stat_wal_receiver;
SELECT * FROM pg_replication_slots;

Итоговый чек-лист

PostgreSQL администрируется как система взаимосвязанных практик: корректные права, контролируемая конфигурация, актуальная статистика, рабочий autovacuum, мониторинг, проверенные планы запросов и регулярно протестированное восстановление.