Перейти к содержимому
Последнее обновление

Query Cache

Query Cache — это механизм конвейерного движка выполнения Apache Doris, который кэширует промежуточные результаты агрегации на уровне tablet и ускоряет повторяющиеся агрегирующие запросы.

Что проверить перед началом​

Перед использованием Query Cache убедитесь, что:

  • Запрос обращается к внутренней таблице OLAP (а не к внешней таблице, такой как Hive/JDBC/Iceberg/Hudi/Paimon)
  • Запрос является агрегирующим (содержит GROUP BY или агрегатные функции)
  • План запроса соответствует шаблону AggregationNode → OlapScanNode
  • Запрос не содержит узлов JOIN, SORT, UNION или WINDOW
  • Запрос не зависит от недетерминированных функций, таких как now(), rand() или uuid()
  • Задано enable_query_cache = true

Определение одним предложением​

Query Cache кэширует результаты агрегации на уровне tablet в конвейерном движке выполнения. Если контекст выполнения последующего запроса совпадает, механизм сразу возвращает кэшированные данные, позволяя избежать повторного сканирования и вычислений.

Зачем нужен Query Cache​

В аналитических сценариях один и тот же агрегирующий запрос часто выполняется многократно, хотя исходные данные не меняются. Например:

SELECT region, SUM(revenue) FROM orders WHERE dt = '2024-01-01' GROUP BY region;
SELECT region, SUM(revenue) FROM orders WHERE dt = '2024-01-01' GROUP BY region;

При каждом выполнении повторно сканируются те же tablet и заново вычисляется результат, что приводит к лишним затратам CPU и ввода-вывода. Query Cache кэширует промежуточные результаты агрегации и сразу возвращает их при попадании в кэш, значительно сокращая задержку.

Важные ограничения
  • Эта функция применяется только к агрегирующим запросам к внутренним таблицам OLAP. Обычное сканирование, JOIN, сортировки и подобные операции не используют Query Cache.
  • Внешние таблицы не поддерживаются (Hive, JDBC, Iceberg, Hudi, Paimon и другие).

Как это работает​

Поддерживаемые шаблоны запросов​

Кэш могут использовать только фрагменты, дерево плана выполнения которых соответствует одному из следующих шаблонов:

  • AggregationNode → OlapScanNode: одностадийная агрегация непосредственно над сканированием.
  • AggregationNode → AggregationNode → OlapScanNode: двухстадийная агрегация над сканированием.

Между узлом агрегации и узлом сканирования допускаются промежуточные узлы, такие как FilterNode и ProjectNode. Однако кэшируемое поддерево не должно содержать узлы JoinNode, SortNode, UnionNode, WindowNode или ExchangeNode.

Три компонента ключа кэша​

КомпонентОписание
Дайджест SQLХеш SHA-256, вычисляемый по нормализованному дереву плана выполнения (агрегатные функции, выражения группировки, предикаты фильтрации, не связанные с партициями, проецируемые столбцы и переменные сеанса, влияющие на результат). Семантически эквивалентные запросы дают одинаковый дайджест.
Список идентификаторов tabletОтсортированный список идентификаторов tablet, назначенных текущему экземпляру конвейера.
Диапазон tabletДопустимый диапазон сканирования для каждого tablet, определяемый по предикатам партиционирования (см. Особенности работы с партициями и фильтрами).

Условия инвалидации кэша​

Условие срабатыванияОписание
Изменение данныхINSERT, DELETE, UPDATE или компактизация увеличивают номер версии tablet; последующие запросы сравнивают версии, и их несовпадение приводит к промаху кэша.
Изменение схемыALTER TABLE изменяет структуру таблицы, что приводит к изменению плана выполнения и дайджеста.
Вытеснение LRUКогда объём памяти кэша превышает лимит, записи вытесняются по алгоритму LRU-K (K=2); для добавления новой записи к ней необходимо обратиться как минимум дважды.
Удаление устаревших записейЗаписи старше 24 часов автоматически удаляются периодической задачей очистки.
Принудительное обновлениеЕсли задано query_cache_force_refresh = true, кэш игнорируется и запрос выполняется заново.

Порядок выполнения​

Первое выполнение (промах кэша):

  1. Оператор сканирования читает данные из tablet в обычном режиме.
  2. Оператор агрегации вычисляет результат.
  3. Результат передаётся последующим потребителям и накапливается для записи в кэш.
  4. После завершения выполнения результат записывается в кэш, если накопленные данные не превышают лимиты размера или количества строк для одной записи.

