# Хранение словарей в памяти {#dicts-external_dicts_dict_layout} Словари можно размещать в памяти множеством способов. Рекомендуем [flat](#flat), [hashed](#hashed) и [complex_key_hashed](#complex-key-hashed). Скорость обработки словарей при этом максимальна. Размещение с кэшированием не рекомендуется использовать из-за потенциально низкой производительности и сложностей в подборе оптимальных параметров. Читайте об этом подробнее в разделе "[cache](#cache)". Повысить производительность словарей можно следующими способами: - Вызывать функцию для работы со словарём после `GROUP BY`. - Помечать извлекаемые атрибуты как инъективные. Атрибут называется инъективным, если разным ключам соответствуют разные значения атрибута. Тогда при использовании в `GROUP BY` функции, достающей значение атрибута по ключу, эта функция автоматически выносится из `GROUP BY`. При ошибках работы со словарями ClickHouse генерирует исключения. Например, в следующих ситуациях: - При обращении к словарю, который не удалось загрузить. - При ошибке запроса к `cached`-словарю. Список внешних словарей и их статус можно посмотреть в таблице `system.dictionaries`. Общий вид конфигурации: ```xml ... ... ``` Соответствущий [DDL-запрос](../create.md#create-dictionary-query): ```sql CREATE DICTIONARY (...) ... LAYOUT(LAYOUT_TYPE(param value)) -- layout settings ... ``` ## Способы размещения словарей в памяти - [flat](#flat) - [hashed](#hashed) - [sparse_hashed](#dicts-external_dicts_dict_layout-sparse_hashed) - [cache](#cache) - [range_hashed](#range-hashed) - [complex_key_hashed](#complex-key-hashed) - [complex_key_cache](#complex-key-cache) - [ip_trie](#ip-trie) ### flat Словарь полностью хранится в оперативной памяти в виде плоских массивов. Объём памяти, занимаемой словарём пропорционален размеру самого большого по размеру ключа. Ключ словаря имеет тип `UInt64` и его величина ограничена 500 000. Если при создании словаря обнаружен ключ больше, то ClickHouse бросает исключение и не создает словарь. Поддерживаются все виды источников. При обновлении, данные (из файла, из таблицы) читаются целиком. Это метод обеспечивает максимальную производительность среди всех доступных способов размещения словаря. Пример конфигурации: ```xml ``` или ```sql LAYOUT(FLAT()) ``` ### hashed Словарь полностью хранится в оперативной памяти в виде хэш-таблиц. Словарь может содержать произвольное количество элементов с произвольными идентификаторами. На практике, количество ключей может достигать десятков миллионов элементов. Поддерживаются все виды источников. При обновлении, данные (из файла, из таблицы) читаются целиком. Пример конфигурации: ```xml ``` или ```sql LAYOUT(HASHED()) ``` ### sparse_hashed {#dicts-external_dicts_dict_layout-sparse_hashed} Аналогичен `hashed`, но при этом занимает меньше места в памяти и генерирует более высокую загрузку CPU. Пример конфигурации: ```xml ``` или ```sql LAYOUT(SPARSE_HASHED()) ``` ### complex_key_hashed Тип размещения предназначен для использования с составными [ключами](external_dicts_dict_structure.md). Аналогичен `hashed`. Пример конфигурации: ```xml ``` или ```sql LAYOUT(COMPLEX_KEY_HASHED()) ``` ### range_hashed Словарь хранится в оперативной памяти в виде хэш-таблицы с упорядоченным массивом диапазонов и соответствующих им значений. Этот способ размещения работает также как и hashed и позволяет дополнительно к ключу использовать дипазоны по дате/времени (произвольному числовому типу). Пример: таблица содержит скидки для каждого рекламодателя в виде: ```text +---------------+---------------------+-------------------+--------+ | advertiser id | discount start date | discount end date | amount | +===============+=====================+===================+========+ | 123 | 2015-01-01 | 2015-01-15 | 0.15 | +---------------+---------------------+-------------------+--------+ | 123 | 2015-01-16 | 2015-01-31 | 0.25 | +---------------+---------------------+-------------------+--------+ | 456 | 2015-01-01 | 2015-01-15 | 0.05 | +---------------+---------------------+-------------------+--------+ ``` Чтобы использовать выборку по диапазонам дат, необходимо в [structure](external_dicts_dict_structure.md) определить элементы `range_min`, `range_max`. В этих элементах должны присутствовать элементы `name` и `type` (если `type` не указан, будет использован тип по умолчанию -- Date). `type` может быть любым численным типом (Date/DateTime/UInt64/Int32/др.). Пример: ```xml Id first Date last Date ... ``` или ```sql CREATE DICTIONARY somedict ( id UInt64, first Date, last Date ) PRIMARY KEY id LAYOUT(RANGE_HASHED()) RANGE(MIN first MAX last) ``` Для работы с такими словарями в функцию `dictGetT` необходимо передавать дополнительный аргумент, для которого подбирается диапазон: dictGetT('dict_name', 'attr_name', id, date) Функция возвращает значение для заданных `id` и диапазона дат, в который входит переданная дата. Особенности алгоритма: - Если не найден `id` или для найденного `id` не найден диапазон, то возвращается значение по умолчанию для словаря. - Если есть перекрывающиеся диапазоны, то можно использовать любой подходящий. - Если граница диапазона `NULL` или некорректная дата (1900-01-01, 2039-01-01), то диапазон считается открытым. Диапазон может быть открытым с обеих сторон. Пример конфигурации: ```xml ... Abcdef StartTimeStamp UInt64 EndTimeStamp UInt64 XXXType String ``` или ```sql CREATE DICTIONARY somedict( Abcdef UInt64, StartTimeStamp UInt64, EndTimeStamp UInt64, XXXType String DEFAULT '' ) PRIMARY KEY Abcdef RANGE(MIN StartTimeStamp MAX EndTimeStamp) ``` ### cache Словарь хранится в кэше, состоящем из фиксированного количества ячеек. Ячейки содержат часто используемые элементы. При поиске в словаре сначала просматривается кэш. На каждый блок данных, все не найденные в кэше или устаревшие ключи запрашиваются у источника с помощью `SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...)`. Затем, полученные данные записываются в кэш. Для cache-словарей может быть задано время устаревания [lifetime](external_dicts_dict_lifetime.md) данных в кэше. Если от загрузки данных в ячейке прошло больше времени, чем `lifetime`, то значение не используется, и будет запрошено заново при следующей необходимости его использовать. Это наименее эффективный из всех способов размещения словарей. Скорость работы кэша очень сильно зависит от правильности настройки и сценария использования. Словарь типа cache показывает высокую производительность лишь при достаточно больших hit rate-ах (рекомендуется 99% и выше). Посмотреть средний hit rate можно в таблице `system.dictionaries`. Чтобы увеличить производительность кэша, используйте подзапрос с `LIMIT`, а снаружи вызывайте функцию со словарём. Поддерживаются [источники](external_dicts_dict_sources.md): MySQL, ClickHouse, executable, HTTP. Пример настройки: ```xml 1000000000 ``` или ```sql LAYOUT(CACHE(SIZE_IN_CELLS 1000000000)) ``` Укажите достаточно большой размер кэша. Количество ячеек следует подобрать экспериментальным путём: 1. Выставить некоторое значение. 2. Запросами добиться полной заполненности кэша. 3. Оценить потребление оперативной памяти с помощью таблицы `system.dictionaries`. 4. Увеличивать/уменьшать количество ячеек до получения требуемого расхода оперативной памяти. !!! warning Не используйте в качестве источника ClickHouse, поскольку он медленно обрабатывает запросы со случайным чтением. ### complex_key_cache Тип размещения предназначен для использования с составными [ключами](external_dicts_dict_structure.md). Аналогичен `cache`. ### ip_trie Тип размещения предназначен для сопоставления префиксов сети (IP адресов) с метаданными, такими как ASN. Пример: таблица содержит префиксы сети и соответствующие им номера AS и коды стран: ```text +-----------------+-------+--------+ | prefix | asn | cca2 | +=================+=======+========+ | 202.79.32.0/20 | 17501 | NP | +-----------------+-------+--------+ | 2620:0:870::/48 | 3856 | US | +-----------------+-------+--------+ | 2a02:6b8:1::/48 | 13238 | RU | +-----------------+-------+--------+ | 2001:db8::/32 | 65536 | ZZ | +-----------------+-------+--------+ ``` При использовании такого макета структура должна иметь составной ключ. Пример: ```xml prefix String asn UInt32 cca2 String ?? ... ``` или ```sql CREATE DICTIONARY somedict ( prefix String, asn UInt32, cca2 String DEFAULT '??' ) PRIMARY KEY prefix ``` Этот ключ должен иметь только один атрибут типа `String`, содержащий допустимый префикс IP. Другие типы еще не поддерживаются. Для запросов необходимо использовать те же функции (`dictGetT` с кортежем), что и для словарей с составными ключами: ```sql dictGetT('dict_name', 'attr_name', tuple(ip)) ``` Функция принимает либо `UInt32` для IPv4, либо `FixedString(16)` для IPv6: ```sql dictGetString('prefix', 'asn', tuple(IPv6StringToNum('2001:db8::1'))) ``` Никакие другие типы не поддерживаются. Функция возвращает атрибут для префикса, соответствующего данному IP-адресу. Если есть перекрывающиеся префиксы, возвращается наиболее специфический. Данные хранятся в побитовом дереве (`trie`), он должен полностью помещаться в оперативной памяти. [Оригинальная статья](https://clickhouse.tech/docs/ru/query_language/dicts/external_dicts_dict_layout/)