ClickHouse/docs/ru/dicts/external_dicts.rst
2017-03-18 11:39:34 +04:00

318 lines
25 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Внешние словари
===============
Существует возможность подключать свои собственные словари из различных источников данных.
Источником данных для словаря может быть файл на локальной файловой системе, сервер ClickHouse, сервер MySQL, MongoDB или любой ODBC источник.
Словарь может полностью храниться в оперативке и периодически обновляться, или быть частично закэшированным в оперативке и динамически подгружать отсутствующие значения.
Конфигурация внешних словарей находится в отдельном файле или файлах, указанных в конфигурационном параметре dictionaries_config.
Этот параметр содержит абсолютный или относительный путь к файлу с конфигурацией словарей. Относительный путь - относительно директории с конфигурационным файлом сервера. Путь может содержать wildcard-ы * и ? - тогда рассматриваются все подходящие файлы. Пример: dictionaries/*.xml.
Конфигурация словарей, а также множество файлов с конфигурацией, может обновляться без перезапуска сервера. Сервер проверяет обновления каждые 5 секунд. То есть, словари могут подключаться динамически.
Создание словарей может производиться при старте сервера или при первом использовании. Это определяется конфигурационном параметром dictionaries_lazy_load (в основном конфигурационном файле сервера). Параметр не обязателен, по умолчанию - true. Если true, то каждый словарь создаётся при первом использовании; если словарь не удалось создать - вызов функции, использующей словарь, кидает исключение. Если false, то все словари создаются при старте сервера, и в случае ошибки, сервер завершает работу.
Конфигурационный файл словарей имеет вид:
.. code-block:: xml
<dictionaries>
<comment>Не обязательный элемент с любым содержимым; полностью игнорируется.</comment>
<!-- Можно задать произвольное количество разных словарей. -->
<dictionary>
<!-- Имя словаря. Под этим именем словарь будет доступен для использования. -->
<name>os</name>
<!-- Источник данных. -->
<source>
<!-- Источник - файл на локальной файловой системе. -->
<file>
<!-- Путь на локальной файловой системе. -->
<path>/opt/dictionaries/os.tsv</path>
<!-- С помощью какого формата понимать файл. -->
<format>TabSeparated</format>
</file>
<!-- или источник - таблица на сервере MySQL.
<mysql>
<!- - Эти параметры могут быть указаны как снаружи (общие для всех реплик), так и внутри конкретной реплики - ->
<port>3306</port>
<user>clickhouse</user>
<password>qwerty</password>
<!- - Можно указать от одной до произвольного количества реплик для отказоустойчивости. - ->
<replica>
<host>example01-1</host>
<priority>1</priority> <!- - Меньше значение - больше приоритет. - ->
</replica>
<replica>
<host>example01-2</host>
<priority>1</priority>
</replica>
<db>conv_main</db>
<table>counters</table>
</mysql>
-->
<!-- или источник - таблица на сервере ClickHouse.
<clickhouse>
<host>example01-01-1</host>
<port>9000</port>
<user>default</user>
<password></password>
<db>default</db>
<table>counters</table>
</clickhouse>
<!- - Если адрес похож на localhost, то запрос будет идти без сетевого взаимодействия.
Для отказоустойчивости, вы можете создать Distributed таблицу на localhost и прописать её. - ->
-->
<!-- или источник - исполняемый файл. Если layout.cache - список нужных ключей будет записан в поток STDIN программы -->
<executable>
<!-- Путь или имя программы (если директория есть в переменной окружения PATH) и параметры -->
<command>cat /opt/dictionaries/os.tsv</command>
<!-- С помощью какого формата понимать вывод и формировать список ключей. -->
<format>TabSeparated</format>
</executable>
<!-- или источник - http сервер. Если layout.cache - список нужных ключей будет послан как POST запрос -->
<http>
<url>http://[::1]/os.tsv</url>
<!-- С помощью какого формата понимать ответ и формировать список ключей. -->
<format>TabSeparated</format>
</http>
</source>
<!-- Периодичность обновления для полностью загружаемых словарей. 0 - никогда не обновлять. -->
<lifetime>
<min>300</min>
<max>360</max>
<!-- Периодичность обновления выбирается равномерно-случайно между min и max,
чтобы размазать по времени нагрузку при обновлении словарей на большом количестве серверов. -->
</lifetime>
<!-- или
<!- - Периодичность обновления для полностью загружаемых словарей или время инвалидации для кэшируемых словарей.
0 - никогда не обновлять. - ->
<lifetime>300</lifetime>
-->
<layout> <!-- Способ размещения в памяти. -->
<flat />
<!-- или
<hashed />
или
<cache>
<!- - Размер кэша в количестве ячеек; округляется вверх до степени двух. - ->
<size_in_cells>1000000000</size_in_cells>
</cache>
-->
</layout>
<!-- Структура. -->
<structure>
<!-- Описание столбца, являющегося идентификатором (ключом) словаря. -->
<id>
<!-- Имя столбца с идентификатором. -->
<name>Id</name>
</id>
<attribute> <!-- id уже входит в атрибуты и дополнительно указывать его здесь не нужно. -->
<!-- Имя столбца. -->
<name>Name</name>
<!-- Тип столбца. (Как столбец понимается при загрузке.
В случае MySQL, в таблице может быть TEXT, VARCHAR, BLOB, но загружается всё как String) -->
<type>String</type>
<!-- Какое значение использовать для несуществующего элемента. В примере - пустая строка. -->
<null_value></null_value>
</attribute>
<!-- Может быть указано произвольное количество атрибутов. -->
<attribute>
<name>ParentID</name>
<type>UInt64</type>
<null_value>0</null_value>
<!-- Определяет ли иерархию - отображение в идентификатор родителя (по умолчанию, false). -->
<hierarchical>true</hierarchical>
<!-- Можно считать отображение id -> attribute инъективным, чтобы оптимизировать GROUP BY. (по умолчанию, false) -->
<injective>true</injective>
</attribute>
</structure>
</dictionary>
</dictionaries>
Идентификатор (ключевой атрибут) словаря должен быть числом, помещающимся в UInt64.
Также есть возможность задавать произвольные составные ключи (см. раздел "Словари с составными ключами"). Замечание: составной ключ может состоять и из одного элемента, что даёт возможность использовать в качестве ключа, например, строку.
Существует шесть способов размещения словаря в памяти.
flat
-----
В виде плоских массивов. Самый эффективный способ. Он подходит, если все ключи меньше 500 000. Если при создании словаря обнаружен ключ больше, то кидается исключение и словарь не создаётся. Словарь загружается в оперативку целиком. Словарь использует количество оперативки, пропорциональное максимальному значению ключа. Ввиду ограничения на 500 000, потребление оперативки вряд ли может быть большим.
Поддерживаются все виды источников. При обновлении, данные (из файла, из таблицы) читаются целиком.
hashed
-------
В виде хэш-таблиц. Слегка менее эффективный способ. Словарь тоже загружается в оперативку целиком, и может содержать произвольное количество элементов с произвольными идентификаторами. На практике, имеет смысл использовать до десятков миллионов элементов, пока хватает оперативки.
Поддерживаются все виды источников. При обновлении, данные (из файла, из таблицы) читаются целиком.
cache
-------
Наименее эффективный способ. Подходит, если словарь не помещается в оперативку. Представляет собой кэш из фиксированного количества ячеек, в которых могут быть расположены часто используемые данные. Поддерживается источник MySQL, ClickHouse, executable, http; источник-файл не поддерживается. При поиске в словаре, сначала просматривается кэш. На каждый блок данных, все не найденные в кэше ключи (или устаревшие ключи) собираются в пачку, и с этой пачкой делается запрос к источнику вида SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...). Затем полученные данные записываются в кэш.
range_hashed
--------
В таблице прописаны какие-то данные для диапазонов дат, для каждого ключа. Дать возможность доставать эти данные для заданного ключа, для заданной даты.
Пример: в таблице записаны скидки для каждого рекламодателя в виде:
::
id рекламодателя дата начала действия скидки дата конца величина
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
Добавляем layout = range_hashed.
При использовании такого layout, в structure должны быть элементы range_min, range_max.
Пример:
.. code-block:: xml
<structure>
<id>
<name>Id</name>
</id>
<range_min>
<name>first</name>
</range_min>
<range_max>
<name>last</name>
</range_max>
...
Эти столбцы должны иметь тип Date. Другие типы пока не поддерживаем.
Столбцы обозначают закрытый диапазон дат.
Для работы с такими словарями, функции dictGetT должны принимать ещё один аргумент - дату:
``dictGetT('dict_name', 'attr_name', id, date)``
Функция достаёт значение для данного id и для диапазона дат, в который входит переданная дата. Если не найден id или для найденного id не найден диапазон, то возвращается значение по умолчанию для словаря.
Если есть перекрывающиеся диапазоны, то можно использовать любой подходящий.
Если граница диапазона является NULL или является некорректной датой (1900-01-01, 2039-01-01), то диапазон следует считать открытым. Диапазон может быть открытым с обеих сторон.
В оперативке данные представлены в виде хэш-таблицы со значением в виде упорядоченного массива диапазонов и соответствующих им значений.
Пример словаря по диапазонам:
.. code-block:: xml
<dictionaries>
<dictionary>
<name>xxx</name>
<source>
<mysql>
<password>xxx</password>
<port>3306</port>
<user>xxx</user>
<replica>
<host>xxx</host>
<priority>1</priority>
</replica>
<db>dicts</db>
<table>xxx</table>
</mysql>
</source>
<lifetime>
<min>300</min>
<max>360</max>
</lifetime>
<layout>
<range_hashed />
</layout>
<structure>
<id>
<name>Abcdef</name>
</id>
<range_min>
<name>StartDate</name>
</range_min>
<range_max>
<name>EndDate</name>
</range_max>
<attribute>
<name>XXXType</name>
<type>String</type>
<null_value />
</attribute>
</structure>
</dictionary>
</dictionaries>
complex_key_hashed
----------------
Для использования с составными ключами. Аналогичен hashed.
complex_key_cache
----------
Для использования с составными ключами. Аналогичен cache.
Примечания
----------
Рекомендуется использовать способ ``flat``, если возможно, или ``hashed``, ``complex_key_hashed``. Скорость работы словарей с таким размещением в памяти является безупречной.
Способы ``cache`` и ``complex_key_cache`` следует использовать лишь если это неизбежно. Скорость работы кэша очень сильно зависит от правильности настройки и сценария использования. Словарь типа cache нормально работает лишь при достаточно больших hit rate-ах (рекомендуется 99% и выше). Посмотреть средний hit rate можно в таблице system.dictionaries. Укажите достаточно большой размер кэша. Количество ячеек следует подобрать экспериментальным путём - выставить некоторое значение, с помощью запроса добиться полной заполненности кэша, посмотреть на потребление оперативки (эта информация находится в таблице system.dictionaries); затем пропорционально увеличить количество ячеек так, чтобы расходовалось разумное количество оперативки. В качестве источника для кэша рекомендуется MySQL, MongoDB, так как ClickHouse плохо обрабатывает запросы со случайными чтениями.
Во всех случаях, производительность будет выше, если вызывать функцию для работы со словарём после ``GROUP BY``, или если доставаемый атрибут помечен как инъективный. Для cache словарей, производительность будет лучше, если вызывать функцию после LIMIT-а - для этого можно использовать подзапрос с LIMIT-ом, и снаружи вызывать функцию со словарём.
Атрибут называется инъективным, если разным ключам соответствуют разные значения атрибута. Тогда при использовании в ``GROUP BY`` функции, достающей значение атрибута по ключу, эта функция автоматически выносится из GROUP BY.
При обновлении словарей из файла, сначала проверяется время модификации файла, и загрузка производится только если файл изменился.
При обновлении из MySQL, для flat и hashed словарей, сначала делается запрос ``SHOW TABLE STATUS`` и смотрится время обновления таблицы. И если оно не NULL, то оно сравнивается с запомненным временем. Это работает для MyISAM таблиц, а для InnoDB таблиц время обновления неизвестно, поэтому загрузка из InnoDB делается при каждом обновлении.
Для cache-словарей может быть задано время устаревания (``lifetime``) данных в кэше. Если от загрузки данных в ячейке прошло больше времени, чем lifetime, то значение не используется, и будет запрошено заново при следующей необходимости его использовать.
Если словарь не удалось ни разу загрузить, то при попытке его использования, будет брошено исключение.
Если при запросе к источнику cached словаря возникла ошибка, то будет брошено исключение.
Обновление словарей (кроме загрузки при первом использовании) не блокирует запросы - во время обновления используется старая версия словаря. Если при обновлении возникнет ошибка, то ошибка пишется в лог сервера, а запросы продолжат использовать старую версию словарей.
Список внешних словарей и их статус можно посмотреть в таблице ``system.dictionaries``.
Для использования внешних словарей, смотрите раздел "Функции для работы с внешними словарями".
Обратите внимание, что вы можете преобразовать значения по небольшому словарю, указав всё содержимое словаря прямо в запросе SELECT - смотрите раздел "Функция transform". Эта функциональность никак не связана с внешними словарями.
Словари с составными ключами
----------------------------
В качестве ключа может выступать кортеж (tuple) из полей произвольных типов. Параметр layout в этом случае должен быть равен complex_key_hashed или complex_key_cache.
Структура ключа задаётся не в элементе ``<id>``, а в элементе ``<key>``. Поля ключа задаются в том же формате, что и атрибуты словаря. Пример:
.. code-block:: xml
<structure>
<key>
<attribute>
<name>field1</name>
<type>String</type>
</attribute>
<attribute>
<name>field2</name>
<type>UInt32</type>
</attribute>
...
</key>
...
При использовании такого словаря, в функции dictGet* в качестве ключа передаётся Tuple со значениями полей. Пример: ``dictGetString('dict_name', 'attr_name', tuple('field1', 123))``.