Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Партиционирование (Partitioning)
Определение
Партиционирование (также шардирование) — это техника разделения больших таблиц или данных на несколько более мелких, логических или физических, частей (партиций/шардов) для улучшения производительности, масштабируемости и управляемости системы.
Классификация
1. Партиционирование на уровне БД (Horizontal Partitioning)
Делим одну таблицу на несколько физических таблиц/партиций:
-- Таблица orders разбита по дате
CREATE TABLE orders_2023_q1 (
id INT PRIMARY KEY,
user_id INT,
amount DECIMAL,
created_at DATE
) PARTITION BY RANGE (YEAR(created_at));
CREATE TABLE orders_2023 PARTITION BY RANGE (MONTH(created_at)) (
PARTITION q1 VALUES LESS THAN (4),
PARTITION q2 VALUES LESS THAN (7),
PARTITION q3 VALUES LESS THAN (10),
PARTITION q4 VALUES LESS THAN (13)
);
-- Данные автоматически распределяются в нужные партиции
2. Шардирование на уровне приложения (Application-level Sharding)
Приложение саму решает, куда писать и откуда читать данные:
public class UserService {
private final UserRepository[] shards; // 10 БД инстансов
public void saveUser(User user) {
int shardId = user.getId() % shards.length; // Рассчитываем шард
shards[shardId].save(user);
}
public User getUser(Long userId) {
int shardId = (int) (userId % shards.length);
return shards[shardId].findById(userId);
}
}
Зачем нужно партиционирование
1. Производительность (Performance)
Проблема без партиционирования
Таблица orders: 500 млн строк
Какие заказы были вчера?
SELECT * FROM orders WHERE created_at = 2024-01-15
BD сканирует все 500 млн строк (full table scan) → 10 сек
С партиционированием
Таблица orders разбита по дате на 365 партиций
Какие заказы были вчера?
SELECT * FROM orders_2024_01_15
BD сканирует только 1.4 млн строк → 50 мс
2. Масштабируемость (Scalability)
Без партиционирования:
1 сервер БД: 500 млн записей
↓
Диск заполняется
Запросы медленнеют
Нужно обновить железо (дорого)
С партиционированием:
Сервер 1: 50 млн записей (user_id % 10 == 0)
Сервер 2: 50 млн записей (user_id % 10 == 1)
...
Сервер 10: 50 млн записей (user_id % 10 == 9)
↓
Каждый сервер управляет 50 млн строк
Не нужно очень мощное оборудование
Можем добавлять новые серверы
3. Управление (Manageability)
Ротирование данных
Таблица orders партиционирована по дням
Понедельник: Удаляем старые данные (partition 2020-01-01)
Вторник: Добавляем новую партицию на завтра (2024-03-24)
Удаление без FULL TABLE LOCK:
DROP PARTITION 2020-01-01; // Мгновенно
Без партиционирования:
DELETE FROM orders WHERE created_at < 2020-01-01;
// Долго, много логирования, LOCK на таблицу
Обслуживание
Партиционирование по дате: 365 партиций
Можно реbuilder индексы на одну партицию без влияния на другие
Можно делать VACUUM только на старые партиции
Можно гарантировать, что свежие данные в памяти
4. Надежность (Reliability)
Сервер 1 упал (содержит user_id % 10 == 1)
↓
Потеряны данные только этого шарда
Другие 9 шардов работают нормально
Без шардирования:
Сервер упал
↓
Вся система недоступна
Стратегии партиционирования
1. Range Partitioning (По диапазону)
Делим по значению колонки:
CREATE TABLE sales (
id INT,
amount INT,
sale_date DATE
) PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p_2020 VALUES LESS THAN (2021),
PARTITION p_2021 VALUES LESS THAN (2022),
PARTITION p_2022 VALUES LESS THAN (2023),
PARTITION p_2023 VALUES LESS THAN (2024),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- Данные 2020 года в p_2020
-- Данные 2021 года в p_2021 и т.д.
2. List Partitioning (По значениям)
CREATE TABLE users (
id INT,
country VARCHAR(50)
) PARTITION BY LIST (country) (
PARTITION p_ru VALUES IN (Russia, Belarus),
PARTITION p_eu VALUES IN (UK, France, Germany),
PARTITION p_us VALUES IN (USA, Canada),
PARTITION p_other VALUES IN (DEFAULT)
);
3. Hash Partitioning (По хешу)
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id INT,
amount DECIMAL
) PARTITION BY HASH (user_id) PARTITIONS 10;
-- 10 партиций, данные распределяются по хешу user_id
-- Партиция = hash(user_id) % 10
В приложении:
public class ShardingStrategy {
private static final int SHARD_COUNT = 10;
public static int getShardId(Long userId) {
return (int) (Math.abs(userId.hashCode()) % SHARD_COUNT);
}
public static void main(String[] args) {
System.out.println(getShardId(1L)); // 7
System.out.println(getShardId(1L)); // 7 (одинаково всегда)
System.out.println(getShardId(100L)); // 3
}
}
4. Composite Partitioning (Комбинированное)
CREATE TABLE transactions (
id INT,
user_id INT,
amount DECIMAL,
created_at DATE
) PARTITION BY RANGE (YEAR(created_at))
SUBPARTITION BY HASH (user_id) (
PARTITION p_2023 (
SUBPARTITION s_0,
SUBPARTITION s_1,
SUBPARTITION s_2
),
PARTITION p_2024 (
SUBPARTITION s_0,
SUBPARTITION s_1,
SUBPARTITION s_2
)
);
-- Сначала по году (2023, 2024)
-- Потом по хешу user_id (0, 1, 2)
Практический пример: Большая таблица логов
// Логирование событий — миллионы записей в день
public class LogService {
private final LogRepository[] shards = new LogRepository[30];
// Один шард на день, ротируем каждый месяц
public void log(Event event) {
LocalDate today = LocalDate.now();
int shardId = today.getDayOfMonth() % shards.length;
shards[shardId].save(event);
}
public List<Event> getEventsForDay(LocalDate date) {
int shardId = date.getDayOfMonth() % shards.length;
return shards[shardId].findByDate(date);
}
public void rotateShards() {
// Каждый месяц очищаем самый старый шард
int oldestShardId = LocalDate.now().getDayOfMonth() % shards.length;
shards[oldestShardId].deleteAll(); // Мгновенно
}
}
Проблемы партиционирования
1. Hot Spot — неравномерное распределение
Шардируем по country:
Россия: 80% всех пользователей
США: 15%
Другие: 5%
Российский шард перегружен, другие простаивают
2. Range Queries — запросы по диапазонам
Шардируем по user_id
Найти заказы пользователей от ID 1 до 1000
↓
Нужно запросить все 10 шардов
Объединить результаты (много сетевых запросов)
3. Рестартование шардов сложно
Добавили шард 11 (было 10 шардов)
Теперь нужно реботировать все данные:
shard_id = user_id % 10 ← старая формула
shard_id = user_id % 11 ← новая формула
Реботировка может занять недели
Когда использовать партиционирование
Используй
✓ Таблица > 1-2 млн строк ✓ Старые данные редко читают (архивные) ✓ Периодически удаляют большие объемы данных ✓ Запросы часто фильтруют по partition key ✓ Данные распределены равномерно
Не используй
✗ Маленькая таблица (< 100k строк) ✗ Много JOINS между партициями ✗ Частые JOINS с другими таблицами ✗ Запросы по неключевым полям ✗ Сложно разделить данные справедливо
Инструменты
PostgreSQL
-- Партиционирование встроено (PostgreSQL 10+)
CREATE TABLE orders (
id INT,
user_id INT
) PARTITION BY RANGE (user_id);
MySQL
-- Поддерживает RANGE, LIST, HASH partitioning
CREATE TABLE orders (
id INT,
created_at DATE
) PARTITION BY RANGE (YEAR(created_at)) (...)
Elasticsearch
-- Шардирование встроено
Индекс автоматически разбивается на шарды
Данные распределяются по узлам кластера
Вывод
Партиционирование — это мощный инструмент для масштабирования, но требует тщательного планирования. Неправильное шардирование может привести к горячим точкам и сложности в обслуживании. Используй партиционирование только если у тебя действительно есть проблемы с производительностью или масштабируемостью.