Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions docs/query-acceleration/query-cache.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---

Check notice on line 1 in docs/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

i18n-sync-locale-candidate

Japanese docs are report-only. Generate a candidate translation from the changed files and merge it only after human review. Owner%3A @apache/doris-website-maintainers

Check warning on line 1 in docs/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

i18n-sync-version-counterpart

English current and 4.x docs are strongly synchronized. Update the counterpart or explain the version-specific exception in the PR description. Owner%3A @apache/doris-website-maintainers

Check warning on line 1 in docs/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

seo-title-duplicate

Rendered SEO title is duplicated across indexable pages%3A "Query Cache User Guide - Apache Doris". Add a version%2C locale%2C or page-specific qualifier. Owner%3A @apache/doris-website-maintainers

Check warning on line 1 in docs/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

seo-description-length

SEO description should be 80-160 characters; current length is 224. Owner%3A @apache/doris-website-maintainers

Check warning on line 1 in docs/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

sidebar-orphan-doc

Document query-acceleration/query-cache is not referenced by sidebars.ts. Owner%3A @apache/doris-website-maintainers
title: Query Cache User Guide
description: How can you accelerate repeated aggregation queries with the Apache Doris Query Cache? This article explains the principles, configuration parameters, hit conditions, invalidation mechanism, and common troubleshooting steps.
keywords:
Expand Down Expand Up @@ -199,13 +199,17 @@

