Словарь в ClickHouse — это хранящееся в памяти представление данных в формате ключ-значение, получаемых из различных внутренних и внешних источников и оптимизированное для запросов поиска со сверхнизкой задержкой.
Словари полезны для:
- Повышения производительности запросов, особенно при использовании
JOIN
- Обогащения принимаемых данных на лету без замедления процесса ингестии
Ускорение JOIN с помощью словаря
Словари можно использовать для ускорения определённого типа JOIN: LEFT ANY, где ключ JOIN должен совпадать с ключевым атрибутом базового хранилища ключ-значение.
В этом случае ClickHouse может использовать словарь для выполнения Direct JOIN. Это самый быстрый алгоритм JOIN в ClickHouse; он применим, когда базовый движок таблицы правой таблицы поддерживает низколатентные запросы ключ-значение. В ClickHouse есть три движка таблиц, которые это обеспечивают: Join (по сути, это заранее вычисленная хеш-таблица), EmbeddedRocksDB и Dictionary. Мы опишем подход на основе словаря, но механизм одинаков для всех трёх движков.
Алгоритм Direct JOIN требует, чтобы правая таблица была основана на словаре, так что данные из этой таблицы, которые нужно объединить, уже находились в памяти в виде низколатентной структуры данных ключ-значение.
Используя набор данных Stack Overflow, ответим на вопрос:
Какой пост о SQL на Hacker News является самым спорным?
Будем считать пост спорным, если число голосов за и против у него примерно одинаково. Мы вычислим эту абсолютную разницу: чем ближе значение к 0, тем более спорным считается пост. Предположим, что у поста должно быть как минимум 10 голосов за и 10 против — посты, за которые почти не голосуют, вряд ли можно считать по-настоящему спорными.
Поскольку наши данные нормализованы, для этого запроса сейчас требуется JOIN с использованием таблиц posts и votes:
Используйте меньшие наборы данных в правой части JOIN: Этот запрос может показаться излишне многословным, поскольку фильтрация по PostId выполняется и во внешнем запросе, и в подзапросе. Это оптимизация производительности, которая позволяет сократить время отклика запроса. Для максимальной производительности всегда следите за тем, чтобы в правой части JOIN находилось меньшее и по возможности минимальное множество данных. Советы по оптимизации производительности JOIN и обзор доступных алгоритмов можно найти в этой серии статей блога.
Хотя этот запрос выполняется быстро, для хорошей производительности JOIN здесь нужно написать очень аккуратно. В идеале мы бы просто отфильтровали посты, содержащие “SQL”, а затем посмотрели на количество UpVote и DownVote для этого подмножества постов, чтобы вычислить нашу Метрику.
Чтобы продемонстрировать эти концепции, мы используем словарь для данных о голосовании. Поскольку словари обычно хранятся в памяти (ssd_cache — исключение), следует учитывать объём данных. Проверим размер нашей таблицы votes:
Данные будут храниться в нашем словаре без сжатия, поэтому, если бы мы хранили в словаре все столбцы (а мы этого делать не будем), потребовалось бы как минимум 4 ГБ памяти. Словарь будет реплицирован по всему кластеру, поэтому такой объём памяти нужно зарезервировать на каждом узле.
В примере ниже данные для нашего словаря берутся из таблицы ClickHouse. Хотя это самый распространённый источник для словарей, поддерживается целый ряд источников, включая файлы, http и базы данных, в том числе Postgres. Как мы покажем, словари можно автоматически обновлять, что делает их идеальным решением для небольших наборов данных, которые часто меняются и должны быть доступны для прямых JOIN.
Нашему словарю нужен первичный ключ, по которому будут выполняться lookup-операции. По сути, он полностью аналогичен первичному ключу в транзакционной базе данных и должен быть уникальным. В приведённом выше запросе lookup выполняется по ключу JOIN — PostId. Соответственно, словарь должен быть заполнен суммарным числом положительных и отрицательных голосов для каждого PostId из нашей таблицы votes. Вот запрос для получения данных для этого словаря:
Для создания нашего словаря нужен следующий DDL — обратите внимание, что в нём используется приведённый выше запрос:
В самоуправляемом OSS приведённую выше команду нужно выполнить на всех узлах. В ClickHouse Cloud словарь автоматически реплицируется на все узлы. Описанные выше действия были выполнены на узле ClickHouse Cloud с 64 ГБ оперативной памяти; загрузка заняла 36 с.
Чтобы проверить, сколько памяти потребляет наш словарь:
Теперь получить голоса «за» и «против» для конкретного PostId можно с помощью простой функции dictGet. Ниже мы извлекаем значения для поста 11227902:
Применив это к нашему предыдущему запросу, мы можем убрать JOIN:
Этот запрос не только намного проще, но и более чем в два раза быстрее! Это можно дополнительно оптимизировать, загружая в словарь только посты, набравшие более 10 голосов за и против, и сохраняя только заранее вычисленное значение controversial.
Обогащение данных во время выполнения запроса
Словари можно использовать для поиска значений во время выполнения запроса. Эти значения можно возвращать в результатах или использовать в агрегированиях. Предположим, мы создаём словарь для сопоставления идентификаторов пользователей с их местоположением:
С помощью этого словаря можно дополнить результаты постов:
Как и в примере с JOIN выше, мы можем использовать тот же словарь, чтобы эффективно определить, откуда поступает большинство постов:
Обогащение на этапе индексации
В приведённом выше примере мы использовали словарь во время выполнения запроса, чтобы избавиться от JOIN. Словари также можно использовать для обогащения строк во время вставки. Обычно это уместно, если значение обогащения не меняется и хранится во внешнем источнике, который можно использовать для заполнения словаря. В этом случае обогащение строки во время вставки позволяет избежать обращения к словарю во время выполнения запроса.
Предположим, что Location пользователя в Stack Overflow никогда не меняется (хотя в реальности это не так) — в частности, речь о столбце Location таблицы users. Предположим, мы хотим выполнить аналитический запрос к таблице posts по местоположению. Она содержит UserId.
Словарь предоставляет сопоставление идентификатора пользователя с местоположением на основе таблицы users:
Мы исключаем пользователей с Id < 0, что позволяет использовать словарь типа Hashed. Пользователи с Id < 0 — системные пользователи.
Чтобы задействовать этот словарь при вставке данных в таблицу posts, нужно изменить схему:
В приведённом выше примере Location объявлен как столбец MATERIALIZED. Это означает, что значение можно указать в запросе INSERT, но оно всё равно всегда будет вычисляться.
ClickHouse также поддерживает столбцы DEFAULT (где значение можно вставить или вычислить, если оно не указано).
Чтобы заполнить таблицу, можно использовать обычный INSERT INTO SELECT из S3:
Теперь мы можем получить название места, откуда поступает большинство постов:
Дополнительные темы по словарям
Рекомендации по выбору структур словарей, использованию словарей вместо JOIN и мониторингу использования словарей см. в разделе Лучшие практики работы со словарями.
Мы указали для словаря LIFETIME со значениями MIN 600 MAX 900. LIFETIME — это интервал обновления словаря; указанные здесь значения приводят к периодической перезагрузке через случайный промежуток от 600 до 900 с. Такой случайный интервал нужен, чтобы распределить нагрузку на источник словаря при обновлении на большом количестве серверов. Во время обновления запросы по-прежнему могут выполняться к старой версии словаря; только начальная загрузка блокирует запросы. Обратите внимание, что установка (LIFETIME(0)) отключает обновление словарей.
Словари можно принудительно перезагрузить с помощью команды SYSTEM RELOAD DICTIONARY.
Для источников баз данных, таких как ClickHouse и Postgres, можно настроить запрос, который будет обновлять словари, только если они действительно изменились (это определяется ответом на запрос), а не по периодическому интервалу. Подробнее см. здесь.
ClickHouse также поддерживает словари Hierarchical, Polygon и Regular Expression.
Последнее изменение 1 июля 2026 г.