Как оптимизировать хранение строк в ClickHouse

Помню как-то спорили с дата-инженерами, стоит ли использовать 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 он только навредит.
Ссылка на доку.
Мои статьи по ClickHouse на Хабре.








