Партиционирование

Предпосылки: таблицы и типы (CREATE TABLE), ограничения (PRIMARY KEY, UNIQUE).

Ограничения | Представления

Сервис аналитики записывает события: клики, просмотры, покупки. За год накопилось 500 млн строк — 120 ГБ данных. DELETE 50 млн старых строк — операция на десятки минут. Таблица не уменьшается: место от удалённых строк освобождается фоновым процессом, который сам нагружает систему.

Партиционирование решает эту проблему: вместо одной огромной таблицы — набор отдельных физических частей (партиций), разделённых по значению ключа. Удаление старых данных — DROP одной партиции, мгновенная операция.

Концепция

Логическая таблица остаётся одна — приложение пишет INSERT INTO events и SELECT FROM events как обычно. Физически данные лежат в отдельных частях, разделённых по значению ключа партиционирования (обычно дата или идентификатор региона). СУБД автоматически направляет вставку в нужную часть и при чтении пропускает части, заведомо не содержащие нужных строк. При 12 месячных партициях запрос по одному месяцу читает 1/12 данных.

Стратегии разделения

Таблицу можно разделить тремя способами. RANGE — по диапазону значений: данные за январь в одной части, за февраль — в другой. Самый частый выбор для дат и последовательных данных, потому что совпадает с типичным фильтром WHERE created_at BETWEEN ... AND ... и позволяет мгновенно удалять старые данные (DROP одной части за месяц).

LIST — по конкретным значениям: заказы из US в одной части, из EU — в другой. Подходит, когда ключ принимает фиксированный набор значений.

HASH — по хешу значения: строки равномерно распределяются по N частям. Используется, когда нет естественного ключа для RANGE или LIST, но нужно разделить нагрузку по I/O. Тот же принцип лежит в основе шардинга — разделения данных между несколькими серверами.

Цена

Запросы без фильтра по ключу партиционирования сканируют все части. SELECT * FROM events WHERE user_id = 42 без ограничения по дате — 36 отдельных сканов вместо одного.

Выбор ключа

Ключ партиционирования должен присутствовать в WHERE подавляющего большинства запросов. Если большинство запросов фильтрует по дате — партиционирование по дате. Если по customer_id — по customer_id. Но партиционирование по customer_id делает невозможным мгновенное удаление старых данных — данные всех дат перемешаны в каждой части, и удаление снова требует поштучного DELETE.

PostgreSQL реализует партиционирование через декларативный DDL (RANGE, LIST, HASH) — подробнее.

Sources


Ограничения | Представления