Кэширование в ClickHouse

ClickHouse обучение примеры курсы, аналитика данных с ClickHouse, использование ClickHouse, интеграция ClickHouse, Школа Больших Данных Учебный Центр Коммерсант

Чем кэширование в OLAP-системах отличается от OLTP и как устроен кэш запросов ClickHouse: принципы работы, конфигурационные настройки и примеры использования SELECT-оператора.

Особенности кэширования в ClickHouse

Кэширование является одним из методов повышения производительности, который сокращает время на получение результатов вычислений за счет их хранения в области быстрого доступа. Обычно кэшируются результаты вычислений, которые были выполнены недавно и/или запрашиваются часто. ClickHouse поддерживает кэширование запросов, используя разные типы кэшей, в зависимости от табличных движков. Например, mark_cache – это кэш меток, используемых табличными движками семейства MergeTree, а uncompressed_cache — кэш несжатых данных этих же движков. Есть еще DNS-кеш, кэш регулярных выражений, скомпилированных выражений, схем формата Avro, словарей, выходной схемы данных, файловой системы на S3, Azure, локальных и других дисках. Также есть страничный кэш операционной системы, который используется косвенно, для файлов с актуальными данными.

Особого внимания заслуживает кэш запросов, который позволяет выполнять оператор SELECT только один раз и обслуживать дальнейшие выполнения того же запроса непосредственно из кэша. В зависимости от типа запросов это может значительно снизить задержку и потребление ресурсов сервера ClickHouse. Этот вид кэширования был добавлен в ClickHouse относительно недавно, с версии 23.1 в качестве экспериментальной функции.

Вообще кэши запросов часто считаются транзакционно согласованными, когда база данных делает невалидными кэшированные результаты SELECT-запроса, если они изменились или или могут измениться. В ClickHouse операциями мутации, т.е. изменяющие данные, являются вставка, обновление и удаление данных в таблицах или свертка слияний. Обычно транзакционно-согласованное кэширование подходит для OLTP-хранилищ, таких как, MySQL, Postgresql и Oracle, которые предъявляют строгие требования к согласованности.

В транзакционно-несогласованных кэшах допускаются небольшие неточности в результатах запроса, если всем записям кэша назначен TTL-период – время, после которого они истекают. Это означает, что базовые меняются незначительно в течение этого TTL-периода. В целом этот подход подходит для аналитических баз данных с OLAP-нагрузкой. К примеру, получасовой отчет о продажах интернет-магазина, где данные о продажах не очень сильно меняются в течение получаса. Поэтому базе данных требуется вычислить отчет только один раз с помощью первого SELECT-запроса. Дальнейшие запросы могут обслуживаться непосредственно из кэша запросов. В этом примере разумный TTL-период кэширования может составлять 30 минут.

Транзакционно-несогласованное кэширование традиционно обеспечивается клиентскими инструментами или прокси-пакетами, взаимодействующими с базой данных, которые дублируют логику и конфигурацию кэширования. ClickHouse как OLAP-СУБД использует кэш запросов, который по своей конструкции несовместим с транзакциями. Результаты выполнения запроса могут быть не очень точными, поскольку записи кэша имеют TTL-период, в рамках которого исходные данные меняются незначительно. Вставки, обновления, удаления и внутренние служебные операции не делают записи кэша недействительными. Такая конструкция позволяет избежать проблем с масштабируемостью, которые мешают кэшу запросов MySQL в сценариях с высокой пропускной способностью. По умолчанию TTL-период кэша запросов в ClickHouse составляет 60 секунд, но другое значение можно указать на уровне сеанса, профиля или запроса с помощью параметра query_cache_ttl.

Еще одно отличие кэша запросов ClickHouse от MySQL заключается в регистронезависимости, т.е. SELECT * и select * обрабатывается как один и тот же запрос. Это обеспечивается благодаря тому, что кэш запросов ClickHouse ссылается на результаты запроса, используя абстрактное синтаксическое дерево (AST) SELECT-запроса вместо самого текста запроса. Чтобы сделать это сопоставление более естественным, все настройки уровня запроса, связанные с кэшем запросов, удаляются из AST-дерева. При использовании кэша запросов ClickHouse логика кэширования перемещается на сторону сервера. Это снижает затраты на техническое обслуживание и позволяет избежать дублирования кода. Как это работает, рассмотрим далее.

