Партиции таблиц
Что такое партиции таблиц в ClickHouse?
Партиции группируют части данных таблицы в семействе MergeTree в организованные логические единицы, что является способом организации данных, который концептуально значим и соответствует определенным критериям, таким как временные диапазоны, категории или другие ключевые атрибуты. Эти логические единицы упрощают управление, запрос и оптимизацию данных.
PARTITION BY
Партиционирование можно включить при первоначальном определении таблицы с помощью оператора PARTITION BY. Этот оператор может содержать SQL-выражение для любых столбцов, результаты которого определяют, к какой партиции относится строка.
Чтобы проиллюстрировать это, мы расширяем пример таблицы Что такое части таблиц, добавляя оператор PARTITION BY toStartOfMonth(date), который организует части данных таблицы на основе месяцев продаж недвижимости:
Вы можете запрашивать эту таблицу в нашем ClickHouse SQL Playground.
Структура на диске
Когда набор строк вставляется в таблицу, вместо создания (по крайней мере) одного единого блока данных, содержащего все вставленные строки (как описано здесь), ClickHouse создает новый блок данных для каждого уникального значения ключа партиции среди вставленных строк:

Сервер ClickHouse сначала разделяет строки из примера вставки с 4 строками, нарисованными на диаграмме выше, по значению ключа партиции toStartOfMonth(date). Затем для каждой идентифицированной партиции строки обрабатываются как обычно, выполняя несколько последовательных шагов (① Сортировка, ② Разделение на столбцы, ③ Сжатие, ④ Запись на диск).
Обратите внимание, что при включенном партиционировании ClickHouse автоматически создает MinMax индексы для каждой части данных. Это просто файлы для каждого столбца таблицы, используемого в выражении ключа партиционирования, содержащие минимальные и максимальные значения этого столбца в пределах части данных.
Слияния по партициям
С включенным партиционированием ClickHouse только сливает части данных внутри, но не между партициями. Мы схематически изображаем это для нашего примера таблицы выше:

Как изображено на диаграмме выше, части, принадлежащие различным партициям, никогда не сливаются. Если выбран ключ партиции с высокой кардинальностью, то части, распространившиеся по тысячам партиций, никогда не будут кандидатами на слияние - превышая предварительно настроенные лимиты и вызывая dreaded Слишком много ^^частей^^ ошибку. Решить эту проблему просто: выберите разумный ключ партиционирования с кардинальностью менее 1000..10000.
Мониторинг партиций
Вы можете запрашивать список всех существующих уникальных партиций нашей примерной таблицы, используя виртуальную колонку _partition_value:
В качестве альтернативы, ClickHouse отслеживает все части и партиции всех таблиц в системной таблице system.parts, и следующий запрос возвращает для нашей примерной таблицы выше список всех партиций, плюс текущее количество активных частей и сумма строк в этих частях на каждую партицию:
Для чего используются партиции таблиц?
Управление данными
В ClickHouse партиционирование в первую очередь является функцией управления данными. Логически организуя данные на основе выражения партиции, каждая партиция может управляться независимо. Например, схема партиционирования в таблице выше позволяет сценариям, в которых только последние 12 месяцев данных сохраняются в основной таблице, автоматически удаляя старые данные с помощью правила TTL (см. добавленную последнюю строку DDL-условия):
Поскольку таблица партиционирована по toStartOfMonth(date), целые партиции (наборы частей таблиц), удовлетворяющие условию TTL, будут удалены, что сделает операцию очистки более эффективной, не требуя переписывания частей.
Аналогично, вместо удаления старых данных их можно автоматически и эффективно переместить в более экономичное уровень хранения:
Оптимизация запросов
Партиции могут помочь с производительностью запросов, но это зависит в значительной степени от паттернов доступа. Если запросы нацелены только на несколько партиций (в идеале одну), производительность может потенциально улучшиться. Это обычно полезно только в том случае, если ключ партиционирования не является частью первичного ключа, и вы фильтруете по нему, как показано в примере запроса ниже.
Запрос выполняется над нашей примерной таблицей выше и вычисляет самую высокую цену всех проданных объектов недвижимости в Лондоне в декабре 2020 года, фильтруя по как колонке (date), использованной в ключе партиционирования таблицы, так и по колонке (town), использованной в первичном ключе таблицы (и date не является частью первичного ключа).
ClickHouse обрабатывает этот запрос, применяя последовательность методов обрезки, чтобы избежать оценки нерелевантных данных:

