SQL Cache: руководство по кэшированию запросов
SQL Cache — это механизм кэширования результатов запросов в Doris. Он сохраняет результаты запросов, используя в качестве ключа метаданные, такие как текст SQL и версия данных, чтобы при последующих идентичных запросах возвращать результаты непосредственно из кэша. Это существенно сокращает затраты на повторные вычисления.
Контрольный список перед чтением
Прежде чем читать это руководство, убедитесь, что вы разобрались в следующих вопросах и выполнили необходимые приготовления:
- Подходит ли кэширование для вашего сценария запросов (например, для офлайн-аналитики T+1 или данных, которые редко обновляются)
- Поддерживает ли ваша текущая версия Doris просмотр SQL Cache через
explain plan(рекомендуется 2.1.3+) - Понимаете ли вы, что SQL Cache поддерживает только внутренние таблицы OlapTable и внешние таблицы Hive
- Понимаете ли вы, что недетерминированные функции (например,
now()иrandom()) влияют на долю попаданий в кэш - Есть ли у вас права на изменение конфигурации FE/BE (для управления памятью и глобального включения кэша)
Прежде чем включать SQL Cache, проверьте по каждому пункту, что ваши запросы соответствуют следующим условиям:
- Операторы SQL выполняются многократно с высокой частотой
- Данные обновляются редко (что обеспечивает высокую долю попаданий в кэш)
- Запросы не содержат функций со случайным результатом (например,
random()) - Набор полей метрик остаётся одинаковым во всех запросах (без динамического добавления или удаления)
1. Введение в концепцию
SQL Cache подходит для сценариев запросов, в которых данные обновляются редко. Кэширование результатов запросов позволяет избежать повторных вычислений.
Ключевые факторы попадания в кэш
SQL Cache однозначно идентифицирует кэшированные данные по сочетанию следующих факторов:
| Ключевой фактор | Описание |
|---|---|
| Текст SQL | Полностью идентичная строка SQL |
| Определение представления | DDL-определение всех используемых представлений |
| Версии таблиц и партиций | Изменялись ли данные |
| Пользовательские переменные и их значения | Текущие значения переменных, используемых в SQL |
| Недетерминированные функции и их результаты | Вычисленные результаты функций, таких как now() и random() |
| Определение политики строк | Конфигурация Row Policy |
| Определение маскирования данных | Конфигурация Data Masking |
При изменении любого фактора (например, при переписывании SQL, изменении полей или условий запроса либо изменении версии из-за обновления данных) попадания в кэш не будет. Для запросов с Join нескольких таблиц обновление любой из таблиц меняет идентификатор или номер версии партиции, что делает попадание в кэш невозможным.
Подходящие сценарии
- Настоятельно рекомендуется: сценарии обновления T+1. Данные обновляются ночью; первый запрос получает результат от BE и записывает его в кэш, а последующие идентичные запросы возвращают результат непосредственно из кэша.
- Возможно использование: данные, обновляемые в реальном времени. SQL Cache можно включить и в этом случае, но доля попаданий будет низкой.
- Поддерживаемые типы таблиц: в настоящее время поддерживаются внутренние таблицы OlapTable и внешние таблицы Hive.
2. Ограничения
Влияние недетерминированных функций
Определение: недетерминированная функция — это функция, результат которой не имеет фиксированной зависимости от входных параметров.
| Функция | Поведение | Возможность использования кэша |
|---|---|---|
now() | Возвращает текущее время с точностью до секунды, меняется каждую секунду | Повторное использование возможно в пределах одной секунды |
date(now()) | Преобразует время с точностью до секунды в дату с точностью до дня | Повторное использование возможно в пределах одного дня (рекомендуется) |
random() | Возвращает разный результат при каждом вызове | Практически никогда не даёт попаданий в кэш |
Совет по оптимизации: уменьшайте точность представления времени. Например, используйте select * from tbl where dt = date(now()) вместо select * from tbl where dt = now(), чтобы все запросы в пределах одного дня могли попадать в кэш. Избегайте использования в запросах функций с высокой степенью недетерминированности, таких как random().
Влияние добавления полей метрик
SQL Cache кэширует результаты строго по запрашиваемым полям. Он не поддерживает выполнение запроса с большим количеством метрик за счёт кэша с меньшим количеством метрик.
| Сценарий | Поведение | Последствия |
|---|---|---|
| Результат для 2 метрик уже закэширован, а новый запрос запрашивает 3 метрики | Повторно использовать кэш невозможно, запрос необходимо выполнить заново | Доля попаданий снижается, а новый результат записывается в новую запись кэша |
Совет по оптимизации: сохраняйте стабильный набор полей метрик в бизнес-отчётах и шаблонах запросов. Если нужно добавить метрики, измените шаблон запроса, а не расширяйте его от случая к случаю, чтобы кэш оставался пригодным для использования.
3. Принципы реализации
Реализация на FE
Последовательность обработки после получения запроса FE:
- Поиск метаданных: поиск метаданных в памяти по строке SQL в качестве ключа (метаданные содержат версии таблиц и партиций).
- Сравнение версий: если метаданные не изменились, данные также не менялись и кэш можно использовать повторно.
- Пропуск разбора: пропуск разбора и оптимизации SQL и определение соответствующего BE с помощью консистентного хеширования.
- Возврат результата:
- Попадание в кэш на BE: результат возвращается непосредственно клиенту.
- Промах кэша: выполняется полный цикл разбора SQL, оптимизации и вычислений.
- Запись результата в кэш: после завершения вычислений на BE процесс FE сохраняет результат на соответствующем BE и записывает метаданные в собственную память для повторного использования при следующем запросе.
Специальная оптимизация: если на этапе оптимизации SQL установлено, что число строк в результате равно только 0 или 1, FE сохраняет результат непосредственно в собственной памяти, чтобы ускорить последующие идентичные запросы.
Реализация на BE
- Для хранения результата выбирается BE с помощью консистентного хеширования. Результат хранится в памяти BE в структуре HashMap.
- При чтении из кэша или записи в него в качестве ключа используется дайджест метаданных, таких как строка SQL, что позволяет быстро получать данные результата.
4. Быстрый старт
Шаг 1: Включение и отключение SQL Cache
Цель: включить SQL Cache на уровне сеанса или глобально (по умолчанию отключён).
-- Turn on SQL Cache in the current session; it is off by default
set enable_sql_cache=true;
-- Turn off SQL Cache in the current session
set enable_sql_cache=false;
-- Turn on SQL Cache globally; it is off by default
set global enable_sql_cache=true;
-- Turn off SQL Cache globally
set global enable_sql_cache=false;
Примечания: настройка на уровне сеанса действует только в текущем сеансе. Глобальная настройка действует для всех вновь создаваемых сеансов.
Шаг 2: Проверка попадания запроса в SQL Cache
Способ A: Использование explain plan (для Doris 2.1.3+)
Цель: определить по плану запроса, произошло ли попадание в кэш.
Команда: выполните explain plan <your SQL>.
Примечания: если в дереве плана запроса появляется узел LogicalSqlCache или PhysicalSqlCache, запрос попал в SQL Cache.
> explain plan select * from t2;
+------------------------------------------------------------------------------------------------------------+
| Explain String(Nereids Planner) |
+------------------------------------------------------------------------------------------------------------+
| ========== PARSED PLAN (time: 28ms) ========== |
| LogicalSqlCache[1] ( queryId=711dea740e4746e6-8bc11afe08f6542c ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
| |
| ========== ANALYZED PLAN ========== |
| LogicalSqlCache[1] ( queryId=711dea740e4746e6-8bc11afe08f6542c ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
| |
| ========== REWRITTEN PLAN ========== |
| LogicalSqlCache[1] ( queryId=711dea740e4746e6-8bc11afe08f6542c ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
| |
| ========== OPTIMIZED PLAN ========== |
| PhysicalSqlCache[3] ( queryId=711dea740e4746e6-8bc11afe08f6542c, backend=192.168.126.3:9051, rowCount=12 ) |
| +--PhysicalResultSink[39] ( outputExprs=[id#0, name#1] ) |
| +--PhysicalOlapScan[t2]@0 ( stats=12 ) |
+------------------------------------------------------------------------------------------------------------+
Способ B: Просмотр Profile (для версий до Doris 2.1.3)
Цель: подтвердить попадание в кэш в версиях, которые не поддерживают explain plan.
Команда: включите Profile и просмотрите сводку выполнения.
Примечания: если поле Is Cached: содержит Yes, запрос попал в SQL Cache.
Execution Summary:
- Parse SQL Time: 18ms
- Nereids Analysis Time: N/A
- Nereids Rewrite Time: N/A
- Nereids Optimize Time: N/A
- Nereids Translate Time: N/A
- Workload Group: normal
- Analysis Time: N/A
- Wait and Fetch Result Time: N/A
- Fetch Result Time: 0ms
- Write Result Time: 0ms
- Doris Version: 915138e801
- Is Nereids: Yes
- Is Cached: Yes
- Total Instances Num: 0
- Instances Num Per BE:
- Parallel Fragment Exec Instance Num: 1
- Trace ID:
- Transaction Commit Time: N/A
- Nereids Distribute Time: N/A
5. Мониторинг метрик
Метрики мониторинга FE
Адрес: http://${FE_IP}:${FE_HTTP_PORT}/metrics
Примечания: значения счётчиков монотонно возрастают. После перезапуска FE отсчёт снова начинается с 0.
# 1 SQL has been written to the cache
doris_fe_cache_added{type="sql"} 1
# The SQL Cache has been hit 2 times
doris_fe_cache_hit{type="sql"} 2
Метрики мониторинга BE
Адрес: http://${BE_IP}:${BE_HTTP_PORT}/metrics
Примечания: разные записи кэша могут храниться на разных BE. Чтобы получить полную картину, необходимо собирать метрики со всех BE.
# There are currently 1205 caches in the memory of this BE
doris_be_query_cache_sql_total_count 1205
# All caches currently take up about 44KB of BE memory
doris_be_query_cache_memory_total_byte 44101
6. Управление памятью
Управление памятью FE
Метаданные кэша в FE используют слабые ссылки: при нехватке памяти на FE автоматически освобождаются метаданные, к которым дольше всего не обращались. Для ограничения использования памяти также поддерживаются следующие параметры:
| Параметр | Значение по умолчанию | Описание |
|---|---|---|
sql_cache_manage_num | 100 | Максимальное количество записей метаданных; при превышении автоматически освобождается запись, к которой дольше всего не обращались |
expire_sql_cache_in_fe_second | 300 | Срок действия метаданных (в секундах); они автоматически освобождаются, если к ним не обращались в течение этого времени |
cache_result_max_row_count | 3000 | Максимальное количество строк результата; при превышении SQL Cache не создаётся |
cache_result_max_data_size | 31457280 (30MB) | Максимальный размер результата (в байтах); при превышении SQL Cache не создаётся |
Команды настройки (вступают в силу немедленно; их нужно выполнить на каждом FE; для сохранения настроек запишите их в fe.conf):
-- Store at most 100 cache metadata entries
ADMIN SET FRONTEND CONFIG ('sql_cache_manage_num'='100');
-- Automatically release a cache metadata entry that has not been accessed for 300 seconds
ADMIN SET FRONTEND CONFIG ('expire_sql_cache_in_fe_second'='300');
-- Do not create SQL Cache when the result has more than 3000 rows by default
ADMIN SET FRONTEND CONFIG ('cache_result_max_row_count'='3000');
-- Do not create SQL Cache when the result is larger than 30MB by default
ADMIN SET FRONTEND CONFIG ('cache_result_max_data_size'='31457280');
Управление памятью BE
| Параметр | Значение по умолчанию (пример) | Описание |
|---|---|---|
query_cache_max_size_mb | 256 | Верхний предел объёма памяти, используемой кэшем в штатном режиме |
query_cache_elasticity_size_mb | 128 | Резерв для временного расширения; при превышении суммы max + elasticity начинается вытеснение записей, которое продолжается, пока использование памяти не опустится ниже max |
Файл конфигурации: be.conf (после изменения необходимо перезапустить BE).
-- When the cache memory usage exceeds query_cache_max_size_mb + query_cache_elasticity_size_mb,
-- release the least recently used cache entries until the memory usage drops below query_cache_max_size_mb.
query_cache_max_size_mb = 256
query_cache_elasticity_size_mb = 128
7. Устранение неполадок: исследование инвалидации кэша
В следующей таблице перечислены распространённые причины промахов кэша или его инвалидации и соответствующие направления проверки:
| № | Причина инвалидации | Типичная операция | Рекомендация по проверке |
|---|---|---|---|
| 1 | Изменение структуры таблицы или представления | drop table, replace table, alter table, alter view | Проверьте историю недавних DDL-операций с таблицами и представлениями |
| 2 | Изменение данных таблицы | insert, delete, update, truncate | Проверьте журналы загрузки и изменений, чтобы определить, изменилась ли версия данных |
| 3 | Отзыв прав пользователя | revoke | Проверьте изменения прав учётной записи, выполняющей запрос |
| 4 | Использование недетерминированных функций | select random(), select now(), и т. д. | Перейдите на функции с меньшей точностью или постоянные параметры |
| 5 | Изменение значения переменной | select * from tbl where dt = @dt_var | Проверьте, совпадают ли значения переменных сеанса |
| 6 | Изменение Row Policy / Data Masking | Изменена политика строк или маскирования | Проверьте историю недавних изменений политики |
| 7 | Количество строк результата превышает ограничение | Превышено значение cache_result_max_row_count (по умолчанию 3000 строк) | Измените порог или сократите набор результатов |
| 8 | Размер результата превышает ограничение | Превышено значение cache_result_max_data_size (по умолчанию 30 МБ) | Измените порог или сократите набор возвращаемых полей |
8. Часто задаваемые вопросы
В1: Включён ли SQL Cache по умолчанию?
О: По умолчанию он отключён. Включите его на уровне сеанса с помощью set enable_sql_cache=true или глобально с помощью set global enable_sql_cache=true.
В2: Какие типы таблиц поддерживает SQL Cache? О: В настоящее время поддерживаются внутренние таблицы OlapTable и внешние таблицы Hive.
В3: Можно ли использовать SQL Cache для данных, обновляемых в реальном времени? О: Да, но каждое обновление данных меняет версию партиции, что приводит к инвалидации кэша. Доля попаданий будет низкой. SQL Cache лучше подходит для сценариев офлайн-аналитики T+1.
В4: Как происходит инвалидация кэша для запросов с Join нескольких таблиц? О: Как только данные меняются в любой из таблиц, участвующих в Join, изменяется идентификатор или номер версии партиции, и попадание в кэш всего запроса становится невозможным.
В: Ранее были закэшированы две метрики. Можно ли повторно использовать кэш для запроса, который теперь запрашивает три метрики? О: Нет. SQL Cache кэширует результаты строго по запрашиваемым полям. Кэш с меньшим количеством метрик не может удовлетворить запрос с большим количеством метрик, поэтому запрос необходимо выполнить заново.
В5: Приводит ли функция now() к полной инвалидации кэша?
О: Нет, полной инвалидации не происходит. Функция now() возвращает время с точностью до секунды, поэтому идентичные запросы в пределах одной секунды могут повторно использовать кэш. Чтобы увеличить период, в течение которого возможны попадания в кэш, используйте date(now()) для преобразования времени в дату с точностью до дня.
В6: Где хранятся кэшированные данные? О: Подавляющее большинство результатов хранится в памяти BE (в структуре HashMap). Если число строк в результате равно только 0 или 1, FE сохраняет его непосредственно в собственной памяти.
В7: Доступен ли кэш после перезапуска FE? О: Метаданные в FE теряются, а метрики мониторинга также сбрасываются до 0. Результаты в памяти BE тоже становятся недействительными после перезапуска.
9. Сравнение и дополнительные сведения
| Критерий | SQL Cache | Partition Cache (где применимо) |
|---|---|---|
| Уровень кэширования | Набор результатов всего запроса SQL | Кэширование промежуточных результатов по партициям |
| Подходящий характер обновлений | T+1, редкие обновления | Некоторые партиции обновляются часто, а остальные остаются неизменными |
| Условие попадания | Текст SQL и все зависимые метаданные не изменились | Версии задействованных партиций не изменились |
| Уровень инвалидации | Изменение любой зависимости приводит к инвалидации всего кэша | Повторно вычисляются только партиции, для которых не было попадания в кэш |
Примечание: столбец Partition Cache в этой таблице приведён только для сравнения. Фактическая доступность зависит от возможностей текущей версии Doris.