Кэш запросов: настройки и использование

В настоящее время кэш ClickHouse хранит свои записи в простой хеш-таблице, содержащей не более 1024 элементов по умолчанию. Можно настроить ее точную емкость. При вставке новой записи в заполненный кэш, все устаревшие записи удаляются. Если места по-прежнему недостаточно, новая запись не будет вставлена в кэш.

Поскольку данные в ClickHouse обрабатываются блоками, которые представляют собой наборы частей столбцов, управление кэшированием активно использует понятие блока, размер которого задается в параметре max_block_size. Этот параметр указывает рекомендуемое максимальное количество строк, которое можно включить в один блок при загрузке данных из таблиц. Размер блока не должен быть слишком маленьким, чтобы избежать заметных затрат при обработке каждого блока. Он также не должен быть слишком большим, чтобы гарантировать быстрое выполнение запросов с оператором LIMIT после обработки первого блока. Оптимальное значение max_block_size позволяет избежать потребления слишком большого количества памяти при извлечении большого количества столбцов в нескольких потоках и сохранить хотя бы некоторую локальность кэша.

По умолчанию записи в кэше запросов сжимаются, чтобы снизить общее потребление памяти за счет более медленной записи/чтения из кэша запросов. Отключить сжатие можно с помощью конфигурации query_cache_compress_entries. Настройка query_cache_squash_partial_results, включенная по умолчанию, определяет, сжимаются ли блоки результатов (если они маленькие) или разбиваются (если они большие) на блоки размера max_block_size перед вставкой в ​​кэш результатов запроса. Это снижает производительность записи в кэш запросов, но повышает степень сжатия записей кэша и обеспечивает более естественную детализацию блоков, когда результаты запроса позже обслуживаются из кэша запросов. В результате кэш запросов сохраняет для каждого запроса несколько блоков результатов.

Для табличного движка MergeTree в ClickHouse используется кэш несжатых блоков, где хранятся результаты запросов,  чтобы ускорить получение ответов на повторяющиеся небольшие запросы. Использование кэша несжатых блоков определяет параметр сервера uncompressed_cache_size, который может быть установлен в 1 или 0. А размер кэша несжатых блоков задается только в конфигурации uncompressed_cache_size и по умолчанию равен 8 ГБ. Несжатый кэш заполняется по мере необходимости, а данные, которые используемые меньше всего автоматически удаляются из него. Для запросов, которые считывают относительно большой объем данных (миллион строк или более), несжатый кэш автоматически отключается, чтобы сэкономить место для действительно небольших запросов. Поэтому можно задать для параметра use_uncompressed_cache значение 1.

Размер кэша запросов в байтах, максимальное количество записей и максимальный размер отдельных записей кэша можно настроить с помощью параметров конфигурации сервера. В частности, для защиты кэша от повреждения запросами, считывающими большой объем данных, используются следующие конфигурации:

  • merge_tree_max_rows_to_use_cache – максимально допустимое количество строк за один запрос, которое можно прочитать из кэша;
  • merge_tree_max_bytes_to_use_cache – максимально допустимое количество байтов за один запрос, которое можно прочитать из кэша.

Чтобы использовать кэширование в ClickHouse, его необходимо сперва включить и настроить. При работе с версией СУБД, где кэширование запросов все еще является экспериментальной функцией, его надо явно разрешить, используя команду

SET allow_experimental_query_cache = true

Для более поздних версий ClickHouse кэш запросов включается установкой параметра use_query_cache в значение true. Параметры Enable_reads_from_query_cache и Enable_writes_to_query_cache управляют использованием кэша более детально, разрешая чтение и запись. Параметр enable_reads_from_query_cache определяет, сохраняются ли результаты запроса в кэше, тогда enable_writes_to_query_cache определяет, должна ли база данных пытаться получить результаты запроса из кэша. Для максимального контроля рекомендуется использовать настройки use_query_cache, enable_writes_to_query_cache и enable_reads_from_query_cache только с конкретными запросами. Также в ClickHouse можно включить кеширование на уровне пользователя или профиля, но тогда все SELECT-запросы могут возвращать кэшированные результаты. Очистить кэш запросов можно очистить с помощью команды

