В аналитической витрине таблица партиционирована по дате загрузки, но отчёты фильтруют дату события. Разберите, какой побочный эффект даст такой выбор ключа.
CREATE TABLE events (
event_id BIGINT,
event_time TIMESTAMP,
ingestion_date DATE,
payload JSON
) PARTITION BY RANGE (ingestion_date);
SELECT count(*)
FROM events
WHERE event_time >= TIMESTAMP '2025-01-01'
AND event_time < TIMESTAMP '2025-02-01';
Партиционирование по ingestion_date не позволяет эффективно отсечь разделы, если запрос ограничивает только event_time. Планировщик не знает, какие разделы содержат подходящие события, поэтому ему обычно приходится проверять большинство или все разделы, а затем фильтровать строки внутри них.
Если основной шаблон запросов использует диапазоны event_time, таблицу следует партиционировать по нему либо создать другой физический путь доступа, например кластеризацию, сортировку или отдельную витрину. Это не означает, что партиционирование по времени загрузки всегда ошибочно: оно полезно для управления поступлением данных и удалением старых загрузок, но не совпадает с аналитическим предикатом.
Партиционирование появилось как способ разделить большую таблицу на управляемые физические части. Это уменьшает объём данных, который нужно читать, упрощает удаление старых периодов и может улучшить параллельную обработку.
Ключевой механизм ускорения запросов называется отсечением разделов. Система исключает разделы до чтения их содержимого, если условие запроса можно сопоставить с диапазоном или списком значений ключа партиционирования.
В примере события могут прибывать с задержкой. Поэтому ingestion_date описывает момент загрузки, а event_time — момент, к которому относится событие. Запрос строится по бизнес-времени события, но физическое разделение построено по техническому времени доставки.
При этом условие по event_time не даёт надёжного вывода о том, в каком разделе находится строка. Событие за январь может попасть в январский, февральский или ещё более поздний раздел загрузки.
Неверный выбор приводит к чтению большого объёма данных, росту задержки и стоимости запроса. Особенно это заметно в хранилищах, где стоимость или время выполнения зависят от объёма просканированных данных.
Для эффективного отсечения ключ партиционирования должен коррелировать с наиболее частыми и селективными условиями запросов. Если отчёты почти всегда ограничивают event_time, естественным кандидатом становится партиционирование по календарным интервалам этого поля.
В текущей схеме условие по event_time нельзя преобразовать в ограничение на ingestion_date: задержка доставки не ограничена известным небольшим диапазоном. Поэтому движок не может безопасно пропустить разделы, не рискуя потерять подходящие строки.
У партиционирования по event_time есть компромисс. Опоздавшее событие придётся записать в исторический раздел, а не просто добавить в текущий раздел загрузки. Это усложняет приём данных и может потребовать операций исправления или обслуживания старых разделов.
Партиционирование по ingestion_date сохраняет другие преимущества: удобно удалять данные по сроку хранения, отслеживать ежедневные загрузки и изолировать недавно поступившие данные. Поэтому на практике иногда используют два уровня физической организации: один ключ для разделов, а внутри разделов — сортировку или кластеризацию по event_time. Конкретная возможность зависит от аналитической СУБД.
Важно отличать отсечение разделов от обычного фильтра. Фильтр по event_time уменьшает число возвращаемых строк после чтения, но не обязательно уменьшает объём прочитанных разделов. Для оценки результата нужно проверять план выполнения и фактически прочитанный объём, а не только текст SQL.
Команда хранила события мобильного приложения в разделах по дате загрузки, потому что данные часто приходили с задержкой и их было удобно удалять по сроку хранения. Суточный отчёт по дате события начал читать почти всю таблицу: события одного дня распределялись по многим разделам загрузки.
Рассматривались три варианта. Перенос партиционирования на event_time улучшал отчёты, но усложнял поздние записи и обслуживание исторических разделов. Создание индекса по event_time могло помочь точечным запросам, однако для больших диапазонов и колоночного хранилища его выгода зависела от реализации и распределения данных. Перестройка данных внутри разделов по event_time сохраняла удобное удаление по дате загрузки, но не давала такого же сильного отсечения разделов.
Выбрали сохранение разделов по ingestion_date с кластеризацией внутри них по event_time и отдельную витрину для наиболее частых отчётов, разделённую по бизнес-времени. Это сохранило удобство приёма и очистки сырых данных, а отчётам дало физическую организацию, соответствующую их фильтрам.
Нет, одной текущей задержки недостаточно для вывода. Быстрый результат может быть следствием малого объёма данных, кэша или высокой мощности кластера. Нужно смотреть план, число просмотренных разделов и фактически прочитанные данные; при росте таблицы скрытая проблема проявится сильнее.
event_time и отказаться от ingestion_date?Потому что ключ должен учитывать не только чтение, но и запись, исправления и жизненный цикл данных. Поздние события могут часто изменять закрытые исторические разделы, создавать большое число мелких файлов или вызывать дорогое обслуживание. Если операции хранения и удаления естественно привязаны к дате загрузки, техническое время может оставаться полезным ключом.
ingestion_date?Тогда система сможет отсечь разделы по этому условию, а внутри выбранных разделов применит фильтр по event_time. Например, ограничение загрузки последними тридцатью днями может резко уменьшить чтение, но оно безопасно только если известно, что нужные события не могли прийти раньше или позже этого диапазона. Такое условие может ускорить запрос, однако не исправляет несоответствие ключа партиционирования бизнес-времени без доказанного ограничения задержки.