Кэш запросов
Кэш запросов позволяет выполнять запросы SELECT только один раз и обслуживать последующие выполнения того же запроса напрямую из кэша.
В зависимости от типа запросов это может существенно снизить время отклика и потребление ресурсов сервера ClickHouse.
Предпосылки, дизайн и ограничения
Кэши запросов в общем случае можно рассматривать как транзакционно согласованные или транзакционно несогласованные.
- В транзакционно согласованных кэшах база данных аннулирует (отбрасывает) кэшированные результаты запроса, если результат запроса
SELECTизменяется или может измениться. В ClickHouse к операциям, изменяющим данные, относятся вставки/обновления/удаления в/из таблиц или схлопывающие слияния (collapsing merges). Транзакционно согласованное кэширование особенно подходит для OLTP-СУБД, например MySQL (в которой кэш запросов был удалён, начиная с версии v8.0) и Oracle. - В транзакционно несогласованных кэшах допускаются небольшие неточности в результатах запросов при предположении, что всем записям в кэше
назначается срок действия, по истечении которого они становятся недействительными (например, 1 минута), и что исходные данные за этот период меняются незначительно.
В целом такой подход больше подходит для OLAP-СУБД. В качестве примера, когда транзакционно несогласованного кэширования достаточно,
рассмотрим почасовой отчёт по продажам в отчётном инструменте, к которому одновременно обращаются несколько пользователей. Данные о продажах
обычно меняются достаточно медленно, чтобы базе данных нужно было вычислить отчёт только один раз (что соответствует первому запросу
SELECT). Последующие запросы могут обслуживаться напрямую из кэша запросов. В этом примере разумным сроком действия кэша может быть 30 минут.
Транзакционно несогласованное кэширование традиционно реализуется клиентскими инструментами или прокси-пакетами (например, chproxy), взаимодействующими с базой данных. В результате одна и та же логика кэширования и конфигурация часто дублируются. С появлением кэша запросов в ClickHouse логика кэширования переносится на сторону сервера. Это снижает затраты на сопровождение и устраняет дублирование.
Параметры конфигурации и их использование
В ClickHouse Cloud для редактирования настроек кэша запросов необходимо использовать настройки на уровне запроса. Редактирование настроек на уровне конфигурации в данный момент не поддерживается.
clickhouse-local выполняет один запрос за раз. Поскольку кэширование результатов запроса в этом случае не имеет смысла, кэш результатов запросов в clickhouse-local отключен.
Параметр use_query_cache можно использовать для управления тем, будет ли использовать кэш запросов конкретный запрос или все запросы текущего сеанса. Например, при первом выполнении запроса
будет сохранять результат запроса в кэше запросов. Последующие выполнения того же запроса (также с параметром use_query_cache = true)
будут считывать вычисленный результат из кэша и возвращать его немедленно.
Настройка use_query_cache и всех остальных параметров, связанных с кэшем запросов, влияет только на отдельные операторы SELECT. В частности,
результаты SELECT в представления, созданные через CREATE VIEW AS SELECT [...] SETTINGS use_query_cache = true, не кэшируются, если только оператор SELECT
не выполняется с SETTINGS use_query_cache = true.
Способ использования кэша можно подробнее настроить с помощью параметров enable_writes_to_query_cache
и enable_reads_from_query_cache (по умолчанию обе имеют значение true). Первый параметр
определяет, сохраняются ли результаты запросов в кэше, а второй — должен ли сервер пытаться получать результаты запросов
из кэша. Например, следующий запрос будет использовать кэш только пассивно, то есть пытаться читать из него, но не сохранять в него свой
результат:
Для максимального контроля обычно рекомендуется задавать настройки use_query_cache, enable_writes_to_query_cache и
enable_reads_from_query_cache только для конкретных запросов. Также можно включить кэширование на уровне пользователя или профиля (например, через SET use_query_cache = true), но следует иметь в виду, что тогда все запросы 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.metrics показывают, сколько записей и байт в данный момент содержит кэш запросов.
Кэш запросов существует в одном экземпляре для каждого серверного процесса ClickHouse. Однако результаты кэша по умолчанию не являются общими между пользователями. Это можно изменить (см. ниже), но делать так не рекомендуется по соображениям безопасности.
Результаты запросов в кэше запросов идентифицируются по абстрактному синтаксическому дереву (AST)
их запроса. Это означает, что кэширование не зависит от регистра, например SELECT 1 и select 1 рассматриваются как один и тот же запрос. Чтобы
сделать сопоставление более естественным, все настройки на уровне запроса, относящиеся к кэшу запросов и форматированию вывода,
удаляются из AST.
Если запрос был прерван из-за исключения или отмены пользователем, запись в кэш запросов не производится.
Размер кэша запросов в байтах, максимальное количество записей в кэше и максимальный размер отдельных записей кэша (в байтах и в строках) можно настроить с помощью различных параметров конфигурации сервера.
Также можно ограничить использование кэша отдельными пользователями с помощью профилей настроек и ограничений настроек. В частности, вы можете ограничить максимальный объём памяти (в байтах), который пользователь может выделить в кэше запросов, и максимальное количество сохранённых результатов запросов. Для этого сначала задайте значения параметров
query_cache_max_size_in_bytes и
query_cache_max_entries в профиле пользователя в users.xml, затем сделайте обе настройки доступными только для чтения:
Чтобы задать минимальную длительность выполнения запроса, начиная с которой его результат может кэшироваться, вы можете использовать настройку query_cache_min_query_duration. Например, результат запроса
кэшируется только если выполнение запроса длится дольше 5 секунд. Также можно задать, сколько раз запрос должен быть выполнен, прежде чем его результат будет закэширован — для этого используйте настройку query_cache_min_query_runs.
Записи в кэше запросов становятся устаревшими через определенный период времени (time-to-live). По умолчанию этот период составляет 60 секунд, но другое значение можно задать на уровне сессии, профиля или отдельного запроса, используя настройку query_cache_ttl. Кэш запросов удаляет записи «лениво», то есть когда запись становится устаревшей, она не удаляется из кэша немедленно. Вместо этого, когда в кэш запросов нужно вставить новую запись, база данных проверяет, достаточно ли в кэше свободного места для новой записи. Если это не так, база данных пытается удалить все устаревшие записи. Если в кэше по-прежнему недостаточно свободного места, новая запись не добавляется.
Если запрос выполняется через HTTP, то ClickHouse устанавливает заголовки Age и Expires с временем жизни (в секундах) и временной меткой истечения срока действия
закэшированной записи.
Записи в кэше запросов по умолчанию сжимаются. Это уменьшает общее потребление памяти ценой более медленной записи в кэш и чтения из кэша запросов. Чтобы отключить сжатие, используйте настройку query_cache_compress_entries.
Иногда полезно хранить в кэше несколько результатов для одного и того же запроса. Это можно сделать с помощью настройки query_cache_tag, которая выступает в роли метки (или пространства имен) для записей кэша запросов. Кэш запросов считает результаты одного и того же запроса с разными тегами разными.
Пример создания трех разных записей в кэше запросов для одного и того же запроса:
Чтобы удалить из кэша запросов только записи с тегом tag, можно использовать оператор SYSTEM DROP QUERY CACHE TAG 'tag'.
ClickHouse читает данные таблицы блоками по max_block_size строк. Из-за фильтрации, агрегации
и т.п. блоки результатов обычно значительно меньше, чем max_block_size, но бывают случаи, когда они существенно больше. Настройка
query_cache_squash_partial_results (включена по умолчанию) управляет тем,
будут ли блоки результатов объединяться (если они маленькие) или разбиваться (если они большие) на блоки размера max_block_size перед
записью в кэш результатов запросов. Это снижает производительность записи в кэш запросов, но улучшает степень сжатия элементов кэша и
обеспечивает более естественную зернистость блоков при последующей выдаче результатов запросов из кэша.
В результате кэш запросов хранит для каждого запроса несколько (частичных) блоков результатов. Хотя такое поведение является разумным значением по умолчанию, его можно отключить с помощью настройки query_cache_squash_partial_results.
Также результаты запросов с недетерминированными функциями по умолчанию не кэшируются. К таким функциям относятся:
- функции для доступа к словарям:
dictGet()и т.п.; - пользовательские функции без тега
<deterministic>true</deterministic>в их XML- определении; - функции, возвращающие текущие дату или время:
now(),today(),yesterday()и т.п.; - функции, возвращающие случайные значения:
randomString(),fuzzBits()и т.п.; - функции, результат которых зависит от размера и порядка внутренних фрагментов (чанков), используемых при обработке запроса:
nowInBlock()и т.п.,rowNumberInBlock(),runningDifference(),blockSize()и т.п.; - функции, зависящие от окружения:
currentUser(),queryID(),getMacro()и т.п.
Чтобы принудительно кэшировать результаты запросов с недетерминированными функциями, независимо от этого поведения, используйте настройку query_cache_nondeterministic_function_handling.
Результаты запросов, которые обращаются к системным таблицам (например, system.processes или information_schema.tables), по умолчанию не кэшируются. Чтобы принудительно кэшировать результаты запросов с системными таблицами, используйте настройку query_cache_system_table_handling.
Наконец, элементы в кэше запросов не разделяются между пользователями по соображениям безопасности. Например, пользователь A не должен иметь возможность обойти политику по строкам таблицы, запустив тот же запрос, что и другой пользователь B, для которого такая политика не задана. Однако при необходимости элементы кэша могут быть помечены как доступные другим пользователям (т.е. общими) с помощью настройки query_cache_share_between_users.