diff --git a/doc/reference_en.html b/doc/reference_en.html
index 2fae81421f2..e92488b2b7f 100644
--- a/doc/reference_en.html
+++ b/doc/reference_en.html
@@ -6649,9 +6649,9 @@ The dictionary config file has the following format:
</dictionaries>
%%
-The dictionary identifier (key attribute) must be a number that fits into UInt64. Compound and string keys are not supported. However, if your dictionary has a complex key, you can hash it and use the hash as the key. You can use View for this purpose (in both ClickHouse and MySQL).
+The dictionary identifier (key attribute) should be a number that fits into UInt64. Also, you can use arbitrary tuples as keys (see section "Dictionaries with complex keys"). Note: you can use complex keys consisting of just one element. This allows using e.g. Strings as dictionary keys.
-There are three ways to store dictionaries in memory.
+There are six ways to store dictionaries in memory.
1. %%flat%% - As flat arrays.
This is the most effective method. It works if all keys are smaller than 500,000. If a larger key is discovered when creating the dictionary, an exception is thrown and the dictionary is not created. The dictionary is loaded to RAM in its entirety. The dictionary uses the amount of memory proportional to maximum key value. With the limit of 500,000, memory consumption is not likely to be high. All types of sources are supported. When updating, data (from a file or from a table) is read in its entirety.
@@ -6662,6 +6662,14 @@ All types of sources are supported. When updating, data (from a file or from a t
3. %%cache%% - This is the least effective method. It is appropriate if the dictionary doesn't fit in RAM. It is a cache of a fixed number of cells, where frequently-used data can be located. MySQL, ClickHouse, executable, http sources are supported, but file sources are not supported. When searching a dictionary, the cache is searched first. For each data block, all keys not found in the cache (or expired keys) are collected in a package, which is sent to the source with the query %%SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...)%%. The received data is then written to the cache.
+4. %%range_hashed%%
+
+5. %%complex_key_hashed%% - The same as %%hashed%%, but for complex keys.
+
+6. %%complex_key_cache%% - The same as %%cache%%, but for complex keys.
+
+===Notes===
+
We recommend using the flat method when possible, or hashed. The speed of the dictionaries is impeccable with this type of memory storage.
Use the cache method only in cases when it is unavoidable. The speed of the cache depends strongly on correct settings and the usage scenario. A cache type dictionary only works normally for high enough hit rates (recommended 99% and higher). You can view the average hit rate in the system.dictionaries table. Set a large enough cache size. You will need to experiment to find the right number of cells - select a value, use a query to get the cache completely full, look at the memory consumption (this information is in the system.dictionaries table), then proportionally increase the number of cells so that a reasonable amount of memory is consumed. We recommend MySQL as the source for the cache, because ClickHouse doesn't handle requests with random reads very well.
@@ -6685,6 +6693,29 @@ To use external dictionaries, see the section "Functions for working with e
Note that you can convert values for a small dictionary by specifying all the contents of the dictionary directly in a SELECT query (see the section "transform function"). This functionality is not related to external dictionaries.
+===Dictionaries with complex keys===
+
+You can use tuples consisting of fields of arbitrary types as keys. Configure your dictionary with %%complex_key_hashed%% or %%complex_key_cache%% layout in this case.
+
+Key structure is configured not in the %%<id>%% element but in the %%<key>%% element. Fields of the key tuple are configured analogously to dictionary attributes. Example:
+
+%%
+<structure>
+ <key>
+ <attribute>
+ <name>field1</name>
+ <type>String</type>
+ </attribute>
+ <attribute>
+ <name>field2</name>
+ <type>UInt32</type>
+ </attribute>
+ ...
+ </key>
+ ...
+%%
+
+When using such dictionary, use a Tuple of field values as a key in dictGet* functions. Example: %%dictGetString('dict_name', 'attr_name', tuple('field1_value', 123))%%.
diff --git a/doc/reference_ru.html b/doc/reference_ru.html
index 8b18859ae6c..175ae1e8b51 100644
--- a/doc/reference_ru.html
+++ b/doc/reference_ru.html
@@ -6778,8 +6778,7 @@ regions_names_*.txt: TabSeparated (без заголовка), столбцы:
</dictionaries>
%%
-Идентификатор (ключевой атрибут) словаря должен быть числом, помещающимся в UInt64.
-Составные и строковые ключи не поддерживаются. Впрочем, если в вашем словаре сложный ключ, вы можете захэшировать его и использовать хэш в качестве ключа. Для этого можно использовать View (как в ClickHouse, так и в MySQL).
+Идентификатор (ключевой атрибут) словаря должен быть числом, помещающимся в UInt64. Также есть возможность задавать произвольные составные ключи (см. раздел "Словари с составными ключами"). Замечание: составной ключ может состоять и из одного элемента, что даёт возможность использовать в качестве ключа, например, строку.
Существует шесть способов размещения словаря в памяти.
@@ -6888,8 +6887,12 @@ dictGetT('dict_name', 'attr_name', id, date)
===5. complex_key_hashed===
+Для использования с составными ключами. Аналогичен hashed.
+
===6. complex_key_cache===
+Для использования с составными ключами. Аналогичен cache.
+
===Примечания===
Рекомендуется использовать способ flat, если возможно, или hashed, complex_key_hashed. Скорость работы словарей с таким размещением в памяти является безупречной.
@@ -6915,6 +6918,29 @@ dictGetT('dict_name', 'attr_name', id, date)
Обратите внимание, что вы можете преобразовать значения по небольшому словарю, указав всё содержимое словаря прямо в запросе SELECT - смотрите раздел "Функция transform". Эта функциональность никак не связана с внешними словарями.
+===Словари с составными ключами===
+
+В качестве ключа может выступать кортеж (tuple) из полей произвольных типов. Параметр layout в этом случае должен быть равен %%complex_key_hashed%% или %%complex_key_cache%%.
+
+Структура ключа задаётся не в элементе %%<id>%%, а в элементе %%<key>%%. Поля ключа задаются в том же формате, что и атрибуты словаря. Пример:
+
+%%
+<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))%%.
diff --git a/docs/ru/dicts/external_dicts.rst b/docs/ru/dicts/external_dicts.rst
index 97444b2ff5b..70df8850eec 100644
--- a/docs/ru/dicts/external_dicts.rst
+++ b/docs/ru/dicts/external_dicts.rst
@@ -144,7 +144,8 @@
Идентификатор (ключевой атрибут) словаря должен быть числом, помещающимся в UInt64.
-Составные и строковые ключи не поддерживаются. Впрочем, если в вашем словаре сложный ключ, вы можете захэшировать его и использовать хэш в качестве ключа. Для этого можно использовать View (как в ClickHouse, так и в MySQL).
+Также есть возможность задавать произвольные составные ключи (см. раздел "Словари с составными ключами"). Замечание: составной ключ может состоять и из одного элемента, что даёт возможность использовать в качестве ключа, например, строку.
+
Существует шесть способов размещения словаря в памяти.
@@ -256,10 +257,15 @@ range_hashed
complex_key_hashed
----------------
+Для использования с составными ключами. Аналогичен hashed.
+
complex_key_cache
----------
+Для использования с составными ключами. Аналогичен cache.
+
Примечания
+----------
Рекомендуется использовать способ ``flat``, если возможно, или ``hashed``, ``complex_key_hashed``. Скорость работы словарей с таким размещением в памяти является безупречной.
@@ -283,3 +289,29 @@ complex_key_cache
Для использования внешних словарей, смотрите раздел "Функции для работы с внешними словарями".
Обратите внимание, что вы можете преобразовать значения по небольшому словарю, указав всё содержимое словаря прямо в запросе SELECT - смотрите раздел "Функция transform". Эта функциональность никак не связана с внешними словарями.
+
+Словари с составными ключами
+----------------------------
+
+В качестве ключа может выступать кортеж (tuple) из полей произвольных типов. Параметр layout в этом случае должен быть равен complex_key_hashed или complex_key_cache.
+
+Структура ключа задаётся не в элементе ``
``, а в элементе ````. Поля ключа задаются в том же формате, что и атрибуты словаря. Пример:
+
+.. code-block:: xml
+
+
+
+
+ field1
+ String
+
+
+ field2
+ UInt32
+
+ ...
+
+ ...
+
+
+При использовании такого словаря, в функции dictGet* в качестве ключа передаётся Tuple со значениями полей. Пример: ``dictGetString('dict_name', 'attr_name', tuple('field1', 123))``.