После того как мы рассмотрели запросы к нашему набору данных со статистикой Википедии, перейдём к оптимизации эффективности его хранения в ClickHouse. В этом разделе показаны практические приёмы, позволяющие сократить потребность в хранилище без ущерба для производительности запросов.
Оптимизация типов
Общий подход к повышению эффективности хранения — использовать оптимальные типы данных.
Возьмём столбцы project и subproject. Эти столбцы имеют тип String, но содержат относительно небольшое количество уникальных значений:
SELECT
uniq(project),
uniq(subproject)
FROM wikistat;┌─uniq(project)─┬─uniq(subproject)─┐
│ 1332 │ 130 │
└───────────────┴──────────────────┘Это означает, что мы можем использовать тип данных LowCardinality(), в котором применяется словарное кодирование. Благодаря этому ClickHouse хранит внутренний идентификатор значения вместо исходного строкового значения, что, в свою очередь, позволяет существенно экономить место:
ALTER TABLE wikistat
MODIFY COLUMN `project` LowCardinality(String),
MODIFY COLUMN `subproject` LowCardinality(String)Мы также использовали тип UInt64 для столбца hits, который занимает 8 байт, хотя его максимальное значение сравнительно невелико:
SELECT max(hits)
FROM wikistat;┌─max(hits)─┐
│ 449017 │
└───────────┘Учитывая это, вместо него можно использовать UInt32: он занимает всего 4 байта и позволяет хранить значения до ~4 млрд:
ALTER TABLE wikistat
MODIFY COLUMN `hits` UInt32;Это уменьшит размер этого столбца в памяти как минимум в два раза. При этом размер на диске не изменится благодаря сжатию. Но будьте внимательны: не выбирайте слишком маленькие типы данных!
Специализированные кодеки
При работе с последовательными данными, например временными рядами, можно дополнительно повысить эффективность хранения с помощью специальных кодеков. Общая идея заключается в том, чтобы хранить изменения между значениями, а не сами абсолютные значения, что позволяет значительно сократить занимаемое место при работе с медленно меняющимися данными:
ALTER TABLE wikistat
MODIFY COLUMN `time` CODEC(Delta, ZSTD);Мы использовали кодек Delta для столбца time, который хорошо подходит для временных рядов.
Правильно выбранный ключ упорядочивания также может сэкономить место на диске.
Поскольку фильтрация обычно выполняется по пути, мы добавим path в ключ сортировки.
Для этого потребуется пересоздать таблицу.
Ниже показана команда CREATE для исходной и оптимизированной таблиц:
CREATE TABLE wikistat
(
`time` DateTime,
`project` String,
`subproject` String,
`path` String,
`hits` UInt64
)
ENGINE = MergeTree
ORDER BY (time);CREATE TABLE optimized_wikistat
(
`time` DateTime CODEC(Delta(4), ZSTD(1)),
`project` LowCardinality(String),
`subproject` LowCardinality(String),
`path` String,
`hits` UInt32
)
ENGINE = MergeTree
ORDER BY (path, time);И давайте посмотрим, сколько места занимают данные в каждой таблице:
SELECT
table,
formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
formatReadableSize(sum(data_compressed_bytes)) AS compressed,
count() AS parts
FROM system.parts
WHERE table LIKE '%wikistat%'
GROUP BY ALL;┌─table──────────────┬─uncompressed─┬─compressed─┬─parts─┐
│ wikistat │ 35.28 GiB │ 12.03 GiB │ 1 │
│ optimized_wikistat │ 30.31 GiB │ 2.84 GiB │ 1 │
└────────────────────┴──────────────┴────────────┴───────┘Оптимизированная таблица в сжатом виде занимает чуть более чем в 4 раза меньше места.