Как выбрать ключ партиционирования: ревью и рефакторинг
Ключ партиционирования должен помогать конкретным запросам и обслуживанию данных, а не просто делить таблицу на части. Разбираем выбор для PostgreSQL 16, типичные замечания на ревью и безопасный план рефакторинга.

В таблице событий уже миллиарды строк, и команда предлагает разбить её по tenant_id: так проще искать данные одного клиента. Но если большинство запросов выбирает события за период, а крупные клиенты создают несопоставимо больше строк, такой ключ может осложнить и запросы, и обслуживание. Ниже — практический разбор выбора и ревью ключа партиционирования в PostgreSQL 16; конкретный план миграции зависит от нагрузки и схемы приложения.
Сначала проверьте, какую проблему решают партиции
Партиционирование делит одну логическую таблицу на физические части. Оно может упростить удаление старых данных и помочь планировщику исключать ненужные части из запроса. Но оно не заменяет индексы, не гарантирует ускорения каждого запроса и не распределяет нагрузку между серверами: последнее обычно связывают с шардингом.
Термин. Отсечение партиций — это исключение планировщиком тех частей таблицы, которые заведомо не подходят под условия запроса. Если условие не ограничивает ключ партиционирования, разделение может не помочь чтению.
На ревью попросите автора назвать не «большую таблицу», а конкретную цену текущего решения: время запросов, объём сканируемых данных, длительность очистки старых записей, рост индексов или влияние обслуживания на рабочую нагрузку. Если проблему нельзя связать с измеряемым показателем, сначала стоит собрать профиль запросов и операций, а не утверждать схему партиций.
Хороший ключ отвечает на вопрос «какую работу станет дешевле делать?». Если ответ — только «таблица будет выглядеть аккуратнее», решение пока не обосновано.
Сравните кандидатов на одном рабочем наборе
Для PostgreSQL 16 обычно сравнивают разбиение по времени, по значению категории или по хешу. Это не равнозначные варианты: каждый оптимизирует свой сценарий. В таблице ниже речь о типичных свойствах, а не о гарантии ускорения.
| Кандидат | Когда подходит | Цена и риск |
|---|---|---|
| Время события, RANGE | Запросы ограничены периодом; старые данные нужно удалять или архивировать целыми периодами. | Запросы без диапазона времени могут затронуть много партиций. Нужны заранее созданные следующие диапазоны и понятная политика для запоздавших событий. |
| Категория или tenant_id, LIST | Набор значений ограничен и известен; запросы и обслуживание регулярно работают с отдельными значениями. | Рост числа значений усложняет создание частей. Неравномерный объём по клиентам приводит к перекосу: крупный tenant может занять одну чрезмерно большую партицию. |
| Хеш от значения, HASH | Нужно разделить строки на фиксированное число частей, а выбранное значение часто присутствует в условиях запросов. | Хеш не делает одну часто повторяющуюся величину менее частой. Удалять данные по сроку хранения целыми временными диапазонами не получится без дополнительного уровня разбиения. |
Выбирайте не по типу столбца, а по сочетанию четырёх вещей: какие условия стоят в запросах, как распределены значения, как удаляются старые данные и какие ограничения целостности нужно сохранить. Например, дата естественно подходит для очистки истории, но бесполезна для запроса, который ищет один аккаунт во всём архиве и не задаёт период.
Что проверять в код-ревью
Пройдитесь по вопросам в этом порядке. Если на первые два нет ясного ответа, обсуждать число партиций преждевременно.
- Есть ли ключ в реальных запросах? Проверьте самые частые и самые дорогие запросы, включая фоновые задачи. Предикат должен ограничивать ключ так, чтобы PostgreSQL мог отсечь лишние части. Результат проверки — список запросов и объяснение, какие партиции они должны задевать.
- Соответствует ли шаг разбиения жизни данных? Для временных данных сравните выбранный интервал с объёмом записей и частотой очистки. Слишком мелкие интервалы создают много объектов для планирования и обслуживания; слишком крупные уменьшают пользу удаления по времени. Подтвердите выбор нагрузочным тестом на близком к рабочему объёме.
- Распределяются ли строки приемлемо? Сравните количество и объём строк по значениям кандидата, включая самых крупных клиентов и периоды пиков. Среднее значение скрывает перекос. Результат — распределение, а не предположение, что все tenants или месяцы одинаковы.
- Сохраняются ли уникальность и связи? В PostgreSQL 16 уникальное ограничение или первичный ключ на секционированной таблице должны включать все столбцы ключа партиционирования. Если текущий ключ — глобальный id, а партиционировать хотят по времени, проверьте, как сохранить требуемую уникальность и внешние связи. Не обещайте глобальную уникальность одним индексом, не проверив ограничение.
- Кто создаёт и обслуживает части? Зафиксируйте, кто создаёт будущие партиции, что происходит с записью за пределами диапазона и как контролируются отсутствующие части. Проверка — тестовая запись на границе диапазона и понятный сигнал об ошибке, если подходящей партиции нет.
Полезное замечание на ревью звучит конкретно: «Запрос отчёта фильтрует по account_id, но ключ выбран по created_at; покажите план запроса и число затрагиваемых партиций». Замечание «ключ кажется неудачным» не задаёт проверяемого критерия и не помогает выбрать замену.
Как менять ключ в существующей таблице
Смена ключа — не косметический рефакторинг: меняются физическое размещение строк, индексы и потенциально ограничения схемы. Универсальной миграции без простоя нет. Перед планированием оцените объём таблицы, скорость записи, доступное место, допустимое окно переключения и способ синхронизировать изменения во время переноса.
- Снимите исходные показатели. Зафиксируйте объём данных, частоту записи, задержки ключевых запросов, длительность очистки и распределение по кандидатам. Это станет базой сравнения.
- Опишите целевую схему. Проверьте первичные и уникальные ключи, внешние ключи, индексы, фоновые задания и запросы приложения. Отдельно решите, как обрабатывать записи вне созданных диапазонов.
- Проведите репетицию переноса. На копии данных измерьте время и потребление диска, проверьте соответствие количества строк и выборочных значений, прогоните характерные запросы. Тест должен учитывать конкурентные записи, если они будут идти во время миграции.
- Выберите способ синхронизации и переключения. Для небольшого объёма может подойти окно обслуживания; при непрерывной записи нужен отдельный план догоняющей синхронизации и сверки. Двойная запись приложением сама по себе рискованна: при частичном сбое таблицы могут разойтись.
- Задайте условия отката. До переключения определите, какой показатель или ошибка отменяет релиз, кто принимает решение и как вернуться к прежнему чтению и записи. Результат — отрепетированный сценарий, а не надежда, что старую таблицу удастся восстановить быстро.
Не переносите строки вслепую в расчёте, что новая схема автоматически станет быстрее. После переключения сравните планы и задержки тех же запросов при сопоставимой нагрузке, а также время обслуживания и объём занятого места. Если улучшился один отчёт, но выросла задержка записи или усложнилось удаление данных, решение нужно оценивать по полной цене.
Где выбор ломается
- Партиционируют по удобному столбцу, которого нет в фильтрах. Запрос продолжает читать много частей. Добавление индекса может помочь отдельному запросу, но не превращает неподходящий ключ в удачный.
- Выбирают tenant_id, не проверив перекос. Несколько крупных клиентов могут сформировать несопоставимые по объёму части. Сначала изучите распределение и рост; не опирайтесь на среднее.
- Делают временные части слишком мелкими. Управлять ими становится сложнее, а запросы без ограничения по времени затрагивают много объектов. Число частей подбирают тестированием и с учётом реального горизонта хранения.
- Забывают про ограничения уникальности. После включения ключа партиционирования прежний первичный ключ может оказаться несовместим с требованиями PostgreSQL к уникальному ограничению на родительской таблице.
- Считают, что партиционирование решит любой рост. Оно помогает организовать данные внутри одной базы, но не заменяет горизонтальное масштабирование и не исправляет медленный запрос само по себе.
Разбиение по времени и затем по tenant_id может быть уместно, если нужны и очистка по периодам, и изоляция объёма клиентов. Но вложенное партиционирование добавляет объекты и правила обслуживания. Вводите второй уровень только после того, как измерения показали, что одного ключа недостаточно.
Следующий шаг
Перед утверждением схемы соберите короткий пакет для ревью: несколько главных запросов с условиями фильтрации, распределение строк по кандидатам, требования к уникальности и план удаления данных. Затем проверьте план запросов и миграцию на копии рабочей нагрузки. В эксплуатации следите за числом затрагиваемых партиций, задержками запросов и записи, перекосом объёма и наличием будущих частей. Если ключ не улучшает ни один измеряемый сценарий, отложите рефакторинг.
Поведение и ограничения в статье относятся к PostgreSQL 16. Синтаксис и возможности для другой версии сверяйте с документацией именно этой версии.
Инфографика для маркетплейса: Три главных преимущества товара короткими фразами, выноски в безопасной зоне превью, точный размер площадки.
Спросить за 60 ₽Источники: PostgreSQL 16: таблицы с секционированием, PostgreSQL 16: планировщик запросов и отсечение секций
Частые вопросы
Как понять, что ключ партиционирования выбран неправильно?
Что выбрать для таблицы событий: дату или tenant_id?
Можно ли сохранить глобальный первичный ключ после партиционирования по дате?
Что будет, если запись попадёт в диапазон, для которого нет партиции?
Если у меня другая версия PostgreSQL, советы применимы?
Что делать, если после миграции запросы не ускорились?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.