Помню как-то спорили с дата-инженерами, стоит ли использовать LowCardinality в DDL-запросах на создание объектов или это бесполезная фича и особого профита вообще не дает. С их стороны даже исследование какое-то было проведено. Как итог, LowCardinality стали использовать, но никто так и не смог наглядно показать в чем его преимущество.
На самом деле достаточно провести несколько тестов и все становится очевидно.
Создадим две таблицы. В одной тип данных определим как String во второй LowCardinality:
-- Таблица с обычным String
CREATE TABLE test_string (
id UInt64,
category String
) ENGINE = MergeTree()
ORDER BY id;
-- Таблица с LowCardinality
CREATE TABLE test_low_cardinality (
id UInt64,
category LowCardinality(String)
) ENGINE = MergeTree()
ORDER BY id;
Загружаем в каждую по 100 млн строк:
-- Заполняем первую таблицу (это займет пару секунд)
INSERT INTO test_string
SELECT
number AS id,
concat('category_name_', toString(number % 50)) AS category
FROM numbers(100000000);
-- Заполняем вторую таблицу такими же данными
INSERT INTO test_low_cardinality
SELECT
number AS id,
concat('category_name_', toString(number % 50)) AS category
FROM numbers(100000000);
Смотрим сколько данные занимают на диске:
SELECT
table,
column,
type,
formatReadableSize(data_uncompressed_bytes) AS uncompressed_size,
formatReadableSize(data_compressed_bytes) AS compressed_size_on_disk
FROM system.columns
WHERE table IN ('test_string', 'test_low_cardinality')
AND column = 'category'
ORDER BY table;
table |column |type |uncompressed_size|compressed_size_on_disk|
--------------------+--------+----------------------+-----------------+-----------------------+
test_low_cardinality|category|LowCardinality(String)|95.68 MiB |750.46 KiB |
test_string |category|String |1.56 GiB |9.19 MiB |
Можно заметить невооруженным глазом, что с LowCardinality данные на диске (compressed_size_on_disk) занимают в разы меньше места чем если бы мы просто хранили их в String. При распаковке данных (uncompressed_size) при чтении LowCardinality также сильно выигрывает. В оперативку будет загружено на порядок меньше данных, следовательно и сами запросы должны будут выполняться быстрее.
Проверим это на простых запросах на агрегацию:
-- Без LowCardinality
SELECT
category,
count() AS cnt
FROM test_string
GROUP BY category;
50 rows in result, 0.10 sec.
100.0%, Read 100.00 million rows, 2.38 GB
-- С LowCardinality
SELECT
category,
count() AS cnt
FROM test_low_cardinality
GROUP BY category;
50 rows in result, 0.02 sec.
100.0%, Read 100.00 million rows, 100.00 MB
Запрос с LowCardinality выполнился в 5 раз быстрее и задействовал всего 100 MB RAM против 2.38 GB.
Вот и говорите потом, что LowCardinality не дает профита.
P.S. Главное правило: используйте LowCardinality только для полей с небольшим количеством уникальных значений (статусы, категории, типы). Для уникальных ID или URL он только навредит.