SYSTEM DROP QUERY CACHE

Содержимое кэша запросов отображается в системной таблице system.query_cache. Количество попаданий и промахов кэша запросов с момента запуска базы данных отображается как события QueryCacheHits и QueryCacheMisses в системной таблице system.events. Оба этих счетчика обновляются только для SELECT-запросов, которые выполняются с параметром use_query_cache = true, другие запросы не влияют на значение QueryCacheMisses. Поле query_cache_usage в системной таблице system.query_log показывает для каждого выполненного запроса, был ли результат запроса записан в кэш запросов или прочитан из него. Если запрос был прерван из-за исключения или отмены пользователем, в кэш запроса не записывается никакая запись.

Асинхронные метрики QueryCacheEntries и QueryCacheBytes в системной таблице system.asynchronous_metrics показывают, сколько записей/байт в данный момент содержит кэш запросов. Кэш запросов существует один раз для каждого процесса сервера ClickHouse. Однако результаты кэша по умолчанию не передаются пользователям. Это обусловлено соображениями безопасности. Например, пользователь А не должен иметь возможности обойти политику строк в таблице, выполнив тот же запрос, что и другой пользователь Б, для которого такой политики не существует. Однако при необходимости записи кэша можно сделать общими, т.е. доступными для других пользователей, настроив параметр query_cache_share_between_users.

Также можно ограничить использование кэша отдельными пользователями с помощью их профилей и ограничений настроек. Например, можно ограничить максимальный объем памяти в байтах, который пользователь может выделить в кэше запросов, и максимальное количество сохраняемых результатов запроса. Для этого надо настроить конфигурации query_cache_max_size_in_bytes и query_cache_max_entries в профиле пользователя в файле users.xml, затем сделать обе настройки доступными только для чтения.

Чтобы разрешить использование кэша запросов для конкретного запроса или для всех запросов текущего сеанса, необходимо установить параметр use_query_cache в значение true. Например, первое выполнение следующего запроса сохранит его результат в кэше запросов:

SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true;

Последующие выполнения того же запроса также с параметром use_query_cache = true будут считывать вычисленный результат из кэша и немедленно возвращать его. Настройка use_query_cache и все другие параметры, связанные с кэшем запросов, влияют только на автономные SELECT-операторы. А SELECT-запросы к представлениям, созданные с помощью конструкции CREATE VIEW AS SELECT […] SETTINGS use_query_cache = true не кэшируются, если оператор SELECT не выполняется с настройкой use_query_cache = true.

Чтобы определить, как долго должен выполняться запрос, чтобы его результат можно было кэшировать, используется настройка query_cache_min_query_duration. Например, результат запроса кэшируется только в том случае, если запрос выполняется дольше 5 секунд:

SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true, query_cache_min_query_duration = 5000;

С помощью настройки query_cache_min_query_runs можно указать, как часто должен выполняться запрос, пока его результат не будет кэширован.

Результаты запросов с недетерминированными функциями, такими как rand() и, now() по умолчанию не кэшируются. Это можно изменить с помощью настройки query_cache_store_results_of_queries_with_nondeterministic_functions, хотя это противоречит самой идее недетерминированных функций. Помимо методов, возвращающих случайные значения или текущие отметки времени, к таким недетерминированным функциям также относятся те, результат которых зависит от размера и порядка или внутренних фрагментов, используемых для обработки запроса (nowInBlock(),rowNumberInBlock(), runningDifference(), blockSize() и пр.), и которые зависят от окружения (currentUser(), queryID(), getMacro()).

Также по умолчанию в ClickHouse не кэшируются результаты запросов, включающих системные таблицы, например system.processes или information_schema.tables. Чтобы принудительно кэшировать результаты запросов к системным таблицам, надо настроить параметр query_cache_system_table_handling.

Освойте администрирование и эксплуатацию ClickHouse для аналитики больших данных на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:

Источники

  1. https://clickhouse.com/docs/en/operations/caches
  2. https://clickhouse.com/docs/en/operations/query-cache
  3. https://clickhouse.com/blog/introduction-to-the-clickhouse-query-cache-and-design

Я даю свое согласие на обработку персональных данных и соглашаюсь с политикой конфиденциальности.
Поиск по сайту