1. The scanned index (base table or the selected rollup) is **append-only**: either the **DUP_KEYS** model, or a **merge-on-write UNIQUE_KEYS** table whose delta window did not rewrite any pre-existing key. For merge-on-write tables BE checks the delete bitmap of `(cached_version, current_version]` per tablet: a window that only appended brand-new keys (the common "hourly load" pattern on a primary-key table) is merged incrementally, while an upsert, a partial update or a delete sign that hit an older key falls back to one full recompute, which re-bases the entry so the next pure-append load is incremental again. Merge-on-read UNIQUE tables resolve duplicates while reading and AGG tables merge rows inside the storage layer, so "cached snapshot + delta = new snapshot" would not hold there. Aggregated materialized views are rejected for the same reason.
2. The cache point is a **non-finalize aggregation directly above the scan** (the common two-phase aggregation shape, e.g. `GROUP BY` on a non-distribution column). One-phase aggregations and nested aggregation cache points are rejected.
3. The cluster runs in the **shared-nothing (local storage)** mode.
3. The tablet view can be synced to the queried version. This holds trivially in the compute-storage coupled mode; in the **compute-storage decoupled (cloud)** mode the per-tablet decision first brings the local view (rowsets, and for merge-on-write tables the delete bitmap) up to the queried version, which is usually free (a no-op when the view is already fresh, otherwise it merely front-loads the sync the scan would perform anyway); a sync that fails, or that stalls past a short fast-fail budget (`query_cache_decision_sync_timeout_ms`, a BE config, so a slow meta service cannot hold the decision's admission thread), falls back to one full recompute.
4. The delta version path is still capturable (not merged away by compaction) and contains **no DELETE predicate**, because a delete inside the delta logically removes rows that are already folded into the cached blocks, which cannot be undone by merging.

For merge-on-write tables, the delete bitmap window check works as follows:

![Merge-on-write delete bitmap window check](/images/next/query-cache/incremental-merge-mow-bitmap.svg)

:::note
On compute-storage decoupled (cloud) clusters, two session variables trade version exactness for speed: `query_freshness_tolerance_ms` lets the scan stop at a warmed-up version below the queried one, and `enable_prefer_cached_rowset` lets it follow the warmed-up rowset layout; under either knob the walk may also reach beyond the queried version (a warmed compaction rowset spanning it drags the path past). A query running with either knob may therefore read a version-inexact view, so it does not use incremental merge: the delta capture always targets the exact queried version, which would defeat the knob. Independent of incremental merge, BE also skips the query cache write-back of such a query on cloud, so no entry whose content mismatches its version stamp is ever created; the query can still consume an exact HIT filled by a plain run. The incremental-merge exclusion itself is applied in every deployment mode (a deliberately mode-agnostic gate); on local storage the knobs are inert, so it merely forgoes the incremental optimization for a query that opted into a cloud-only trade-off, and results stay correct either way.
:::

### Compaction of Merged Entries

Every incremental merge appends the delta blocks to the entry, so the entry gets more fragmented over time. When an entry has accumulated `query_cache_max_incremental_merge_count` merges (BE configuration, default `8`, changeable at runtime), the next query recomputes it from a full scan, which naturally compacts the entry.
Expand All @@ -216,12 +220,12 @@

### Observability

- In the query profile of the CACHE_SOURCE operator: `HitCacheStale = 1` indicates an incremental merge; `IncrementalDeltaVersions` shows the delta range (for example `(100, 114514]`); when a stale entry could not be reused, `IncrementalFallbackReason` explains why (`delta versions not capturable`, `delta contains delete predicates`, `delta rewrites history rows`, `keys type not append-only`, and so on).
- BE metrics: `doris_be_query_cache_stale_hit_total`, `doris_be_query_cache_incremental_fallback_total`, and `doris_be_query_cache_write_back_total`.
- In the query profile of the CACHE_SOURCE operator: `HitCacheStale = 1` indicates an incremental merge; `IncrementalDeltaVersions` shows the delta range (for example `(100, 114514]`); when a stale entry could not be reused, `IncrementalFallbackReason` explains why (`delta versions not capturable`, `delta contains delete predicates`, `delta rewrites history rows`, `keys type not append-only`, `cloud rowset sync failed`, `cloud rowset sync timed out`, and so on).
- BE metrics: `doris_be_query_cache_stale_hit_total`, `doris_be_query_cache_incremental_fallback_total`, `doris_be_query_cache_write_back_total`, and `doris_be_query_cache_decision_sync_time_ms` (only advances in the compute-storage decoupled mode: cumulative wall time the incremental decision spent syncing tablet views to the queried version before capturing the delta).

How BE decides between HIT, INCREMENTAL and MISS, and where each fallback reason comes from:

![Decision flow and the eight fallback reasons](/images/next/query-cache/incremental-merge-decision-flow.svg)
![Decision flow and the main fallback reasons](/images/next/query-cache/incremental-merge-decision-flow.svg)

Putting it all together, from the FE authorization to the storage-level guards:

Expand All @@ -248,6 +252,7 @@
| ------------------ | -------------------------------------------------------- | ------- |
| `query_cache_size` | The total memory capacity of the Query Cache on each BE (MB). | `512` |
| `query_cache_max_incremental_merge_count` | The maximum number of incremental merges accumulated on one cache entry before a full recompute is forced to compact it. `0` disables incremental merge at runtime. | `8` |
| `query_cache_decision_sync_timeout_ms` | (Cloud mode only) Fast-fail budget in milliseconds for the incremental-merge decision to sync each tablet view up to the queried version before capturing the delta; if a sync does not finish within it the query falls back to one full recompute. `<= 0` disables cloud incremental merge (any pending sync falls back). Runtime-changeable. | `2000` |

:::note
The `query_cache_max_size_mb` and `query_cache_elasticity_size_mb` settings in `be.conf` control the legacy SQL Result Cache. They are **not** the pipeline-level Query Cache described in this article. Do not confuse them.
Expand Down Expand Up @@ -375,7 +380,7 @@

**Q3: Can aggregations that involve a JOIN be cached?**

No. A `JoinNode` in the cached subtree disables the Query Cache for that fragment. Consider rewriting the query to aggregate first and then JOIN, or use a materialized view.
It depends on where the aggregation sits relative to the JOIN. If the aggregation is directly above the JOIN (a `JoinNode` appears in the cached subtree), that fragment is not cached, independent of runtime filters. But if you rewrite the query to aggregate first and then JOIN (pushing the aggregation down onto each table's scan), the per-tablet aggregation of each table can be cached on its own. One independent constraint still applies here: the JOIN pushes a runtime filter onto the probe-side scan, which makes that side's fragment non-cacheable, so with runtime filters on, only the build side is cached; to let every table in the JOIN be cached, also set `runtime_filter_mode=OFF`. Alternatively, use a materialized view.

**Q4: Does the cache need warm-up after a BE restart?**

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---

Check warning on line 1 in i18n/zh-CN/docusaurus-plugin-content-docs/current/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

seo-title-duplicate

Rendered SEO title is duplicated across indexable pages%3A "Query Cache 查询缓存使用指南 - Apache Doris". Add a version%2C locale%2C or page-specific qualifier. Owner%3A @apache/doris-website-maintainers

Check warning on line 1 in i18n/zh-CN/docusaurus-plugin-content-docs/current/query-acceleration/query-cache.md

View workflow job for this annotation

GitHub Actions / Build Check

seo-description-length

SEO description should be 80-160 characters; current length is 67. Owner%3A @apache/doris-website-maintainers
title: Query Cache 查询缓存使用指南
description: 如何用 Apache Doris Query Cache 加速重复聚合查询?本文讲解原理、配置参数、命中条件、失效机制与常见问题排查。
keywords:
Expand Down Expand Up @@ -199,13 +199,17 @@

1. 被扫描的索引(基表或选中的 rollup)为**追加写**模型:**DUP_KEYS** 表,或增量窗口内未改写既有主键的**写时合并(merge-on-write)UNIQUE_KEYS** 表。对写时合并表,BE 会按 tablet 检查 `(cached_version, current_version]` 窗口内的 delete bitmap:窗口内只新增了全新主键(主键表上常见的“每小时追加导入”模式)即可增量合并;一旦有 upsert、部分列更新或 delete sign 命中了更早的主键,则回退一次全量重算,重算会以新版本重建条目,下一次纯追加导入即恢复增量。读时合并(merge-on-read)UNIQUE 表在读取时跨 rowset 归并去重、AGG 表在存储层合并行,“缓存快照 + 增量 = 新快照”不成立;聚合物化视图同理被拒绝。
2. 缓存点是**直接位于扫描之上、且不做 finalize 的聚合**(常见的两阶段聚合形态,例如按非分桶列 `GROUP BY`)。单阶段聚合与嵌套聚合缓存点会被拒绝。
3. 集群为**存算一体(本地存储)**部署
3. tablet 视图可同步到查询版本。存算一体形态下天然满足;**存算分离(cloud)**形态下,逐 tablet 的判定会先把本地视图(rowset 列表,merge-on-write 表还包括 delete bitmap)同步到查询版本,这通常没有额外代价(视图已新鲜时是空操作,否则只是把扫描阶段本来就要做的同步提前执行);同步失败,或同步耗时超过一个短的快速失败预算(`query_cache_decision_sync_timeout_ms`,BE 配置,防止慢的 meta service 长时间占住决策所在的准入线程),则回退为一次全量重算
4. 增量版本路径仍可捕获(未被 compaction 合并跨界),且增量中**不含 DELETE 谓词**(增量中的删除会逻辑上移除已折入缓存块的行,无法通过合并撤销)。

写时合并表的 delete bitmap 窗口检查方式如下:

![写时合并表的 delete bitmap 窗口检查](/images/next/query-cache/incremental-merge-mow-bitmap.svg)

:::note
存算分离(cloud)形态下,有两个以版本精确性换取速度的会话变量:`query_freshness_tolerance_ms` 允许扫描停在低于查询版本的已预热版本上,`enable_prefer_cached_rowset` 允许扫描沿已预热的 rowset 布局读取;两者的版本走线都可能越过查询版本(一个跨越查询版本的已预热 compaction rowset 会把路径带到其后)。开启任一变量的查询因此可能读取版本不精确的视图,所以不走增量合并:增量捕获必须精确对准查询版本,这与两个开关的目的相悖。此外(这一点与增量合并无关),BE 在存算分离形态下也不会把这类查询的结果写回 Query Cache,保证缓存中不会出现内容与版本戳不符的条目;这类查询仍可消费由普通查询灌入的精确命中(HIT)条目。增量合并的排除本身在所有部署形态下都生效(有意做成与形态无关的门禁);存算一体形态下这两个变量本身不生效,因此这只是让一个开启了 cloud 专属开关的查询放弃增量优化,两种形态下结果都正确。
:::

### 增量部分的合并

每次增量合并都会把增量块追加进条目,条目随之逐渐碎片化。当条目累计 `query_cache_max_incremental_merge_count` 次合并(BE 配置,默认 `8`,运行时可调)后,下一次查询会强制全量重算,条目自然压实。
Expand All @@ -216,12 +220,12 @@

### 可观测性

- CACHE_SOURCE 算子的查询 profile:`HitCacheStale = 1` 表示走了增量合并;`IncrementalDeltaVersions` 展示增量区间(如 `(100, 114514]`);过期条目未能复用时,`IncrementalFallbackReason` 给出原因(`delta versions not capturable`、`delta contains delete predicates`、`delta rewrites history rows`、`keys type not append-only` 等)。
- BE 指标:`doris_be_query_cache_stale_hit_total`、`doris_be_query_cache_incremental_fallback_total`、`doris_be_query_cache_write_back_total`。
- CACHE_SOURCE 算子的查询 profile:`HitCacheStale = 1` 表示走了增量合并;`IncrementalDeltaVersions` 展示增量区间(如 `(100, 114514]`);过期条目未能复用时,`IncrementalFallbackReason` 给出原因(`delta versions not capturable`、`delta contains delete predicates`、`delta rewrites history rows`、`keys type not append-only`、`cloud rowset sync failed`、`cloud rowset sync timed out` 等)。
- BE 指标:`doris_be_query_cache_stale_hit_total`、`doris_be_query_cache_incremental_fallback_total`、`doris_be_query_cache_write_back_total`、`doris_be_query_cache_decision_sync_time_ms`(仅在存算分离形态下增长:增量判定在捕获增量前把 tablet 视图同步到查询版本所花费的累计耗时)

BE 如何在 HIT、INCREMENTAL、MISS 三态间决策,以及每种回退原因的来源:

![决策流程与八种回退原因](/images/next/query-cache/incremental-merge-decision-flow.svg)
![决策流程与主要回退原因](/images/next/query-cache/incremental-merge-decision-flow.svg)

从 FE 授权到存储层防线的全景视图:

Expand All @@ -248,6 +252,7 @@
| ------------------ | ------------------------------------------ | ------ |
| `query_cache_size` | 每个 BE 上 Query Cache 的总内存容量(MB) | `512` |
| `query_cache_max_incremental_merge_count` | 单个缓存条目上累计增量合并的最大次数,达到后强制一次全量重算以压实条目;设为 `0` 可在运行时禁用增量合并 | `8` |
| `query_cache_decision_sync_timeout_ms` | (仅 cloud 存算分离模式)增量合并判定在捕获增量前,把每个 tablet 视图同步到查询版本的快速失败预算(毫秒);同步未在此预算内完成则该查询回退为一次全量重算。设为 `<= 0` 时任何未完成的同步都会回退,即禁用 cloud 增量合并。运行时可调。 | `2000` |

:::note
`be.conf` 中的 `query_cache_max_size_mb` 和 `query_cache_elasticity_size_mb` 控制的是旧版 SQL Result Cache,**不是** 本文描述的流水线级别 Query Cache,请勿混淆。
Expand Down Expand Up @@ -375,7 +380,7 @@

**Q3:可以缓存涉及 JOIN 的聚合吗?**

不可以。缓存子树中包含 `JoinNode` 会使 Query Cache 在该 Fragment 上被禁用。可以考虑改写为先聚合再 JOIN,或使用物化视图
要看聚合相对 JOIN 的位置。若聚合直接位于 JOIN 之上(缓存子树里含 `JoinNode`),该 Fragment 不缓存,且与 runtime filter 无关。但若改写为**先聚合再 JOIN**(把聚合下推到各表扫描之上),每张表按 Tablet 的聚合就能各自作为缓存点。这里还有一层独立的限制:JOIN 会向探测侧扫描下发 runtime filter,使该侧 Fragment 无法缓存,因此 runtime filter 开启时只有 Build 侧命中;要让参与 JOIN 的各表都命中缓存,需同时设置 `runtime_filter_mode=OFF`。也可改用物化视图

**Q4:BE 重启后需要预热吗?**

Expand Down
Loading
Loading