① Обрезка партиций: MinMax индексы используются для игнорирования целых партиций (наборов частей), которые логически не могут соответствовать фильтру запроса по колонкам, использованным в ключе партиционирования таблицы.
② Обрезка гранул: Для оставшихся частей данных после шага ① используется их первичный индекс, чтобы игнорировать все гранулы (блоки строк), которые логически не могут соответствовать фильтру запроса по колонкам, использованным в первичном ключе таблицы.
Мы можем наблюдать эти шаги обрезки данных, инспектируя физический план выполнения запроса для нашего примерного запроса выше через EXPLAIN оператор:
Вывод выше показывает:
① Обрезка партиций: Строки 7-18 вывода EXPLAIN выше показывают, что ClickHouse сначала использует MinMax индекс поля date, чтобы идентифицировать 11 из 3257 существующих гранул (блоков строк), хранящихся в 1 из 436 существующих активных частях данных, которые содержат строки, соответствующие фильтру date запроса.
② Обрезка гранул: Строки 19-24 вывода EXPLAIN выше указывают на то, что ClickHouse затем использует первичный индекс (созданный по полю town) части данных, идентифицированной на шаге ①, для дальнейшего уменьшения количества гранул (которые потенциально также могут соответствовать фильтру town запроса) с 11 до 1. Это также отражено в выводе ClickHouse-клиента, который мы напечатали выше для выполнения запроса:
Что означает, что ClickHouse просканировал и обработал 1 гранулу (блок 8192 строк) за 6 миллисекунд для вычисления результата запроса.
Партиционирование в первую очередь является функцией управления данными
Обратите внимание, что запросы по всем партициям обычно медленнее, чем выполнение того же запроса на непартиционированной таблице.
С партиционированием данные обычно распределяются по большему количеству частей данных, что часто приводит к тому, что ClickHouse сканирует и обрабатывает больший объем данных.
Мы можем продемонстрировать это, запустив тот же запрос и на таблице Что такое части таблиц (без включенного партиционирования), и на текущей примерной таблице выше (с включенным партиционированием). Обе таблицы содержат одинаковые данные и количество строк:
Тем не менее, таблица с включенным партиционированием имеет больше активных частей данных, потому что, как упоминалось выше, ClickHouse только сливает части данных внутри, но не между партициями:
Как показано выше, партиционированная таблица uk_price_paid_simple_partitioned имеет более 600 партиций, и поэтому 600 306 активных частей данных. В то время как для нашей непартиционированной таблицы uk_price_paid_simple все начальные части данных могут быть слиты в одну активную часть в результате фоновых слияний.
Когда мы проверяем физический план выполнения запроса с оператором EXPLAIN для нашего примерного запроса выше без фильтра по партиции, выполняя его через партиционированную таблицу, мы можем увидеть в строках 19 и 20 вывода ниже, что ClickHouse определил 671 из 3257 существующих гранул (блоков строк), распределенных по 431 из 436 существующих активных частях данных, которые потенциально содержат строки, соответствующие фильтру запроса, и поэтому будут просканированы и обработаны движком запросов:
Физический план выполнения запроса для того же примерного запроса, выполняемого над таблицей без партиций показывает в строках 11 и 12 вывода ниже, что ClickHouse определил 241 из 3083 существующих блоков строк в единственной активной части данных таблицы, которые потенциально содержат строки, соответствующие фильтру запроса:
Для выполнения запроса через партиционированную версию таблицы ClickHouse сканирует и обрабатывает 671 блока строк (~ 5.5 миллиона строк) за 90 миллисекунд:
В то время как для выполнения запроса через непартиционированную таблицу ClickHouse сканирует и обрабатывает 241 блока (~ 2 миллиона строк) за 12 миллисекунд: