Ключ сортировки и партиции событий
Исходная таблица «Событий» использует ORDER BY (course_id, lesson_id, occurred_at, event_id) и месячную партицию по времени действия. Эти параметры не являются случайной концовкой DDL. Разберём, какие вопросы они помогают задавать и какие ожидания были бы неверными. Результат урока — обоснованная физическая модель для выбранных отчётов.
Вы уже знаете форму событий и пакетную доставку. Теперь рассматриваем чтение данных, не выполняя запросов и замеров. Маленькая фикстура не позволяет подтвердить выигрыш скорости; приведённые планы рассуждения относятся к устройству таблицы и будущей проверке на подходящем объёме.
Сначала вопросы редактора
Основной отчёт группирует действия по уроку внутри курса за период. Частый фильтр выглядит как course_id = 'web' и диапазон occurred_at. Иногда редактор выбирает конкретный lesson_id. Поэтому начало сортировки связано с курсом и уроком: похожие записи располагаются ближе внутри частей.
Запрос только по одному anon_id отвечает другой задаче. Его поле не находится в начале выбранного ключа. Такая схема не обязана быть оптимальной для подробной истории отдельного посетителя. Если продукт позже потребует эту операцию постоянно, понадобится отдельное проектирование, а не уверенность, что один ключ ускоряет всё.
Рассмотрим полный запрос количества доставок конкретного материала. Это ещё исходный слой без устранения повторов, поэтому название результата должно сохранять слово «доставки».
SELECT event_name, count() AS deliveries
FROM events_lab.deliveries
WHERE course_id = 'web'
AND lesson_id = 'html-start'
AND occurred_at >= toDateTime64('2026-10-01 00:00:00', 3, 'UTC')
AND occurred_at < toDateTime64('2026-10-05 00:00:00', 3, 'UTC')
GROUP BY event_name
ORDER BY event_name;
Полуоткрытый интервал включает первое число и исключает начало пятого. Он удобен для соседних периодов: одно пограничное событие не считается дважды. Условие <= на последнюю миллисекунду дня потребовало бы помнить точность типа и легко пропустить более точный будущий формат.
Сортировка не задаёт единственность
В ClickHouse ключ MergeTree служит физическому расположению и индексированию, но не запрещает одинаковые строки. Идентичный event_id может встречаться несколько раз. Даже явное слово PRIMARY KEY не следует читать как обычное ограничение уникальности рабочей SQL-базы. Для логических событий понадобится другая модель чтения.
Руководство MergeTree описывает разреженный первичный индекс и ключ сортировки. Применение к нашим данным означает, что запрос способен отбирать участки, однако точная стоимость зависит от частей, распределения значений и условий. Здесь не получен план выполнения и не измерено число прочитанных строк.
Если убрать фильтр курса и искать только по дате, новое условие не будет соответствовать первому полю ключа. Это не делает запрос запрещённым, но меняет возможности чтения. Порядок полей следует обсуждать с перечнем рабочих запросов. Выбор «сначала время, потому что события имеют время» не всегда соответствует аналитической нагрузке.
Месяц как единица управления
Партиция по toYYYYMM(occurred_at) разделяет крупные периоды. Она помогает управлять хранением и операциями над месяцами. Это не список пользователей и не механизм авторизации. Если отчёт разрешён конкретной команде, право доступа должно обеспечиваться отдельно от партиционирования.
Не создавайте партицию на каждый event_id или anon_id. Такой выбор порождает множество мелких групп и мешает нормальной работе частей. В учебном наборе четыре посетителя могут скрыть проблему, но реальный поток быстро изменит масштаб. Поэтому физическое решение нужно оценивать по ожидаемой кардинальности поля.
Для событий в пределах одного месяца все шестнадцать доставок попадут в одну партицию. Позднее событие первого октября тоже относится к октябрю, несмотря на получение пятого. Если оно придёт в ноябре, партиция всё равно определяется октябрьским временем действия. Это согласуется с историческим отчётом и требует учитывать допустимую давность доставки.
Неизменяемые поля повторного события
В следующей главе ReplacingMergeTree будет считать одинаковыми строки с совпадающим ключом сортировки. Поэтому идентификатора недостаточно, если остальные поля ключа меняются. Повтор того же event_id с другим lesson_id может стать другой ключевой записью, а перенос времени через месяц — другой партицией.
Контракт версии один запрещает изменение anon_id, course_id, lesson_id, event_name и occurred_at для одного event_id. Разрешена новая доставка с другим временем получения и большей версией. Исправление самого действия является отдельной задачей: понадобится политика отмены или корректировки, которую нельзя скрывать под простым повтором.
Это правило нужно применять в сборщике или контроле качества. ClickHouse не знает, что ошибочный lesson_id нарушил идентичность события. Если данные поступают из нескольких производителей, они должны согласовать генерацию идентификаторов и правила повторной отправки. Иначе красивый SQL устранит только часть повторов.
Как изменить схему осознанно
Изменение ключа обычно означает создание новой таблицы и перенос данных с проверкой результата, а не бездумное редактирование исходного DDL. В рабочем проекте нужно выбрать окно переноса, маршрут новых вставок и возможность отката. Наша серия сохраняет первоначальный исходник и отдельный логический слой, не выполняя внешнюю миграцию.
Для будущего сравнения полезны одинаковые запросы, одинаковый набор и показатели чтения из среды ClickHouse. До фактического запуска не указывайте процент ускорения. Сама ссылка на документацию доказывает механизм, а не результат именно вашего объёма и распределения событий.
Ширина ключа и форма запроса
Добавление всех столбцов в ключ не является универсальной оптимизацией. Ключ становится сложнее, а совпадение для ReplacingMergeTree строже. Если включить изменяемое время получения, две доставки одного события перестанут иметь одинаковый ключ. Мы сознательно оставляем его вне ORDER BY логического слоя.
Фильтр должен сохранять удобный диапазон времени. Сравнение исходного occurred_at с границами выражает период прямо. Произвольное преобразование каждой строки в текст может усложнить чтение и скрыть временную семантику. Форматируйте дату для экрана после выбора данных, когда это возможно.
В будущем отчёте по всем курсам стоит заново рассмотреть начало ключа, но сначала определить частоту таких запросов. Одна редкая служебная выборка не обязательно оправдывает изменение основной таблицы. Разные потребители могут использовать отдельные агрегаты, сохраняя исходный слой согласованным с контрактом.
Сейчас ключ и партиция связаны с задачей, а их ограничения названы. Следующий урок создаст таблицу логических событий и покажет, почему фоновое объединение частей не позволяет сразу доверять обычному count() как окончательному числу уникальных действий.