Выбор типов данных
Одна из основных причин производительности запросов в ClickHouse — это его эффективное сжатие данных. Меньший объем данных на диске приводит к более быстрым запросам и вставкам за счет минимизации накладных расходов на ввод-вывод. Столбцовая архитектура ClickHouse естественным образом располагает похожие данные рядом, позволяя кодекам и алгоритмам сжатия значительно уменьшать размер данных. Чтобы максимально использовать эти преимущества сжатия, важно тщательно выбирать подходящие типы данных.
Эффективность сжатия в ClickHouse в значительной степени зависит от трех факторов: ключа сортировки, типов данных и кодеков, все из которых определяются через схему таблицы. Выбор оптимальных типов данных обеспечивает немедленные улучшения как в хранении, так и в производительности запросов.
Некоторые простые рекомендации могут значительно улучшить схему:
-
Используйте строго типизированные данные: Всегда выбирайте правильный тип данных для колонок. Числовые и дата-поля должны использовать соответствующие числовые и датовые типы, а не общие строковые типы. Это обеспечивает корректную семантику для фильтрации и агрегатов.
-
Избегайте nullable колонок: Nullable колонки вводят дополнительные накладные расходы, поддерживая отдельные колонки для отслеживания нулевых значений. Используйте Nullable только в том случае, если это явно необходимо для различения пустых и нулевых состояний. В противном случае обычно достаточно значений по умолчанию или нулевых, эквивалентных значениям. Для получения дополнительной информации о том, почему этот тип следует избегать, если это не необходимо, см. Избегайте Nullable колонок.
-
Минимизируйте точность чисел: Выбирайте числовые типы с минимальной шириной бит, которая все же охватывает ожидаемый диапазон данных. Например, предпочтите UInt16 вместо Int32, если отрицательные значения не нужны, и диапазон вписывается в 0–65535.
-
Оптимизируйте точность дат и времени: Выбирайте самый грубый тип даты или времени, который соответствует требованиям запроса. Используйте Date или Date32 для полей только с датами, и предпочтите DateTime над DateTime64, если миллисекундная или более высокая точность не является обязательной.
-
Используйте LowCardinality и специализированные типы: Для колонок с количеством уникальных значений менее примерно 10,000 используйте типы LowCardinality, чтобы значительно сократить объем хранения за счет кодирования словарей. Аналогично, используйте FixedString только в том случае, если значения колонки являются строго строками фиксированной длины (например, коды стран или валют), и предпочтите типы Enum для колонок с конечным набором возможных значений для создания эффективного хранения и встроенной проверки данных.
-
Enums для проверки данных: Тип Enum может быть использован для эффективного кодирования перечисляемых типов. Enums могут занимать 8 или 16 бит в зависимости от числа уникальных значений, которые они должны хранить. Рассматривайте возможность использования этого типа, если вам нужна либо связанная проверка во время вставки (необъявленные значения будут отклонены), либо вы хотите выполнять запросы, которые используют естественный порядок значений Enum, например, представьте колонку обратной связи, содержащую ответы пользователей Enum(':(' = 1, ':|' = 2, ':)' = 3).
Пример
ClickHouse предлагает встроенные инструменты для упрощения оптимизации типов. Например, вывод схемы может автоматически определить начальные типы. Рассмотрим набор данных Stack Overflow, общедоступный в формате Parquet. Запуск простого вывода схемы с помощью команды DESCRIBE
предоставляет первоначальную не оптимизированную схему.
По умолчанию ClickHouse отображает их в эквивалентные Nullable типы. Это предпочтительно, так как схема основана только на выборке строк.
Обратите внимание, что ниже мы используем шаблон glob *.parquet
, чтобы прочитать все файлы в папке stackoverflow/parquet/posts.
Применяя наши ранние простые правила к таблице постов, мы можем определить оптимальный тип для каждой колонки:
Колонка | Числовая | Мин, Мак | Уникальные значения | Нули | Комментарий | Оптимизированный тип |
---|---|---|---|---|---|---|
PostTypeId | Да | 1, 8 | 8 | Нет | Enum('Question' = 1, 'Answer' = 2, 'Wiki' = 3, 'TagWikiExcerpt' = 4, 'TagWiki' = 5, 'ModeratorNomination' = 6, 'WikiPlaceholder' = 7, 'PrivilegeWiki' = 8) | |
AcceptedAnswerId | Да | 0, 78285170 | 12282094 | Да | Различение Null с 0 значением | UInt32 |
CreationDate | Нет | 2008-07-31 21:42:52.667000000, 2024-03-31 23:59:17.697000000 | - | Нет | Миллисекундная точность не требуется, используйте DateTime | DateTime |
Score | Да | -217, 34970 | 3236 | Нет | Int32 | |
ViewCount | Да | 2, 13962748 | 170867 | Нет | UInt32 | |
Body | Нет | - | - | Нет | String | |
OwnerUserId | Да | -1, 4056915 | 6256237 | Да | Int32 | |
OwnerDisplayName | Нет | - | 181251 | Да | Рассматривайте Null как пустую строку | String |
LastEditorUserId | Да | -1, 9999993 | 1104694 | Да | 0 неиспользуемое значение может быть использовано для Nulls | Int32 |
LastEditorDisplayName | Нет | - | 70952 | Да | Рассматривайте Null как пустую строку. Проверили LowCardinality и не было пользы | String |
LastEditDate | Нет | 2008-08-01 13:24:35.051000000, 2024-04-06 21:01:22.697000000 | - | Нет | Миллисекундная точность не требуется, используйте DateTime | DateTime |
LastActivityDate | Нет | 2008-08-01 12:19:17.417000000, 2024-04-06 21:01:22.697000000 | - | Нет | Миллисекундная точность не требуется, используйте DateTime | DateTime |
Title | Нет | - | - | Нет | Рассматривайте Null как пустую строку | String |
Tags | Нет | - | - | Нет | Рассматривайте Null как пустую строку | String |
AnswerCount | Да | 0, 518 | 216 | Нет | Рассматривайте Null и 0 как одно и то же | UInt16 |
CommentCount | Да | 0, 135 | 100 | Нет | Рассматривайте Null и 0 как одно и то же | UInt8 |
FavoriteCount | Да | 0, 225 | 6 | Да | Рассматривайте Null и 0 как одно и то же | UInt8 |
ContentLicense | Нет | - | 3 | Нет | LowCardinality превосходит FixedString | LowCardinality(String) |
ParentId | Нет | - | 20696028 | Да | Рассматривайте Null как пустую строку | String |
CommunityOwnedDate | Нет | 2008-08-12 04:59:35.017000000, 2024-04-01 05:36:41.380000000 | - | Да | Рассматривайте значение по умолчанию 1970-01-01 для Nulls. Миллисекундная точность не требуется, используйте DateTime | DateTime |
ClosedDate | Нет | 2008-09-04 20:56:44, 2024-04-06 18:49:25.393000000 | - | Да | Рассматривайте значение по умолчанию 1970-01-01 для Nulls. Миллисекундная точность не требуется, используйте DateTime | DateTime |
Определение типа для колонки зависит от понимания её числового диапазона и количества уникальных значений. Чтобы найти диапазон всех колонок и количество уникальных значений, пользователи могут использовать простой запрос SELECT * APPLY min, * APPLY max, * APPLY uniq FROM table FORMAT Vertical
. Мы рекомендуем выполнять это на меньшем подмножестве данных, так как это может быть ресурсоемко.
В результате получается следующая оптимизированная схема (с учетом типов):
Избегайте nullable колонок
Nullable
column (e.g. Nullable(String)
) creates a separate column of UInt8
type. This additional column has to be processed every time a user works with a Nullable column. This leads to additional storage space used and almost always negatively affects performance.
To avoid Nullable
columns, consider setting a default value for that column. For example, instead of:
use
Consider your use case, a default value may be inappropriate.