Последующее выполнение (попадание в кэш):

  1. Оператор сканирования обнаруживает попадание в кэш и пропускает диапазон сканирования. Данные из tablet не читаются.
  2. Оператор агрегации не получает входных данных и ничего не выдаёт.
  3. Оператор источника кэша напрямую предоставляет кэшированные блоки данных.
  4. Если порядок столбцов отличается от порядка в записи кэша (например, SELECT a, b и SELECT b, a дают одинаковый дайджест), столбцы автоматически переставляются.

Особенности работы с партициями и фильтрами​

Чтобы добиться высокой доли попаданий в кэш, необходимо понимать, как предикаты партиционирования и выражения фильтрации взаимодействуют с Query Cache.

Предикаты партиционирования RANGE по одному столбцу​

Для таблиц с партиционированием RANGE по одному столбцу предикаты партиционирования обрабатываются особым образом:

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

Пример: таблица orders разбита на ежедневные партиции по столбцу dt.

-- Query A
SELECT region, SUM(revenue) FROM orders
WHERE dt >= '2024-01-01' AND dt < '2024-01-03' GROUP BY region;

-- Query B
SELECT region, SUM(revenue) FROM orders
WHERE dt >= '2024-01-02' AND dt < '2024-01-04' GROUP BY region;
  • Запрос A сканирует партиции 2024-01-01 и 2024-01-02.
  • Запрос B сканирует партиции 2024-01-02 и 2024-01-03.
  • Дайджест и диапазон tablet для партиции 2024-01-02 совпадают, поэтому запрос B повторно использует кэш запроса A для 2024-01-02, и пересчитать требуется только партицию 2024-01-03.

Таблицы с партиционированием RANGE по нескольким столбцам / LIST / без партиционирования​

Для таблиц с партиционированием RANGE по нескольким столбцам, партиционированием LIST или без партиционирования предикаты партиционирования нельзя исключить: они включаются непосредственно в дайджест. Даже небольшие различия в этих предикатах приводят к разным дайджестам и промахам кэша.

Выражения фильтрации, не связанные с партициями​

Выражения фильтрации, не связанные с партициями (например, WHERE status = 'active'), включаются в дайджест нормализованного плана выполнения. Два запроса могут совместно использовать кэш только в том случае, если их нормализованные выражения фильтрации семантически идентичны.

Запрос 1Запрос 2Совместное использование кэша
WHERE status = 'active'WHERE status = 'active'Да (одинаковый дайджест)
WHERE status = 'active'WHERE status = 'inactive'Нет (разные дайджесты)
WHERE status = 'active' AND region = 'ASIA'WHERE region = 'ASIA' AND status = 'active'Да (после нормализации порядок не имеет значения)

Переменные сеанса​

Переменные сеанса, влияющие на результаты запросов (такие как time_zone, sql_mode и sql_select_limit), включаются в дайджест. Изменение любой из этих переменных между двумя запросами приводит к другому ключу кэша и промаху.

Условия, при которых Query Cache отключается​

УсловиеПричина
К фрагменту применяется фильтр времени выполненияЗначения фильтра времени выполнения неизвестны на этапе планирования, поэтому кэширование привело бы к неверным результатам.
Содержит недетерминированные выражения (rand(), now(), uuid(), UDFs и другие)Даже при одинаковых входных данных результаты различаются от выполнения к выполнению.
Кэшируемое поддерево содержит узел JOIN, SORT, UNION или WINDOWПоддерживается только шаблон «агрегация со сканированием».
Узел сканирования не является OlapScanNode (например, сканируется внешняя таблица)Кэш опирается на идентификаторы и версии tablet, которых нет у внешних таблиц.

Почему Query Cache не поддерживает внешние таблицы​

Query Cache опирается на три свойства, присущие только внутренним таблицам OLAP:

  1. Организация данных на основе tablet: ключ кэша содержит идентификаторы tablet и диапазон сканирования для каждого из них. Внешние таблицы хранятся во внешних системах, таких как HDFS, S3 или JDBC, и не используют понятие tablet.
  2. Инвалидация на основе версий: каждый внутренний tablet имеет монотонно возрастающий номер версии, по которому кэш определяет устаревшие данные. Внешние таблицы не предоставляют Doris такой механизм версий.
  3. Требование OlapScanNode: логика нормализации плана выполнения распознаёт только OlapScanNode как допустимый узел сканирования под точкой кэширования агрегации.

