Партиционирование, репликация, connection pooling, full-text search
Партиционирование, репликация, полнотекстовый поиск и другие продвинутые возможности PostgreSQL.
Зачем это нужно? Когда ваши таблицы растут до миллионов строк, а нагрузка увеличивается, базовых оптимизаций становится недостаточно. Этот материал научит вас масштабировать PostgreSQL для высоких нагрузок.
Партиционирование — это разделение одной большой таблицы на меньшие физические части (партиции). Для приложения таблица остаётся единой, но PostgreSQL хранит и обрабатывает данные раздельно.
| Признак | Решение |
|---|---|
| Таблица > 10-50 ГБ | ✅ Рассмотреть партиционирование |
| Таблица > 100 млн строк | ✅ Сильно рекомендуется |
| Есть «горячие» данные (последние N дней) | ✅ Идеально |
| Нужно быстро удалять старые данные | ✅ DROP PARTITION быстрее DELETE |
| Таблица растёт со временем | ✅ Да |
Важно: Не партиционируйте маленькие таблицы! Партиционирование добавляет накладные расходы на планирование запросов.
PostgreSQL 10+ поддерживает декларативное партиционирование — вы объявляете таблицу партиционированной, а PostgreSQL автоматически направляет запросы к нужным партициям.
-- Создание партиционированной таблицы
CREATE TABLE sensor_data (
id SERIAL,
sensor_id INTEGER NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
value NUMERIC
) PARTITION BY RANGE (timestamp);
-- Создание партиций
CREATE TABLE sensor_data_2026_01
PARTITION OF sensor_data
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE sensor_data_2026_02
PARTITION OF sensor_data
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
-- Партиция по умолчанию (для данных вне диапазонов)
CREATE TABLE sensor_data_default
PARTITION OF sensor_data
DEFAULT;Что происходит внутри:
INSERT PostgreSQL автоматически определяет, в какую партицию вставить строкуSELECT PostgreSQL пропускает ненужные партиции (partition pruning)Самый популярный тип — для временных рядов и последовательных данных:
-- Партиционирование по дате
CREATE TABLE events (
id BIGSERIAL,
event_type TEXT,
created_at TIMESTAMPTZ NOT NULL
) PARTITION BY RANGE (created_at);
-- Месячные партиции
CREATE TABLE events_2026_01
PARTITION OF events
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE events_2026_02
PARTITION OF events
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
-- Дневные партиции (для очень больших объёмов)
CREATE TABLE events_2026_01_01
PARTITION OF events
FOR VALUES FROM ('2026-01-01') TO ('2026-01-02');Для дискретных значений — категории, регионы, статусы:
-- Партиционирование по стране
CREATE TABLE users (
id INTEGER,
name TEXT,
country TEXT NOT NULL
) PARTITION BY LIST (country);
-- Партиции по регионам
CREATE TABLE users_ru PARTITION OF users
FOR VALUES IN ('RU', 'BY', 'KZ', 'UA');
CREATE TABLE users_eu PARTITION OF users
FOR VALUES IN ('DE', 'FR', 'ES', 'IT', 'PL');
CREATE TABLE users_us PARTITION OF users
FOR VALUES IN ('US', 'CA');
-- Партиция по умолчанию
CREATE TABLE users_other PARTITION OF users
DEFAULT;Для равномерного распределения данных без явной логики:
-- Распределение по 4 партициям
CREATE TABLE events (
id INTEGER,
event_type TEXT NOT NULL
) PARTITION BY HASH (event_type);
CREATE TABLE events_part_0 PARTITION OF events
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE events_part_1 PARTITION OF events
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE events_part_2 PARTITION OF events
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE events_part_3 PARTITION OF events
FOR VALUES WITH (MODULUS 4, REMAINDER 3);Зачем? Если у вас 100 типов событий и нет явного разделения, HASH равномерно распределит строки.
Можно комбинировать типы для более детального разделения:
-- Сначала по RANGE (год), потом по LIST (регион)
CREATE TABLE sales (
id SERIAL,
region TEXT NOT NULL,
sale_date DATE NOT NULL,
amount NUMERIC
) PARTITION BY RANGE (sale_date);
-- Годовые партиции
CREATE TABLE sales_2025
PARTITION OF sales
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01')
PARTITION BY LIST (region);
-- Региональные под-партиции
CREATE TABLE sales_2025_ru
PARTITION OF sales_2025
FOR VALUES IN ('RU', 'BY', 'KZ');
CREATE TABLE sales_2025_us
PARTITION OF sales_2025
FOR VALUES IN ('US', 'CA');| Преимущество | Объяснение |
|---|---|
| 🚀 Partition Pruning | PostgreSQL пропускает ненужные партиции при запросах |
| 🗑️ Быстрое удаление | DROP TABLE партиции мгновенно, в отличие от DELETE |
| 📊 Локальные индексы | Индексы на каждой партиции меньше и эффективнее |
| 🔧 Обслуживание | Можно делать VACUUM/ANALYZE на отдельных партициях |
| 📈 Параллелизм | Разные воркеры могут обрабатывать разные партиции |
-- Запрос с фильтром по дате
EXPLAIN (COSTS OFF)
SELECT * FROM sensor_data
WHERE timestamp >= '2026-02-01'
AND timestamp < '2026-03-01';
-- Вывод:
-- Seq Scan on sensor_data_2026_02 -- Только одна партиция!
-- Filter: (timestamp >= '2026-02-01' ...)
-- Запрос без фильтра (сканирует все партиции)
EXPLAIN (COSTS OFF)
SELECT * FROM sensor_data;
-- Вывод:
-- Append
-- -> Seq Scan on sensor_data_2026_01
-- -> Seq Scan on sensor_data_2026_02
-- -> Seq Scan on sensor_data_2026_03-- Посмотреть все партиции таблицы
SELECT
inhparent::regclass AS parent,
inhrelid::regclass AS partition
FROM pg_inherits
WHERE inhparent = 'sensor_data'::regclass
ORDER BY inhrelid::regclass::text;
-- Добавить новую партицию
CREATE TABLE sensor_data_2026_03
PARTITION OF sensor_data
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
-- Удалить старую партицию (данные удаляются!)
DROP TABLE sensor_data_2025_01;
-- Отсоединить партицию (данные сохраняются)
ALTER TABLE sensor_data
DETACH PARTITION sensor_data_2025_01;
-- Присоединить партицию обратно
ALTER TABLE sensor_data
ATTACH PARTITION sensor_data_2025_01
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');Триггер для автоматического создания месячных партиций:
CREATE OR REPLACE FUNCTION create_monthly_partition()
RETURNS TRIGGER AS $$
DECLARE
partition_name TEXT;
start_date DATE;
end_date DATE;
BEGIN
start_date := DATE_TRUNC('month', NEW.timestamp);
end_date := start_date + INTERVAL '1 month';
partition_name := 'sensor_data_' || to_char(start_date, 'YYYY_MM');
-- Создаём партицию если не существует
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF sensor_data
FOR VALUES FROM (%L) TO (%L)',
partition_name, start_date, end_date
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Триггер срабатывает перед вставкой
CREATE TRIGGER create_partition_trigger
BEFORE INSERT ON sensor_data
FOR EACH ROW
EXECUTE FUNCTION create_monthly_partition();Лучшая практика: Создавайте партиции заранее (например, на месяц вперёд) через cron или pg_cron, а не полагайтесь на триггеры.
PostgreSQL предоставляет мощный движок полнотекстового поиска без необходимости устанавливать внешние решения вроде Elasticsearch.
tsvector — нормализованное представление текста для поиска:
tsquery — поисковый запрос с операторами:
& — AND (и)| — OR (или)! — NOT (не)<-> — NEAR (рядом)-- Создание tsvector
SELECT to_tsvector('russian', 'Бегущая собака ловит мышь');
-- 'бегущ' 'лов' 'мыш' 'собак'
-- Создание tsquery
SELECT to_tsquery('russian', 'бежать & собака');
-- 'бежать' & 'собак'
-- Поиск
SELECT to_tsvector('russian', 'Бегущая собака') @@
to_tsquery('russian', 'бежать & собака');
-- TRUE (найдено!)-- Добавляем столбец для поискового вектора
ALTER TABLE articles ADD COLUMN search_vector tsvector;
-- Заполняем вектор (объединяем заголовок и контент)
UPDATE articles SET search_vector =
to_tsvector('russian', COALESCE(title, '') || ' ' || COALESCE(content, ''));
-- Создаём GIN-индекс для быстрого поиска
CREATE INDEX idx_search ON articles USING GIN (search_vector);
-- Поиск
SELECT title, content
FROM articles
WHERE search_vector @@ to_tsquery('russian', 'программирование & база');-- ts_rank — ранжирование по релевантности
SELECT
title,
ts_rank(search_vector, query) AS rank
FROM articles,
to_tsquery('russian', 'PostgreSQL') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 10;
-- ts_rank_cd — учитывает близость слов друг к другу
SELECT
title,
ts_rank_cd(search_vector, query) AS rank
FROM articles,
to_tsquery('russian', 'база <-> данных') query
WHERE search_vector @@ query
ORDER BY rank DESC;-- ts_headline выделяет найденные слова в тексте
SELECT
title,
ts_headline('russian', content, query,
'StartSel=<b>, StopSel=</b>, MaxFragments=3') AS snippet
FROM articles,
to_tsquery('russian', 'PostgreSQL & производительность') query
WHERE search_vector @@ query
LIMIT 5;
-- Вывод:
-- "... PostgreSQL обеспечивает высокую производительность при ..."PostgreSQL использует словари для нормализации слов:
-- Стемминг (приведение к основе)
SELECT to_tsvector('russian', 'бегу бежит бежал') @@
to_tsquery('russian', 'бежать');
-- TRUE (все формы глагола найдены)
-- Синонимы (требуется настройка словаря)
-- В словаре можно указать: постгрес => postgresql
SELECT to_tsvector('russian', 'постгрес') @@
to_tsquery('russian', 'postgresql');Для поиска похожих строк (опечатки, частичные совпадения):
-- Включить расширение
CREATE EXTENSION pg_trgm;
-- Оператор % — похожесть (similarity)
SELECT * FROM users
WHERE name % 'Алексей'; -- Найдёт: Алексей, Алексеи, Alexey
-- Получить степень похожести (0-1)
SELECT
name,
similarity(name, 'Александр') AS sim
FROM users
ORDER BY sim DESC
LIMIT 10;
-- Индекс для ускорения
CREATE INDEX idx_users_name_trgm ON users
USING GIN (name gin_trgm_ops);
-- Поиск по индексу
SELECT * FROM users
WHERE name % 'Алексей'
ORDER BY similarity(name, 'Алексей') DESC;-- Хранить вектор в отдельной колонке (рекомендуется)
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
to_tsvector('russian', COALESCE(title, '') || ' ' || COALESCE(content, ''))
) STORED;
-- Индекс на выражении (альтернатива)
CREATE INDEX idx_search_expr ON articles
USING GIN (to_tsvector('russian', title || ' ' || content));
-- Конфигурация поиска (можно создать свою)
SELECT * FROM pg_ts_config WHERE cfgname = 'russian';Репликация позволяет создавать копии (реплики) базы данных для масштабирования чтения, отказоустойчивости и географического распределения.
┌─────────────┐ WAL Stream ┌─────────────┐
│ Primary │ ──────────────────────────> │ Standby │
│ (Master) │ │ (Replica) │
│ │ │ │
│ Запись │ │ Чтение │
│ Чтение │ │ (опц.) │
└─────────────┘ └─────────────┘
Асинхронная репликация в реальном времени через WAL-лог:
Настройка Primary (master):
# postgresql.conf
wal_level = replica # Уровень WAL для репликации
max_wal_senders = 10 # Максимум репликационных соединений
wal_keep_size = 1GB # Хранить WAL для отстающих реплик
synchronous_standby_names = '' # Пусто = асинхронная
# pg_hba.conf (разрешить репликацию)
host replication replicator 192.168.1.0/24 md5Настройка Standby (replica):
# Создать standby.signal
touch /var/lib/postgresql/data/standby.signal
# postgresql.conf на standby
hot_standby = on # Разрешить чтение на реплике
primary_conninfo = 'host=192.168.1.10 port=5432 user=replicator password=secret'Создание реплики:
# pg_basebackup — создание физической копии
pg_basebackup -h primary_host -U replicator -D /var/lib/postgresql/data \
-Fp -Xs -P -R| Характеристика | Асинхронная | Синхронная |
|---|---|---|
| Производительность | ⭐⭐⭐ Высокая | ⭐⭐ Ниже (ждёт подтверждения) |
| Потеря данных при сбое | Возможна | Нет |
| Доступность при сбое standby | Не влияет | Может заблокировать primary |
| Использование | По умолчанию, для масштабирования | Для критичных данных |
Настройка синхронной реплики:
# postgresql.conf на primary
synchronous_standby_names = 'standby1,standby2' # Имена из application_name
synchronous_commit = on # Ждать подтверждения от standby
# postgresql.conf на standby
application_name = 'standby1'Репликация на уровне таблиц, а не всей базы:
-- На Primary: создать публикацию
CREATE PUBLICATION my_pub FOR TABLE users, orders;
-- На Subscriber: создать подписку
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=primary_host dbname=mydb user=replicator password=secret'
PUBLICATION my_pub;
-- Добавить таблицу в публикацию
ALTER PUBLICATION my_pub ADD TABLE products;
-- Посмотреть статус
SELECT * FROM pg_stat_subscription;Когда использовать логическую репликацию:
Primary → Standby 1 → Standby 2
Standby 1 становится master для Standby 2:
# На Standby 1
primary_conninfo = 'host=primary ...'
# Разрешить репликацию дальше-- На Primary: статус реплик
SELECT
client_addr,
state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
sync_state,
(sent_lsn - replay_lsn) AS lag_bytes
FROM pg_stat_replication;
-- На Standby: статус восстановления
SELECT
pg_is_in_recovery(),
pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn(),
pg_last_xact_replay_timestamp();
-- Задержка репликации (на standby)
SELECT EXTRACT(EPOCH FROM (NOW() - pg_last_xact_replay_timestamp())) AS lag_seconds;Ручной failover:
# На standby: создать триггер-файл или выполнить
pg_ctl promote -D /var/lib/postgresql/data
# Или SQL
SELECT pg_promote();Автоматический failover:
Установка соединения с PostgreSQL дорогая операция. Пулинг решает эту проблему, переиспользуя соединения.
| Без пулинга | С пулингом |
|---|---|
| Новое соединение на каждый запрос | Соединения переиспользуются |
| Ограничение max_connections | Тысячи клиентов на несколько соединений |
| Накладные расходы на handshake | Мгновенное получение соединения |
| Риск исчерпания соединений | Контроль нагрузки |
Самый популярный пулер соединений для PostgreSQL.
Конфигурация:
; /etc/pgbouncer/pgbouncer.ini
[databases]
; Имя БД = подключение к реальной БД
mydb = host=localhost port=5432 dbname=mydb
* = host=localhost port=5432 ; все остальные
[pgbouncer]
listen_port = 6432
listen_addr = 0.0.0.0
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
; Режим пулинга
pool_mode = transaction
; Размеры пула
max_client_conn = 1000 ; Максимум клиентов
default_pool_size = 20 ; Соединений на БД
; Таймауты
server_idle_timeout = 600 ; Закрыть серверное соединение после простоя
client_idle_timeout = 0 ; Не закрывать клиентскоеФайл пользователей:
; /etc/pgbouncer/userlist.txt
"myuser" "md5c4ca4238a0b923820dcc509a6f75849b"Запуск:
pgbouncer -d /etc/pgbouncer/pgbouncer.iniПодключение через PgBouncer:
# Было (напрямую к PostgreSQL:5432)
conn = psycopg2.connect("host=localhost port=5432 dbname=mydb ...")
# Стало (через PgBouncer:6432)
conn = psycopg2.connect("host=localhost port=6432 dbname=mydb ...")| Режим | Описание | Когда использовать |
|---|---|---|
| Session | Соединение закрепляется за клиентом на всю сессию | Приложения с long-lived соединениями |
| Transaction | Соединение освобождается после каждой транзакции | ✅ Большинство приложений (по умолчанию) |
| Statement | Соединение освобождается после каждого запроса | Только для stateless запросов |
Важно: В режиме Transaction нельзя использовать PREPARE или временные таблицы между запросами.
from sqlalchemy import create_engine
# Правильная настройка для PgBouncer
engine = create_engine(
"postgresql://user:pass@localhost:6432/mydb",
pool_size=10, # Пул SQLAlchemy
max_overflow=20,
pool_pre_ping=True, # Проверка соединения перед использованием
pool_recycle=3600 # Пересоздавать соединения через час
)PostgreSQL расширяем через расширения — дополнительные модули с функциями, типами данных и операторами.
-- Установить расширение
CREATE EXTENSION pg_stat_statements;
-- Посмотреть установленные
SELECT * FROM pg_extension;
-- Доступные расширения
SELECT * FROM pg_available_extensions;
-- Версии расширения
SELECT * FROM pg_available_extension_versions WHERE name = 'pg_trgm';
-- Обновить расширение
ALTER EXTENSION pg_trgm UPDATE;
-- Удалить расширение
DROP EXTENSION pg_trgm;CREATE EXTENSION pg_stat_statements;
-- Топ медленных запросов
SELECT
query,
calls,
total_exec_time / calls AS avg_time_ms,
rows / calls AS avg_rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- Сбросить статистику
SELECT pg_stat_statements_reset();CREATE EXTENSION pg_trgm;
-- Похожесть строк
SELECT similarity('Александр', 'Алексей'); -- 0.3636...
-- Поиск с опечатками
SELECT * FROM users WHERE name % 'Алексндр';
-- Индекс
CREATE INDEX idx_name_trgm ON users USING GIN (name gin_trgm_ops);CREATE EXTENSION pgcrypto;
-- Хэширование
SELECT crypt('password', gen_salt('bf')); -- bcrypt
SELECT digest('data', 'sha256');
-- Шифрование
SELECT pgp_sym_encrypt('secret', 'key');
SELECT pgp_sym_decrypt(encrypted, 'key');
-- UUID
SELECT gen_random_uuid();CREATE EXTENSION postgis;
-- Расстояние между точками (в метрах)
SELECT ST_Distance(
ST_GeogFromText('POINT(37.6176 55.7558)'), -- Москва
ST_GeogFromText('POINT(30.3141 59.9387)') -- СПб
); -- ~634000 метров
-- Найти объекты в радиусе
SELECT * FROM shops
WHERE ST_DWithin(
location,
ST_GeogFromText('POINT(37.6176 55.7558)'),
1000 -- 1 км
);CREATE EXTENSION timescaledb;
-- Гипертаблица (автоматическое партиционирование)
SELECT create_hypertable('sensor_data', 'time');
-- Непрерывные агрегаты (материализованные представления)
CREATE MATERIALIZED VIEW sensor_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS hour,
sensor_id,
avg(value) AS avg_value
FROM sensor_data
GROUP BY hour, sensor_id;CREATE EXTENSION citus;
-- Распределённая таблица
SELECT create_distributed_table('users', 'user_id');
-- Данные шардируются по кластеру| Расширение | Описание |
|---|---|
uuid-ossp | Генерация UUID разных версий |
hstore | Хранение key-value пар |
ltree | Иерархические данные (деревья) |
pg_partman | Автоуправление партициями |
pg_cron | Планировщик задач внутри PostgreSQL |
pgvector | Векторные embeddings для ML |
BRIN (Block Range Index) — сверхкомпактные индексы для больших отсортированных таблиц.
Вместо индексации каждой строки, BRIN хранит минимальные и максимальные значения для диапазонов страниц:
Страницы 1-1000: min=2026-01-01, max=2026-01-10
Страницы 1001-2000: min=2026-01-11, max=2026-01-20
...
| Сценарий | BRIN | B-Tree |
|---|---|---|
| Таблица > 10 ГБ | ✅ Да | ⚠️ Большой индекс |
| Данные отсортированы | ✅ Обязательно | ✅ Да |
| Временные ряды | ✅ Идеально | ⚠️ Медленно |
| Частые UPDATE | ❌ Нет | ✅ Да |
| Точечные запросы | ❌ Нет | ✅ Да |
-- Создать BRIN индекс
CREATE INDEX idx_timestamp_brin ON measurements
USING BRIN (timestamp);
-- Размер индекса (для 100 ГБ таблицы)
-- BRIN: ~10 МБ
-- B-Tree: ~2 ГБ
-- Запрос использует BRIN
EXPLAIN
SELECT * FROM measurements
WHERE timestamp = '2026-01-15';
-- Bitmap Heap Scan
-- -> Bitmap Index Scan on idx_timestamp_brin-- pages_per_range — сколько страниц в одном диапазоне
CREATE INDEX idx_brin ON measurements
USING BRIN (timestamp) WITH (pages_per_range = 128);
-- Меньше значение = больше точность, больше размер
-- По умолчанию: 128 страниц (1 МБ)PostgreSQL может выполнять запросы параллельно, используя несколько CPU ядер.
# postgresql.conf
max_parallel_workers_per_gather = 4 -- Воркеров на запрос
max_parallel_workers = 8 -- Всего воркеров
max_parallel_maintenance_workers = 4 -- Для VACUUM, CREATE INDEX
parallel_tuple_cost = 0.1 -- Стоимость передачи строки
parallel_setup_cost = 1000 -- Стоимость настройки параллелизмаEXPLAIN ANALYZE
SELECT COUNT(*) FROM large_table;
-- Вывод:
-- Finalize Aggregate
-- -> Gather
-- Workers Planned: 4
-- Workers Launched: 4
-- -> Partial Aggregate
-- -> Parallel Seq Scan on large_table-- Включить параллелизм
SET max_parallel_workers_per_gather = 4;
-- Выключить для отладки
SET max_parallel_workers_per_gather = 0;
-- Проверить план
EXPLAIN (ANALYZE, BUFFERS)
SELECT SUM(amount) FROM orders WHERE created_at > '2026-01-01';-- Гипертаблица с автоматическим партиционированием
CREATE TABLE sensor_data (
time TIMESTAMPTZ NOT NULL,
sensor_id INTEGER,
value NUMERIC
);
SELECT create_hypertable('sensor_data', 'time', chunk_time_interval => INTERVAL '1 day');
-- Непрерывный агрегат для почасовых данных
CREATE MATERIALIZED VIEW sensor_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS hour,
sensor_id,
avg(value) AS avg_value,
min(value) AS min_value,
max(value) AS max_value
FROM sensor_data
GROUP BY hour, sensor_id;
-- Индексы
CREATE INDEX idx_sensor_time ON sensor_data (sensor_id, time DESC);
CREATE INDEX idx_time_brin ON sensor_data USING BRIN (time);SELECT
a.title,
a.content,
ts_rank(a.search_vector, query) AS rank,
ts_headline('russian', a.content, query,
'StartSel=<mark>, StopSel=</mark>, MaxFragments=3') AS snippet
FROM articles a,
to_tsquery('russian', 'PostgreSQL & производительность & база') query
WHERE a.search_vector @@ query
ORDER BY rank DESC
LIMIT 10;-- Топ запросов по времени выполнения
SELECT
query,
calls,
total_exec_time / 1000 AS total_sec,
mean_exec_time AS avg_ms,
rows / calls AS avg_rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- Долгие транзакции
SELECT
pid,
usename,
NOW() - xact_start AS duration,
query
FROM pg_stat_activity
WHERE state = 'active'
AND NOW() - xact_start > INTERVAL '5 minutes';
-- Размер таблиц и индексов
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
pg_size_pretty(pg_relation_size(relid)) AS table_size,
pg_size_pretty(pg_indexes_size(relid)) AS index_size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;-- Функция для создания будущих партиций
CREATE OR REPLACE FUNCTION create_future_partitions()
RETURNS void AS $$
DECLARE
i INTEGER;
start_date DATE;
end_date DATE;
partition_name TEXT;
BEGIN
-- Создать партиции на 3 месяца вперёд
FOR i IN 0..2 LOOP
start_date := DATE_TRUNC('month', NOW() + (i || ' month')::INTERVAL);
end_date := start_date + INTERVAL '1 month';
partition_name := 'sensor_data_' || to_char(start_date, 'YYYY_MM');
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF sensor_data
FOR VALUES FROM (%L) TO (%L)',
partition_name, start_date, end_date
);
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Запуск через pg_cron каждый месяц
SELECT cron.schedule('create_partitions', '0 0 1 * *',
$$SELECT create_future_partitions()$$);| Задача | Решение |
|---|---|
| Таблица > 50 ГБ | Партиционирование + BRIN |
| Быстрое удаление старых данных | DROP PARTITION |
| Поиск по тексту | Full-Text Search + GIN |
| Поиск с опечатками | pg_trgm |
| Масштабирование чтения | Streaming Replication |
| Критичные данные | Синхронная репликация |
| > 100 соединений | PgBouncer (transaction mode) |
| Геоданные | PostGIS |
| Временные ряды | TimescaleDB |
| Мониторинг запросов | pg_stat_statements |
| Большие агрегации | Параллельное выполнение |
В чём разница между декларативным и наследуемым партиционированием?
Когда BRIN лучше B-Tree?
Что такое partition pruning?
В чём разница между физической и логической репликацией?
Какой режим PgBouncer выбрать?
transaction для большинства приложенийsession если нужны временные таблицы или PREPAREГотовы применить знания на практике? Переходите к упражнениям!
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.