Оптимальный выбор типов данных
В row-oriented СУБД выбор между Int32 и Int64 редко влияет на производительность: строки хранятся вместе, и разница в 4 байта на столбец тонет в общем весе строки. В ClickHouse каждый столбец хранится в отдельном .bin файле, и каждый лишний байт умножается на миллиарды строк. Выбор типа данных — это первый и самый простой способ сократить объём хранения и ускорить запросы.
Целочисленные типы: каждый байт на счету
ClickHouse предлагает знаковые (Int8, Int16, Int32, Int64, Int128, Int256) и беззнаковые (UInt8, UInt16, UInt32, UInt64, UInt128, UInt256) целые типы. Разница в размерах значительна:
Практический пример: столбец с HTTP-статусами (200, 301, 404, 500). Максимальное значение — 599. UInt8 не подходит (максимум 255), но UInt16 (максимум 65535) — более чем достаточно:
- UInt16: 2 байта на строку. При 1 миллиарде строк = 2 ГБ
- Int32: 4 байта на строку. При 1 миллиарде строк = 4 ГБ
- Int64: 8 байт на строку. При 1 миллиарде строк = 8 ГБ
Разница между UInt16 и Int64 — 6 ГБ на один столбец. И это до сжатия: LZ4 сожмёт оба, но меньшие типы сжимаются эффективнее (меньше энтропии в старших байтах).
Правило выбора целого типа: определите максимально возможное значение, выберите минимальный тип, вмещающий этот диапазон. Если значения всегда неотрицательны, используйте UInt (беззнаковый) — он вдвое увеличивает диапазон при том же размере.
DateTime vs DateTime64: платите только за нужную точность
ClickHouse предлагает два типа для хранения временных меток:
Критерий выбора прост: если данные имеют точность до секунды (логи веб-сервера, аналитические события, регистрации пользователей), DateTime экономит 4 байта на каждую строку. DateTime64 нужен только когда миллисекунды (или микросекунды) действительно несут информацию — трейсинг запросов, HFT-транзакции, IoT-сенсоры с частотой выше 1 Гц.
String vs FixedString(N): переменная длина против фиксированной
String — тип переменной длины без ограничений. Каждое значение хранит фактические байты плюс длину. Подходит для большинства текстовых данных.
FixedString(N) — ровно N байт на значение. Короткие значения дополняются нулевыми байтами. Нет overhead на хранение длины. Подходит для данных с гарантированно фиксированной длиной:
| Данные | Тип | Размер на строку |
|---|---|---|
| ISO country code (‘RUS’, ‘USA’) | FixedString(3) | 3 байта |
| MD5 hash (hex, 32 символа) | FixedString(32) | 32 байта |
| UUID (без дефисов, 32 символа) | UUID (нативный тип) | 16 байт |
| Email адрес (переменная длина) | String | переменная |
| URL (переменная длина) | String | переменная |
FixedString(N) дополняет короткие значения нулевыми байтами до N. Если передать строку длиннее N байт, ClickHouse выбросит ошибку. Используйте FixedString только для данных с гарантированно одинаковой длиной (коды стран, хэши). Для обычного текста используйте String.
Float32/Float64 vs Decimal: точность вычислений
Типы с плавающей запятой (Float32, Float64) хранят приблизительные значения. Для большинства метрик (температура, latency, CPU usage) это допустимо. Но для денежных вычислений приближение недопустимо:
-- Float64 теряет точность
SELECT toFloat64(0.1) + toFloat64(0.2) AS result
-- Результат: 0.30000000000000004 (не 0.3)
-- Decimal64(2) гарантирует точность
SELECT toDecimal64(0.1, 2) + toDecimal64(0.2, 2) AS result
-- Результат: 0.30
| Тип | Размер | Точность (всего цифр) | Применение |
|---|---|---|---|
| Decimal32(S) | 4 байта | до 9 цифр | Цены до миллионов с копейками |
| Decimal64(S) | 8 байт | до 18 цифр | Финансовые суммы, бухгалтерия |
| Decimal128(S) | 16 байт | до 38 цифр | Криптовалюта (18 decimals wei) |
| Decimal256(S) | 32 байта | до 76 цифр | Экзотические случаи |
Параметр S — количество знаков после запятой. Decimal64(2) хранит до 18 цифр, из которых 2 после запятой.
Проверка реального размера через system.columns
Не полагайтесь на теоретические расчёты — измерьте реальный размер после загрузки данных:
SELECT
name AS column_name,
type,
formatReadableSize(sum(data_compressed_bytes)) AS compressed,
formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 2) AS ratio
FROM system.columns
WHERE table = 'events'
GROUP BY name, type
ORDER BY sum(data_uncompressed_bytes) DESC
Compression ratio зависит от распределения данных. Столбец UInt8 со значениями 0 и 1 сжимается в десятки раз. Столбец String с UUID сжимается слабо. Реальные замеры всегда важнее теории.
Числовые типы SQL: INT, BIGINT, NUMERIC и почему деньги — не FLOAT Integer Encodings: как кодирование зависит от выбора типаПроверяйте реальный размер через system.columns: compression ratio зависит от распределения данных. Столбец с тысячей уникальных значений сожмётся лучше, чем столбец с миллионом уникальных значений, даже при одинаковом типе.
Ключевые выводы
- Минимальный тип по диапазону — определите максимальное значение и выберите наименьший вмещающий тип. Int8 вместо Int32 экономит 3 байта на строку, что при миллиарде строк равно 3 ГБ.
- DateTime по умолчанию — используйте DateTime64 только когда точность ниже секунды действительно нужна. Экономия: 4 байта на строку.
- Decimal для денег, Float для метрик — Float64 теряет копейки. Decimal64(2) гарантирует точность финансовых вычислений.
- Измеряйте, а не угадывайте — system.columns показывает реальные размеры после сжатия. Теоретический выигрыш не всегда совпадает с практическим.