Для кэширования запросов к внешним таблицам используйте SQL Cache.

Параметры конфигурации​

Переменные сеанса (FE)​

ПараметрОписаниеПо умолчанию
enable_query_cacheГлавный переключатель, включающий или отключающий Query Cache.false
query_cache_force_refreshЕсли задано true, кэшированный результат игнорируется и запрос выполняется заново; новый результат по-прежнему записывается в кэш.false
query_cache_entry_max_bytesМаксимальный размер одной записи кэша в байтах; результаты фрагментов, превышающие этот лимит, не кэшируются.5242880 (5 MB)
query_cache_entry_max_rowsМаксимальное количество строк в одной записи кэша; результаты фрагментов, превышающие этот лимит, не кэшируются.500000

Настройки BE (be.conf)​

ПараметрОписаниеПо умолчанию
query_cache_sizeОбщий объём памяти Query Cache на каждом BE (MB).512
примечание

Настройки query_cache_max_size_mb и query_cache_elasticity_size_mb в be.conf управляют устаревшим SQL Result Cache. Они не относятся к Query Cache уровня конвейера, описанному в этой статье. Не путайте эти механизмы.

Примеры использования​

Шаг 1: включить Query Cache​

Цель: включить главный переключатель Query Cache.

SET enable_query_cache = true;

Описание: эта переменная действует в пределах сеанса, поэтому её нужно включать в каждом соединении. Также можно задать её значение по умолчанию как глобальную переменную FE.

Шаг 2: выполнить типичный агрегирующий запрос​

Цель: вызвать запись в кэш и чтение из него.

-- First execution: cache miss, compute the result and write it to the cache
SELECT region, SUM(revenue), COUNT(*)
FROM orders
WHERE dt = '2024-01-15' AND status = 'completed'
GROUP BY region;

-- Second execution: cache hit, return the result directly from the cache
SELECT region, SUM(revenue), COUNT(*)
FROM orders
WHERE dt = '2024-01-15' AND status = 'completed'
GROUP BY region;

Описание: дайджест SQL, список идентификаторов tablet и диапазон tablet при втором выполнении совпадают с первым, поэтому происходит попадание в кэш.

Шаг 3: проверить попадание в кэш по профилю​

Цель: убедиться, что запрос действительно использует кэш.

После выполнения запроса откройте профиль и найдите раздел CacheSourceOperator:

Поле профиляЗначение
HitCache: trueЗапрос получил результат из кэша.
HitCache: false, InsertCache: trueПроизошёл промах, но результат успешно записан в кэш.
HitCache: false, InsertCache: falseПроизошёл промах, а результат оказался слишком большим для кэширования.
CacheTabletIdИдентификатор tablet, к которому относится запись кэша.

Шаг 4: принудительно обновить кэш​

Цель: проигнорировать существующий кэш и заново вычислить результат (например, если вы подозреваете, что кэшированные данные неверны).

-- Force the next query to skip the cache and recompute the result
SET query_cache_force_refresh = true;

SELECT region, SUM(revenue) FROM orders WHERE dt = '2024-01-15' GROUP BY region;

-- Reset
SET query_cache_force_refresh = false;

Описание: после принудительного обновления новый результат по-прежнему записывается в кэш.

Сравнение сценариев использования​

СценарийПрименимостьПричина
Панели мониторинга или инструменты BI, многократно выполняющие один и тот же агрегирующий SQLДаДайджест и tablet полностью совпадают, обеспечивая высокую долю попаданий в кэш.
Отчёты T+1 (данные загружаются раз в день)ДаПоследующие запросы в тот же день могут попадать в кэш.
Агрегирующие запросы с пересекающимися диапазонами датДаПартиционирование RANGE по одному столбцу позволяет совместно использовать записи кэша на уровне tablet.
Обычное сканирование SELECT, JOIN, сортировки и оконные функцииНетПоддерживается только шаблон «агрегация со сканированием».
Внешние таблицы (Hive, JDBC, Iceberg, Hudi, Paimon)НетНет механизма tablet или версий. Используйте SQL Cache.
Часто обновляемые таблицыНетВерсии tablet быстро меняются, что приводит к низкой доле попаданий в кэш.
Запросы, содержащие now(), rand(), uuid() или UDFsНетНедетерминированные результаты отключают кэш.
Запросы, зависящие от фильтров времени выполненияНетЗначения фильтров времени выполнения неизвестны на этапе планирования.

