Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Партиционирование (Partitioning) в базах данных
Партиционирование — это разделение большой таблицы на несколько физических частей (партиций) по определённым правилам. Это мощный инструмент оптимизации для высоконагруженных систем.
Основная идея
ДО партиционирования:
┌─────────────────────────────────────┐
│ orders (10 млн строк) │
│ Один огромный файл на диске │
│ Медленные запросы и сканирования │
└─────────────────────────────────────┘
ПОСЛЕ партиционирования:
┌──────────────┬──────────────┬──────────────┐
│ orders_2023 │ orders_2024 │ orders_2025 │
│ (3 млн) │ (4 млн) │ (3 млн) │
│ Быстрые запросы, параллельные скан │
└──────────────┴──────────────┴──────────────┘
Типы партиционирования
1. Range Partitioning (по диапазону)
Разделение по диапазону значений (часто по датам).
CREATE TABLE orders (
id SERIAL,
user_id INT,
amount DECIMAL,
created_at TIMESTAMP,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_2022 VALUES LESS THAN (2023),
PARTITION p_2023 VALUES LESS THAN (2024),
PARTITION p_2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
Когда использовать: логи, события, исторические данные.
Пример запроса (очень быстро):
-- Обращается ТОЛЬКО к p_2024 (1 млн строк вместо 10 млн)
SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
2. Hash Partitioning (по хэшу)
Распределение равномерно по хэш-функции.
CREATE TABLE user_sessions (
id SERIAL,
user_id INT,
session_data JSON,
PRIMARY KEY (id, user_id)
) PARTITION BY HASH (user_id) PARTITIONS 16;
Как работает:
user_id=100 → hash(100) % 16 = 4 → partition 4
user_id=200 → hash(200) % 16 = 8 → partition 8
user_id=500 → hash(500) % 16 = 4 → partition 4
Когда использовать: распределение по user_id, session_id (равномерное распределение).
3. List Partitioning (по списку значений)
Разделение по конкретным значениям.
CREATE TABLE customer_orders (
id SERIAL,
customer_id INT,
region VARCHAR(50),
amount DECIMAL
) PARTITION BY LIST (region) (
PARTITION p_us VALUES IN ('NY', 'CA', 'TX'),
PARTITION p_eu VALUES IN ('UK', 'DE', 'FR'),
PARTITION p_asia VALUES IN ('JP', 'CN', 'IN'),
PARTITION p_other VALUES IN (DEFAULT)
);
Когда использовать: данные по географическому распределению, категориям.
4. Composite Partitioning (комбинированное)
Разделение по нескольким критериям.
CREATE TABLE events (
id SERIAL,
user_id INT,
event_type VARCHAR(50),
created_at TIMESTAMP
) PARTITION BY RANGE (YEAR(created_at))
SUBPARTITION BY HASH (user_id) SUBPARTITIONS 8 (
PARTITION p_2024 VALUES LESS THAN (2025),
PARTITION p_2025 VALUES LESS THAN (2026)
);
Преимущества партиционирования
1. Улучшение производительности запросов
-- БЕЗ партиционирования: сканирует 10 млн строк
SELECT COUNT(*) FROM orders WHERE created_at >= '2024-01-01';
-- С партиционированием: сканирует только 2 млн строк (p_2024)
-- Скорость: 5x быстрее
Пример из production:
# Django ORM
orders = Order.objects.filter(
created_at__gte='2024-01-01',
created_at__lt='2025-01-01'
).count()
# БД сразу понимает: только p_2024
2. Более быстрые DELETE операции
-- БЕЗ партиционирования: удаляет 1 млн старых записей
DELETE FROM orders WHERE created_at < '2020-01-01';
-- С партиционированием: просто DROP PARTITION p_2019 (мгновенно!)
ALTER TABLE orders DROP PARTITION p_2019;
3. Параллельные операции
-- PostgreSQL может сканировать несколько партиций параллельно
-- EXPLAIN показывает: "Parallel Seq Scan"
SELECT * FROM orders WHERE created_at >= '2024-01-01';
4. Более простое управление индексами
-- Можно создать индексы только для активных партиций
CREATE INDEX idx_orders_2024 ON orders_2024(user_id);
-- Старые партиции могут быть архивированы
5. Лучше для больших таблиц
Таблица без партиций: 10 млн строк × 100 байт = ~1 ГБ
Индексы могут быть больше таблицы!
Таблица с партициями: каждая партиция ~2 млн строк
Индексы меньше, работают быстрее
Недостатки и сложности
1. Усложнение запросов
-- Нужно помнить о partition key
-- ДА
SELECT * FROM orders WHERE created_at >= '2024-01-01';
-- НЕТ (полное сканирование всех партиций!)
SELECT * FROM orders WHERE user_id = 123;
2. Слабые места в JOIN
-- Если JOIN не по partition key, может быть медленно
SELECT o.*, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at >= '2024-01-01';
-- Может потребовать все партиции!
3. Maintenance overhead
-- Нужно регулярно создавать новые партиции
-- Или использовать RANGE INTERVAL для автосоздания
CREATE TABLE orders (...)
PARTITION BY RANGE (INTERVAL '1 month' FOR created_at);
Практический пример: таблица логов
CREATE TABLE application_logs (
id BIGSERIAL,
user_id INT,
log_level VARCHAR(10), -- DEBUG, INFO, WARNING, ERROR
message TEXT,
created_at TIMESTAMP,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_2023 VALUES LESS THAN (2024),
PARTITION p_2024 VALUES LESS THAN (2025),
PARTITION p_2025 VALUES LESS THAN (2026),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- Индекс по log_level на активной партиции
CREATE INDEX idx_logs_2025_level ON application_logs_p_2025(log_level);
-- Быстрый запрос: только в p_2025
SELECT * FROM application_logs
WHERE created_at >= '2025-01-01'
AND log_level = 'ERROR';
Когда использовать партиционирование
- Таблица > 1 ГБ (обычно 10+ млн строк)
- Часто удаляются старые данные (достаточно DROP PARTITION)
- Есть natural partition key (дата, регион, category)
- Запросы фильтруют по partition key
- Нужна архивизация данных
Когда НЕ использовать
- Маленькие таблицы (< 100 млн строк)
- Нет хорошего partition key
- Запросы редко используют filter по partition key
- Сложность не стоит выигрыша в производительности
Отличие от шардирования
Партиционирование (Partitioning):
- Одна БД, несколько партиций
- Прозрачно для приложения
- Автоматическое управление
Шардирование (Sharding):
- Несколько БД на разных серверах
- Приложение знает про шарды
- Ручное управление
Production пример: метрики системы
# Django модель с партиционированием
class SystemMetric(models.Model):
server_id = models.IntegerField()
cpu_usage = models.FloatField()
memory_usage = models.FloatField()
timestamp = models.DateTimeField()
class Meta:
db_table = 'system_metrics'
# Для MySQL/PostgreSQL нужно SQL
# CREATE TABLE system_metrics (...)
# PARTITION BY RANGE INTERVAL ...
# Быстрый запрос (только за последний день)
metrics = SystemMetric.objects.filter(
timestamp__gte=datetime.now() - timedelta(days=1)
).values('server_id').annotate(avg_cpu=Avg('cpu_usage'))
# БД сканирует только партицию за последний день!
Автоматическое управление партициями
-- PostgreSQL 11+ может автоматически создавать партиции
CREATE TABLE events (
id SERIAL,
event_data JSON,
created_at TIMESTAMP
) PARTITION BY RANGE (created_at) (
PARTITION p_2024_01 FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'),
PARTITION p_2024_02 FOR VALUES FROM ('2024-02-01') TO ('2024-03-01')
);
-- Или использовать pg_partman для автоматизации
CREATE TABLE events_partman (...)
PARTITION BY RANGE (created_at);
-- pg_partman автоматически создает новые партиции
Мониторинг партиций
-- PostgreSQL: размер каждой партиции
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables
WHERE tablename LIKE 'orders%'
ORDER BY size DESC;
-- MySQL: информация о партициях
SELECT
PARTITION_NAME,
PARTITION_EXPRESSION,
PARTITION_METHOD,
PARTITION_ORDINAL_POSITION
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME = 'orders' AND TABLE_SCHEMA = 'mydb';
Итог
Партиционирование — это критичная оптимизация для высоконагруженных систем:
- Огромные таблицы (100+ млн строк) → почти обязательно
- Частое удаление старых данных → ОЧЕНЬ полезно
- Есть естественный ключ партиционирования → используй!
В production системах, где я работал, партиционирование использовалось для:
- Таблиц логов (по дате)
- Таблиц событий (по дате + hash)
- Таблиц истории (по дате)
- Аналитических данных (по географии)
Это дает 5-10x прирост производительности запросов на старые данные и делает удаление архивных данных тривиальным.