← Назад к вопросам

Для чего нужно партиционирование?

3.0 Senior🔥 61 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Партиционирование (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

-- Шардирование встроено
Индекс автоматически разбивается на шарды
Данные распределяются по узлам кластера

Вывод

Партиционирование — это мощный инструмент для масштабирования, но требует тщательного планирования. Неправильное шардирование может привести к горячим точкам и сложности в обслуживании. Используй партиционирование только если у тебя действительно есть проблемы с производительностью или масштабируемостью.