Примечания​

  • Кэш не сохраняется: Query Cache находится в памяти BE и очищается при перезапуске BE.
  • Потребление памяти: кэшированные блоки данных занимают память BE. Следите за использованием памяти и при необходимости изменяйте query_cache_size.
  • Допуск в кэш по LRU-K: когда кэш заполнен, для добавления новой записи к ней необходимо обратиться как минимум дважды (K=2). Это предотвращает заполнение кэша результатами редко выполняемых запросов.

Устранение неполадок​

СимптомВозможная причинаРешение
Постоянно появляется HitCache: falseНе включён enable_query_cache.Выполните SET enable_query_cache = true.
HitCache: false, InsertCache: falseРезультат для одной записи слишком велик и превышает query_cache_entry_max_bytes или _max_rows.Увеличьте соответствующий порог или добавьте фильтры, чтобы уменьшить размер результата.
В плане отсутствует CacheSourceOperatorПлан содержит JOIN/SORT/UNION/WINDOW или к нему применяется фильтр времени выполнения.Перепишите SQL в соответствии с шаблоном «агрегация со сканированием».
Таблица является внешнейQuery Cache не поддерживает внешние таблицы.Используйте SQL Cache.
Данные не изменились, но кэш по-прежнему даёт промахиИзменилась схема или переменная сеанса либо задано query_cache_force_refresh = true.Проверьте историю ALTER, сравните переменные сеанса и сбросьте query_cache_force_refresh.
Доля попаданий в кэш очень низкаяTablet часто обновляется или подвергается компактизации.Уменьшите частоту записи или включайте кэш только для редко обновляемых таблиц.
Растёт нагрузка на память BEЗадано слишком большое значение query_cache_size.Уменьшите query_cache_size и перезапустите BE.

Частые вопросы​

Вопрос 1: чем Query Cache отличается от SQL Cache?

КритерийQuery CacheSQL Cache
Гранулярность кэшаПромежуточные результаты агрегации на уровне tablet.Итоговый результат всего SQL-оператора.
Поддерживаемые запросыТолько агрегирующие запросы к внутренним таблицам OLAP.Любые запросы, включая запросы к внешним таблицам.
Возможность совместного использованияРазные SQL-операторы могут совместно использовать записи кэша на уровне tablet.Попадание возможно только при точном совпадении текста SQL.
ИнвалидацияСразу после изменения версии tablet.На основе версий партиций или времени.

Вопрос 2: начнутся ли попадания в кэш сразу после его включения?

Нет. При первом выполнении происходит «промах кэша с записью в кэш»; попадания возможны только при втором и последующих выполнениях. Кроме того, LRU-K (K=2) требует как минимум двух обращений к новой записи, прежде чем она будет фактически добавлена в кэш.

Вопрос 3: можно ли кэшировать агрегации с JOIN?

Нет. Наличие JoinNode в кэшируемом поддереве отключает Query Cache для этого фрагмента. Попробуйте переписать запрос так, чтобы сначала выполнялась агрегация, а затем JOIN, или используйте материализованное представление.

Вопрос 4: нужен ли прогрев кэша после перезапуска BE?

Да. Query Cache хранится в памяти и очищается при перезапуске. Для прогрева можно выполнять основные агрегирующие SQL-запросы в часы низкой нагрузки.

Вопрос 5: как убедиться, что действительно произошло попадание в кэш?

После выполнения SQL проверьте поле HitCache оператора CacheSourceOperator в профиле.

Итоги​

Query Cache — это механизм оптимизации на уровне конвейера в Doris, который кэширует промежуточные результаты агрегации на уровне tablet. Его основные особенности:

  • Применяется только к агрегирующим запросам к внутренним таблицам OLAP.
  • Автоматически инвалидирует кэш на основе версий tablet.
  • Выделяет предикаты партиционирования из дайджеста, позволяя запросам с пересекающимися диапазонами партиций совместно использовать кэш.
  • Ограничивает размер и количество строк для каждой записи, чтобы слишком большие результаты не занимали память кэша.
  • Использует политику вытеснения LRU-K (K=2), чтобы поддерживать высокую эффективность кэша.
На этой странице