Learning Platform
Глоссарий Troubleshooting
Урок 03.02 · 15 мин
Средний
LowCardinalityDictionary EncodingStringПроизводительность

LowCardinality: словарное кодирование

Столбец country в таблице аналитических событий содержит ~200 уникальных значений, но миллиарды строк. Хранить строку ‘United States’ целиком в каждой из миллиарда строк — расточительство. LowCardinality решает эту проблему через dictionary encoding: повторяющиеся значения хранятся в словаре один раз, а в столбце — только индексы.


Как работает dictionary encoding

Без LowCardinality каждая строка хранит полное значение:

Строка 0: "Chrome"    (6 байт + overhead)
Строка 1: "Firefox"   (7 байт + overhead)
Строка 2: "Chrome"    (6 байт + overhead)
Строка 3: "Safari"    (6 байт + overhead)
Строка 4: "Chrome"    (6 байт + overhead)

С LowCardinality(String) ClickHouse создаёт словарь и заменяет значения на индексы:

Dictionary Encoding: словарь + индексы
Словарь (Dictionary)Уникальные значенияСловарь: хранит каждое уникальное значение ровно один раз. При 50 уникальных browser_name общий размер словаря -- несколько сотен байт. Словарь загружается в RAM при открытии part.
Индексы (Indices)Целые числа вместо строкИндексы: вместо строк хранятся целые числа (UInt8 при кардинальности до 255, UInt16 при кардинальности до 65535). Размер индекса: 1-2 байта вместо 6-15 байт на строку. Сравнение строк заменяется сравнением целых чисел -- операция в 5-10 раз быстрее.

Результат:

  • Хранение: вместо 6-15 байт строки — 1-2 байта индекса. Экономия 5-10x по объёму.
  • Запросы: фильтрация WHERE browser = 'Chrome' превращается в поиск индекса 0 в словаре, затем сравнение целых чисел вместо строковое сравнение. Ускорение до 10x на типичных аналитических запросах.

Когда LowCardinality эффективен

Правило: LowCardinality даёт максимальный эффект при менее 10 000 уникальных значений в столбце. Работает хорошо до ~100 000 уникальных значений. Выше этого порога словарь растёт, индексы требуют больше байт, и преимущество теряется.

Хорошие кандидаты для LowCardinality(String):

СтолбецУникальных значенийЭффект
country~200Отличный
browser_name~50Отличный
os_name~20Отличный
http_method7 (GET, POST, …)Отличный
city~50 000Хороший
user_agentмиллионыПлохой (не использовать)
emailмиллионыПлохой (не использовать)
WARNING

LowCardinality с более чем 100 000 уникальных значений может работать медленнее обычного String. Словарь перестаёт помещаться в кэш процессора, и dictionary lookup добавляет overhead вместо экономии. Проверьте кардинальность перед применением: SELECT uniq(column) FROM table.


Применение LowCardinality

Задать при создании таблицы:

CREATE TABLE events (
    event_date Date,
    country LowCardinality(String),
    browser LowCardinality(String),
    os LowCardinality(String),
    url String  -- высокая кардинальность, обычный String
) ENGINE = MergeTree()
ORDER BY (event_date, country)

Или изменить тип существующего столбца:

ALTER TABLE events MODIFY COLUMN country LowCardinality(String)

LowCardinality можно применить к нескольким базовым типам: String, FixedString, Date, DateTime, и числовым типам (Int, UInt, Float). Это не только строковая оптимизация.


LowCardinality vs Enum8/Enum16

Enum8 и Enum16 тоже хранят строки как числа, но с существенным отличием:

СвойствоLowCardinality(String)Enum8 / Enum16
Добавление нового значенияАвтоматическиALTER TABLE … MODIFY COLUMN
Максимум значений~100 000 (практический)256 (Enum8) / 65536 (Enum16)
ВалидацияНет (любая строка)Да (только объявленные)
Размер хранения1-2 байта (автоматически)1 байт (Enum8) / 2 байта (Enum16)
ORDER BY эффективностьПо словарному порядкуПо числовому значению

Рекомендация: используйте LowCardinality(String) по умолчанию. Enum оправдан только когда нужна строгая валидация значений (например, статус заказа ‘pending’/‘paid’/‘shipped’/‘cancelled’ — новые значения не должны появляться без явного изменения схемы).


Влияние на запросы: числа с примером

Таблица events, 1 миллиард строк, столбец country (200 уникальных стран):

МетрикаStringLowCardinality(String)Разница
Размер столбца (несжатый)~12 ГБ~1.5 ГБ8x меньше
GROUP BY country~3.2 с~0.3 с10x быстрее
WHERE country = ‘RU’~1.8 с~0.2 с9x быстрее

Числа приблизительные и зависят от оборудования, но порядок ускорения стабильно воспроизводится.


Ключевые выводы

  1. LowCardinality заменяет значения индексами — словарь хранит уникальные значения, столбец — 1-2 байта целочисленных индексов. Ускорение запросов до 10x, сокращение хранения в 5-8 раз.
  2. Оптимальная кардинальность — до 10 000 уникальных значений. Работает до ~100 000. Выше — проверяйте на реальных данных.
  3. LowCardinality(String) вместо Enum — более гибкий (не нужен ALTER TABLE для новых значений), достаточно эффективный для большинства случаев.
  4. Проверяйте кардинальность перед применением: SELECT uniq(column) FROM table. Высокая кардинальность (email, UUID) — противопоказание.
Dictionary Encoding: кросс-форматный анализ

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Столбец browser_name содержит ~50 уникальных значений (Chrome, Firefox, Safari...) в таблице с 1 миллиардом строк. Какой тип обеспечит максимальное ускорение запросов?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 8