Словари: layout-ы и конфигурация
External dictionaries — механизм ClickHouse для хранения справочных данных в оперативной памяти с point-lookup доступом. В отличие от обычных таблиц, словарь загружается в RAM целиком (или кэшируется) и обеспечивает O(1) доступ по ключу через функцию dictGet().
Типичные use case: обогащение фактов dimension-атрибутами (страна по IP, название города по коду, категория товара по product_id). Словарь заменяет JOIN с lookup-таблицей, давая прирост производительности до 25x.
CREATE DICTIONARY: DDL-синтаксис
CREATE DICTIONARY country_dict (
code String,
country_name String,
continent String
)
PRIMARY KEY code
SOURCE(CLICKHOUSE(TABLE 'countries' DB 'default'))
LAYOUT(HASHED())
LIFETIME(MIN 300 MAX 600);
Ключевые секции:
- PRIMARY KEY — столбец (или столбцы), по которому выполняется lookup. Определяет тип ключа для layout-а.
- SOURCE — откуда загружать данные. ClickHouse периодически перечитывает source для обновления словаря.
- LAYOUT — структура хранения в памяти. Определяет скорость lookup-а, потребление памяти и тип поддерживаемых ключей.
- LIFETIME — интервал автообновления. ClickHouse случайно выбирает момент обновления в диапазоне [MIN, MAX] для распределения нагрузки между репликами.
SOURCE: откуда загружать данные
| Source | Синтаксис | Когда использовать |
|---|---|---|
| ClickHouse table | SOURCE(CLICKHOUSE(TABLE 'tbl' DB 'db')) | Справочник в той же или удалённой инстанции |
| ClickHouse query | SOURCE(CLICKHOUSE(QUERY 'SELECT ...')) | Нужна трансформация или фильтрация |
| HTTP | SOURCE(HTTP(URL 'https://...' FORMAT CSV)) | Внешний REST API или файл на CDN |
| File | SOURCE(FILE(PATH '/path/file.csv' FORMAT CSV)) | Локальный файл на сервере |
| MySQL | SOURCE(MYSQL(HOST '...' PORT 3306 DB '...' TABLE '...')) | Справочник в MySQL |
| PostgreSQL | SOURCE(POSTGRESQL(HOST '...' PORT 5432 DB '...' TABLE '...')) | Справочник в PostgreSQL |
| MongoDB | SOURCE(MONGODB(HOST '...' COLLECTION '...')) | Справочник в MongoDB |
Наиболее распространённый: CLICKHOUSE(TABLE) — словарь из таблицы в том же кластере. CLICKHOUSE(QUERY) позволяет применять фильтры и трансформации при загрузке.
LIFETIME: автообновление словаря
-- Обновление каждые 300-600 секунд (5-10 минут)
LIFETIME(MIN 300 MAX 600)
-- Обновление отключено (данные загружаются один раз)
LIFETIME(0)
-- Обновление каждые 60 секунд (точный интервал)
LIFETIME(60)
ClickHouse случайно выбирает момент обновления в диапазоне [MIN, MAX]. Это распределяет нагрузку: если 10 реплик обновляют словарь, они не делают это одновременно. При LIFETIME(0) словарь загружается при старте сервера и никогда не обновляется.
Для production-словарей всегда указывайте MIN и MAX. LIFETIME(300) без MIN/MAX обновляет каждые 300 секунд на всех репликах одновременно — spike нагрузки на source.
10 layout-ов: структуры хранения в памяти
Подробнее о каждом layout-е
flat — массив
CREATE DICTIONARY status_dict (
id UInt64,
name String
)
PRIMARY KEY id
SOURCE(CLICKHOUSE(TABLE 'statuses'))
LAYOUT(FLAT())
LIFETIME(MIN 300 MAX 600);
Ключ = индекс массива. id=5 хранится в array[5]. Если ключи идут от 0 до 500K без больших пропусков — flat оптимален. При ключе id=1000000000 массив выделит память на миллиард элементов (большинство пустых) — неэффективно.
hashed — hash-таблица
LAYOUT(HASHED())
Универсальный layout для числовых ключей. Подходит для любого диапазона UInt64. Стандартный выбор, когда flat неприменим (ключи разреженные или диапазон слишком велик).
sparse_hashed — разреженная hash-таблица
LAYOUT(SPARSE_HASHED())
Экономит 30-50% памяти по сравнению с hashed за счёт более медленного lookup. Подходит для словарей с миллионами записей, где критичен объём RAM.
cache — LRU-кэш
LAYOUT(CACHE(SIZE_IN_CELLS 1000000))
Кэширует только запрашиваемые ключи. SIZE_IN_CELLS определяет максимальное количество записей в кэше. Cache miss вызывает запрос к source — повышает latency. Подходит для словарей с миллиардами записей и skewed access pattern (20% ключей покрывают 80% запросов).
complex_key_hashed — составной ключ
CREATE DICTIONARY geo_dict (
country_code String,
city_code String,
city_name String,
population UInt32
)
PRIMARY KEY country_code, city_code
SOURCE(CLICKHOUSE(TABLE 'cities'))
LAYOUT(COMPLEX_KEY_HASHED())
LIFETIME(MIN 600 MAX 1200);
Lookup по составному ключу: dictGet('geo_dict', 'city_name', tuple('RU', 'MSK')). Используйте, когда один столбец не уникально идентифицирует запись.
complex_key_cache — составной ключ + LRU
LAYOUT(COMPLEX_KEY_CACHE(SIZE_IN_CELLS 500000))
Комбинация composite key и LRU cache. Для больших словарей с составными ключами и hot key pattern.
ip_trie — IP-геолокация
CREATE DICTIONARY geoip_dict (
prefix String, -- '192.168.0.0/16'
country String,
city String,
asn UInt32
)
PRIMARY KEY prefix
SOURCE(CLICKHOUSE(TABLE 'geoip_ranges'))
LAYOUT(IP_TRIE())
LIFETIME(MIN 3600 MAX 7200);
Lookup: dictGet('geoip_dict', 'country', toIPv4('192.168.1.100')) — находит наиболее специфичный CIDR prefix, содержащий IP. Единственный layout для GeoIP и ASN определения.
range_hashed — диапазонные ключи
CREATE DICTIONARY exchange_rates (
currency String,
rate Float64,
valid_from Date,
valid_to Date
)
PRIMARY KEY currency
RANGE(MIN valid_from MAX valid_to)
SOURCE(CLICKHOUSE(TABLE 'rates_history'))
LAYOUT(RANGE_HASHED())
LIFETIME(MIN 300 MAX 600);
Lookup: dictGet('exchange_rates', 'rate', 'USD', toDate('2024-06-15')) — находит запись, где valid_from <= 2024-06-15 <= valid_to. Подходит для исторических данных с datе-range validity (курсы валют, тарифы, цены).
direct — без кэширования
LAYOUT(DIRECT())
Каждый вызов dictGet() выполняет запрос к source. Нет кэша, нет загрузки в RAM. Самый медленный, но данные всегда актуальны. Подходит для редких lookup-ов, где свежесть критична.
ssd_cache — SSD-кэш
LAYOUT(SSD_CACHE(
BLOCK_SIZE 4096
FILE_SIZE 16106127360
PATH '/var/lib/clickhouse/ssd_cache/'
))
Двухуровневый кэш: hot keys в RAM (index), cold keys на SSD. Для очень больших словарей (сотни миллионов записей), не помещающихся в RAM.
Рекомендации по выбору layout-а
Максимальная производительность запросов:
flat— если ключи bounded UInt64 (0..N) и словарь до 500K записейhashed— если ключи UInt64 любого диапазонаcomplex_key_hashed— если составной ключ
Экономия памяти:
sparse_hashed— для больших словарей с числовыми ключамиcache/ssd_cache— для огромных словарей с hot key pattern
Специализированные:
ip_trie— для IP-геолокации и CIDR matchingrange_hashed— для date-range lookups (курсы, тарифы)direct— для always-fresh данных при редких запросах
Начинайте с hashed (или complex_key_hashed для составных ключей). Переходите на flat, если ключи bounded и словарь маленький. Переходите на cache/sparse_hashed, если словарь не помещается в RAM.
Ключевые выводы
- CREATE DICTIONARY определяет 4 секции: PRIMARY KEY (ключ lookup-а), SOURCE (откуда данные), LAYOUT (структура в памяти), LIFETIME (интервал обновления).
- LIFETIME(MIN, MAX) рандомизирует обновление между репликами. LIFETIME(0) — загрузка один раз без обновлений.
- flat — массив, самый быстрый, только для bounded UInt64 до 500K. hashed — hash-таблица, универсальный для числовых ключей. complex_key_hashed — для составных ключей.
- cache — LRU для огромных словарей с hot key pattern. ip_trie — для IP/CIDR. range_hashed — для date-range.
- Рекомендация по умолчанию: flat (маленькие), hashed (средние), complex_key_hashed (составной ключ) — лучшая производительность запросов.