Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Партиционирование (Partitioning): Масштабирование баз данных
Партиционирование — это техника разделения большой таблицы на несколько меньших физических таблиц, которые хранятся отдельно, но логически представляют одну таблицу. Это критическая техника для масштабирования баз данных.
Основная идея
Есть таблица с 100 миллионами записей. Вместо того, чтобы искать в одной большой таблице:
Одна большая таблица (медленно):
[запись 1][запись 2][запись 3]...[запись 100M]
↑
Поиск требует сканирования всей таблицы
Партиционированная таблица (быстро):
Партиция 1: [1-25M] <- Поиск только в нужной партиции
Партиция 2: [25-50M]
Партиция 3: [50-75M]
Партиция 4: [75-100M]
Три типа партиционирования
1. Range Partitioning (по диапазону)
Делит данные по значению столбца в диапазоны.
-- Таблица заказов, партиционирована по дате
CREATE TABLE orders (
order_id INT,
customer_id INT,
order_date DATE,
amount DECIMAL
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- Запрос автоматически ищет только в p2023
SELECT * FROM orders WHERE order_date = '2023-05-15';
-- База знает: нужна только партиция p2023
Когда использовать: Временные данные, логи, события по датам
Плюсы:
- Быстрое удаление старых данных (drop partition вместо delete)
- Архивирование по периодам
- Очень быстрые range queries
-- Быстро удалить все данные за 2020
ALTER TABLE orders DROP PARTITION p2020;
-- Вместо: DELETE FROM orders WHERE YEAR(order_date) = 2020; (медленно!)
2. Hash Partitioning (по хешу)
Делит данные по хешу столбца равномерно.
-- Таблица пользователей, 4 партиции по хешу user_id
CREATE TABLE users (
user_id INT,
username VARCHAR(100),
email VARCHAR(100)
) PARTITION BY HASH(user_id)
PARTITIONS 4;
-- user_id 1001 -> hash(1001) % 4 = 1 -> партиция 1
-- user_id 2002 -> hash(2002) % 4 = 2 -> партиция 2
-- Данные распределены равномерно
SELECT * FROM users WHERE user_id = 1001; -- Быстро
SELECT * FROM users WHERE username = 'john'; -- Медленно (нужно сканировать все партиции)
Когда использовать: Когда нужно равномерное распределение данных
Плюсы:
- Равномерное распределение
- Простая балансировка
Минусы:
- Нельзя эффективно делать range queries
- Сложнее переоформировать партиции
3. List Partitioning (по спискам значений)
Делит данные по конкретным значениям.
-- Таблица продаж по регионам
CREATE TABLE sales (
sale_id INT,
region VARCHAR(50),
amount DECIMAL
) PARTITION BY LIST(region) (
PARTITION p_eu VALUES IN ('UK', 'France', 'Germany'),
PARTITION p_asia VALUES IN ('Japan', 'China', 'India'),
PARTITION p_americas VALUES IN ('USA', 'Canada', 'Brazil'),
PARTITION p_other VALUES IN (DEFAULT)
);
-- Запрос для EU быстро найдет нужную партицию
SELECT * FROM sales WHERE region = 'Germany';
Когда использовать: Категориальные данные (регионы, страны, типы)
Разница: Partitioning vs Sharding
Это часто путают, но это разные концепции.
Partitioning (внутри одного сервера)
Повых оптимизаций
Один сервер:
┌─────────────────────────┐
│ Одна база данных │
│ ┌────────┬────────┬────┐ │
│ │Part 1 │Part 2 │Part 3│ │
│ └────────┴────────┴────┘ │
└─────────────────────────┘
Нужен для: Оптимизации больших таблиц внутри одного сервера
Sharding (распределение по разным серверам)
Несколько серверов:
Сервер 1 Сервер 2 Сервер 3
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│Shard 1 │ │Shard 2 │ │Shard 3 │
│user_id 1-3M │ │user_id 3-6M │ │user_id 6-10M │
└──────────────┘ └──────────────┘ └──────────────┘
Нужен для: Горизонтального масштабирования между серверами
Практический пример: Логирование событий
-- Большая таблица логов: 1 млрд записей в день
CREATE TABLE events (
event_id BIGINT,
user_id INT,
event_type VARCHAR(50),
created_at TIMESTAMP
) PARTITION BY RANGE (DATE(created_at)) (
PARTITION p_2024_01_01 VALUES LESS THAN ('2024-01-02'),
PARTITION p_2024_01_02 VALUES LESS THAN ('2024-01-03'),
PARTITION p_2024_01_03 VALUES LESS THAN ('2024-01-04'),
-- ... и так далее
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- Запрос за сегодня — быстро (только одна партиция)
SELECT COUNT(*) FROM events WHERE DATE(created_at) = '2024-01-03';
-- Сканирует только p_2024_01_03
-- Удалить логи старше месяца — очень быстро
ALTER TABLE events DROP PARTITION p_2024_01_01;
-- Удаляет целую партицию, не трогая остальное
Индексы на партиционированных таблицах
-- Локальный индекс (по одному на каждую партицию)
CREATE INDEX idx_user_id
ON events (user_id)
LOCAL; -- Индекс распределен по партициям
-- Глобальный индекс (один на все партиции)
CREATE INDEX idx_event_type
ON events (event_type)
GLOBAL; -- Один индекс на все партиции
Производительность: До и После
До партиционирования
# Одна таблица с 100M записей
# Запрос: SELECT * FROM orders WHERE order_date = '2023-05-15'
# Выполнение:
# 1. Index seeks на order_date
# 2. Сканирует ~100K записей из индекса
# 3. Fetches данные из таблицы
# Time: ~500ms
После партиционирования
# Таблица разбита по году: 12 партиций
# Запрос: SELECT * FROM orders WHERE order_date = '2023-05-15'
# Выполнение:
# 1. Partition pruning: база знает, что нужна только p2023
# 2. Index seeks на order_date в p2023 (~8M записей вместо 100M)
# 3. Сканирует ~8K записей из индекса
# 4. Fetches данные из таблицы
# Time: ~50ms <- 10x быстрее!
Недостатки и сложности
# 1. Сложность операций
join_result = session.query(Orders).join(OrderDetails).filter(
Orders.order_date > '2023-01-01'
).all()
# Если таблицы партиционированы по разным столбцам — может быть медленно
# 2. Перебалансирование
# Если добавили новые партиции — может потребоваться redistribution
# 3. Уникальные ключи
# Partition key должен быть частью primary key или unique constraint
Когда партиционировать?
✅ Когда нужно:
- Таблица > 100GB
- Нужна частая очистка старых данных
- Есть очевидный key для разделения (дата, region, user_id)
- Запросы часто фильтруют по этому ключу
❌ Когда НЕ нужно:
- Таблица < 10GB
- Много JOIN-ов между партиционированными таблицами
- Нет четкого ключа для разделения
- Нужны ACID транзакции между партициями
Python пример: Работа с партиционированной таблицей
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.orm import declarative_base, Session
from datetime import datetime, timedelta
Base = declarative_base()
class Event(Base):
__tablename__ = 'events'
event_id = Column(Integer, primary_key=True)
user_id = Column(Integer)
event_type = Column(String)
created_at = Column(DateTime)
engine = create_engine('postgresql://...')
# Автоматическое добавление новой партиции
def ensure_partition_for_date(engine, date):
"""Создает партицию если её нет."""
partition_name = f"p_{date.strftime('%Y_%m_%d')}"
next_date = date + timedelta(days=1)
with engine.connect() as conn:
conn.execute(f"""
ALTER TABLE events
ADD PARTITION {partition_name}
VALUES LESS THAN ('{next_date.date()}');
""")
conn.commit()
# Использование
today = datetime.now().date()
ensure_partition_for_date(engine, today)
# Быстрый поиск в нужной партиции
with Session(engine) as session:
today_events = session.query(Event).filter(
Event.created_at >= datetime.combine(today, datetime.min.time()),
Event.created_at < datetime.combine(today + timedelta(days=1), datetime.min.time())
).all()
Ключевые выводы
- Партиционирование разделяет большую таблицу на меньшие физические части
- Range partitioning для временных данных (логи, события)
- Hash partitioning для равномерного распределения
- List partitioning для категориальных данных
- Partition pruning позволяет базе сканировать только нужные партиции
- Не партиционируй пока таблица не станет > 100GB
- Главная выгода — быстрое удаление старых данных и улучшение performance на больших таблицах