This is an automated email from the ASF dual-hosted git repository.
zclll pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git
The following commit(s) were added to refs/heads/master by this push:
new b0144e5801c docs: add Query Cache guide and Chinese translation (#3439)
b0144e5801c is described below
commit b0144e5801c7ac85476cc0089b805af46a78fa1e
Author: HappenLee <[email protected]>
AuthorDate: Thu Mar 12 16:33:58 2026 +0800
docs: add Query Cache guide and Chinese translation (#3439)
Add Query Cache documentation and its Chinese localization; update
sidebars.json to include the new page. This documents Query Cache
behavior, configuration, limitations and examples.
---
docs/query-acceleration/query-cache.md | 231 ++++++++++++++++++++
.../current/query-acceleration/query-cache.md | 238 +++++++++++++++++++++
.../version-4.x/query-acceleration/query-cache.md | 238 +++++++++++++++++++++
.../version-4.x/query-acceleration/query-cache.md | 231 ++++++++++++++++++++
versioned_sidebars/version-4.x-sidebars.json | 1 +
5 files changed, 939 insertions(+)
diff --git a/docs/query-acceleration/query-cache.md
b/docs/query-acceleration/query-cache.md
new file mode 100644
index 00000000000..baaea5d540d
--- /dev/null
+++ b/docs/query-acceleration/query-cache.md
@@ -0,0 +1,231 @@
+# Query Cache
+
+## Introduction
+
+In analytical workloads, the same aggregation query is often executed
repeatedly on data that has not changed, for example:
+
+```sql
+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;
+```
+
+Each execution re-scans identical tablets and re-computes identical
aggregation results, wasting CPU and I/O resources.
+
+To address this, Apache Doris provides a **Query Cache** mechanism. It caches
the intermediate aggregation results produced inside the pipeline execution
engine and serves them directly to subsequent queries that share the same
execution context, significantly reducing query latency.
+
+:::caution Important Limitations
+- Query Cache applies **only to aggregation queries** on **internal OLAP
tables**. Non-aggregation queries (plain scans, joins, sorts, etc.) do not use
Query Cache.
+- Query Cache **does not work on external tables** (Hive, JDBC, Iceberg, Hudi,
Paimon, etc.).
+:::
+
+## Working Principle
+
+### Applicable Query Patterns
+
+Query Cache is designed for aggregation queries. Specifically, only fragments
whose plan tree matches one of the following patterns are eligible:
+
+- `AggregationNode → OlapScanNode` (single-phase aggregation directly on a
scan)
+- `AggregationNode → AggregationNode → OlapScanNode` (two-phase aggregation on
a scan)
+
+Intermediate nodes such as `FilterNode` and `ProjectNode` are allowed between
the aggregation and scan nodes. However, the plan tree must **not** contain
`JoinNode`, `SortNode`, `UnionNode`, `WindowNode`, or `ExchangeNode` within the
cache-eligible subtree.
+
+### Cache Key
+
+The cache key is composed of three parts:
+
+1. **SQL Digest** — A SHA-256 hash computed from the normalized plan tree
(aggregation functions, grouping expressions, non-partition filter predicates,
projections, and result-affecting session variables). The normalization process
assigns canonical IDs to all internal identifiers, so two semantically
identical queries produce the same digest even if they have different internal
plan node / slot IDs.
+
+2. **Tablet IDs** — The sorted list of tablet IDs assigned to the current
pipeline instance.
+
+3. **Tablet Range** — The effective scan range for each tablet, derived from
partition predicates (see [Partition and Filter
Behavior](#partition-and-filter-behavior)).
+
+### Cache Invalidation
+
+A cache entry becomes invalid when any of the following occurs:
+
+- **Data changes**: INSERT, DELETE, UPDATE, or Compaction causes the tablet
version to increment. On the next query, the tablet version is compared against
the cached version; a mismatch means a cache miss.
+- **Schema changes**: ALTER TABLE operations change the table structure, which
changes the plan and thus the digest.
+- **LRU eviction**: When the cache memory exceeds the configured limit, least
recently used entries are evicted. The cache uses an LRU-K (K=2) algorithm — a
new entry must be accessed at least twice before it is admitted into the cache
when the cache is full.
+- **Stale sweep**: Entries older than 24 hours are automatically removed by
periodic pruning.
+- **Force refresh**: When `query_cache_force_refresh = true`, cached results
are ignored and the query re-executes.
+
+### Execution Flow
+
+**First execution (cache miss)**:
+
+1. The scan operator reads data from tablets normally.
+2. The aggregation operator computes results.
+3. The results are sent to the downstream consumer and simultaneously
accumulated for cache insertion.
+4. On completion, if the accumulated result does not exceed the per-entry
size/row limits, the result is inserted into the cache.
+
+**Subsequent execution (cache hit)**:
+
+1. The scan operator detects a cache hit and skips adding any scan ranges — no
tablet data is read.
+2. The aggregation operator produces nothing (no input data).
+3. The cache source operator serves the cached blocks directly.
+4. If the column order differs from the cached entry (e.g., `SELECT a, b` vs.
`SELECT b, a` with the same digest), columns are reordered automatically.
+
+## Partition and Filter Behavior
+
+Understanding how partition predicates and filter expressions interact with
Query Cache is essential for achieving good hit rates.
+
+### Partition Predicates
+
+For tables with **single-column RANGE partitioning**, partition predicates
receive special treatment:
+
+- The partition predicate is **extracted from the digest**. Instead, the
effective range (the intersection of the predicate range with each partition's
actual range boundary) is computed and appended to the cache key as the tablet
range string.
+- This means two queries that differ **only** in their partition filter range
can share cache entries for the tablets they have in common.
+
+**Example:**
+
+Consider a table `orders` partitioned by `dt` with daily partitions:
+
+```sql
+-- 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;
+```
+
+- Query A scans tablets from partitions `2024-01-01` and `2024-01-02`.
+- Query B scans tablets from partitions `2024-01-02` and `2024-01-03`.
+- The tablets for partition `2024-01-02` have the same digest and the same
tablet range, so **Query B can reuse Query A's cache for the `2024-01-02`
partition**. Only partition `2024-01-03` needs to be computed fresh.
+
+For **multi-column RANGE partitioning**, **LIST partitioning**, or
**UNPARTITIONED** tables, partition predicates cannot be extracted and are
included directly in the digest. In this case, even minor differences in
partition predicates produce different digests and cache misses.
+
+### Non-Partition Filter Expressions
+
+Non-partition filter expressions (e.g., `WHERE status = 'active'`) are
included in the normalized plan digest. Two queries can share a cache entry
only when their non-partition filter expressions are semantically identical
after normalization.
+
+- `WHERE status = 'active'` and `WHERE status = 'active'` — same digest, cache
hit.
+- `WHERE status = 'active'` and `WHERE status = 'inactive'` — different
digest, cache miss.
+- `WHERE status = 'active' AND region = 'ASIA'` and `WHERE region = 'ASIA' AND
status = 'active'` — the normalization process sorts conjuncts, so they produce
the same digest and can hit the cache.
+
+### Session Variables
+
+Session variables that affect query results (such as `time_zone`, `sql_mode`,
`sql_select_limit`, etc.) are included in the digest. Changing any of these
variables between queries produces a different cache key and causes a cache
miss.
+
+### Conditions That Disable Query Cache
+
+The following conditions cause the planner to skip Query Cache entirely for a
fragment:
+
+| Condition | Reason |
+|-----------|--------|
+| Fragment is a target of runtime filters | Runtime filter values are dynamic
and unknown at plan time; caching would produce incorrect results |
+| Non-deterministic expressions (`rand()`, `now()`, `uuid()`, UDFs, etc.) |
Results vary across executions even with identical input |
+| Plan contains JOIN, SORT, UNION, or WINDOW nodes in the cache subtree | Only
aggregation-over-scan patterns are supported |
+| Scan node is not `OlapScanNode` (e.g., external table scan) | Cache depends
on tablet IDs and versions, which do not exist for external tables |
+
+## Why Query Cache Does Not Work on External Tables
+
+Query Cache relies on three properties unique to internal OLAP tables:
+
+1. **Tablet-based data organization** — The cache key includes tablet IDs and
per-tablet scan ranges. External tables store data in external systems (HDFS,
S3, JDBC, etc.) and have no tablet concept.
+
+2. **Version-based invalidation** — Each internal tablet has a monotonically
increasing version number that changes on data modification. The cache uses
this version to detect staleness. External tables do not expose such versioning
to Doris.
+
+3. **OlapScanNode requirement** — The plan normalization logic only recognizes
`OlapScanNode` as a valid scan node beneath the aggregation cache point.
External table scan nodes are not recognized.
+
+For caching needs on external tables, consider using [SQL
Cache](./sql-cache-manual.md) instead.
+
+## Configuration
+
+### Session Variables (FE)
+
+| Parameter | Description | Default |
+|-----------|-------------|---------|
+| `enable_query_cache` | Master switch to enable or disable Query Cache |
`false` |
+| `query_cache_force_refresh` | When `true`, ignores cached results and
re-executes the query; the new result is still written to cache | `false` |
+| `query_cache_entry_max_bytes` | Maximum size (in bytes) of a single cache
entry. If the aggregation result exceeds this limit, caching is abandoned for
that fragment | `5242880` (5 MB) |
+| `query_cache_entry_max_rows` | Maximum number of rows for a single cache
entry. If the aggregation result exceeds this limit, caching is abandoned for
that fragment | `500000` |
+
+### BE Configuration (be.conf)
+
+| Parameter | Description | Default |
+|-----------|-------------|---------|
+| `query_cache_size` | Total memory capacity of the Query Cache on each BE, in
MB | `512` |
+
+:::note
+The parameters `query_cache_max_size_mb` and `query_cache_elasticity_size_mb`
in `be.conf` control the older SQL Result Cache, not the pipeline-level Query
Cache described here. Do not confuse the two.
+:::
+
+## Usage Example
+
+### Enable Query Cache
+
+```sql
+SET enable_query_cache = true;
+```
+
+### Typical Scenario
+
+```sql
+-- First execution: cache miss, results are computed and cached
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+
+-- Second execution: cache hit, results are served directly from cache
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+```
+
+### Verify Cache Hit in Profile
+
+After executing a query, examine the query profile. Look for the
`CacheSourceOperator` section:
+
+- `HitCache: true` — The query served results from the cache.
+- `HitCache: false`, `InsertCache: true` — The query missed the cache but
successfully inserted results.
+- `HitCache: false`, `InsertCache: false` — The query missed the cache and the
result was too large to cache.
+
+The profile also shows `CacheTabletId` to indicate which tablets were involved.
+
+### Force Refresh
+
+```sql
+-- Force the next query to bypass cache and re-compute results
+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;
+```
+
+## Applicable Scenarios
+
+Query Cache is most effective in the following cases:
+
+- **Repeated aggregation queries**: Dashboard queries, reporting queries, or
BI tools that issue the same aggregation SQL repeatedly.
+- **T+1 reporting**: Data is loaded once daily; subsequent queries on the same
day hit the cache.
+- **Partition-based queries with overlapping ranges**: Queries on overlapping
date ranges can partially share cache entries at the partition/tablet level.
+
+Query Cache is **not** suitable for:
+
+- **Non-aggregation queries**: Plain SELECT scans, JOINs, SORT, WINDOW
functions.
+- **External tables**: Hive, JDBC, Iceberg, Hudi, Paimon, etc.
+- **Frequently updated tables**: High ingestion rates cause tablet versions to
change rapidly, reducing cache hit rates.
+- **Queries with non-deterministic functions**: `now()`, `rand()`, `uuid()`,
and UDFs disable caching.
+- **Queries that depend on runtime filters**: Joins that produce runtime
filters for the scan fragment disable caching on that fragment.
+
+## Notes
+
+- **Cache is not persistent**: Query Cache resides in BE memory and is cleared
on BE restart.
+- **Memory consumption**: Cached blocks consume BE memory. Monitor usage and
adjust `query_cache_size` as needed.
+- **LRU-K admission**: When the cache is full, a new entry must be accessed at
least twice to be admitted (LRU-K with K=2), which prevents low-frequency
queries from polluting the cache.
+
+## Summary
+
+Query Cache is a pipeline-level optimization mechanism in Doris that caches
intermediate aggregation results per tablet. Its key characteristics:
+
+- Applies **only to aggregation queries** on **internal OLAP tables**
+- Uses tablet version for automatic cache invalidation
+- Intelligently separates partition predicates from the cache digest, enabling
cache sharing across queries with overlapping partition ranges
+- Provides per-entry size and row limits to prevent oversized results from
consuming cache memory
+- Uses LRU-K eviction to maintain a high-quality cache
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/query-acceleration/query-cache.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/query-acceleration/query-cache.md
new file mode 100644
index 00000000000..25c3aa9a805
--- /dev/null
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/query-acceleration/query-cache.md
@@ -0,0 +1,238 @@
+---
+{
+ "title": "Query Cache",
+ "language": "zh-CN"
+}
+---
+
+# Query Cache(查询缓存)
+
+## 概述
+
+在分析型查询场景中,同一个聚合查询经常会被重复执行,而底层数据并未发生变化。例如:
+
+```sql
+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 和 I/O 资源。
+
+为了解决这一问题,Apache Doris 提供了 **Query Cache**
机制。它缓存流水线执行引擎中产生的中间聚合结果,当后续查询具有相同的执行上下文时,直接返回缓存的结果,从而大幅降低查询延迟。
+
+:::caution 重要限制
+- Query Cache **仅适用于**内部 OLAP 表上的**聚合查询**。非聚合查询(普通扫描、JOIN、排序等)不会使用 Query Cache。
+- Query Cache **不支持外部表**(Hive、JDBC、Iceberg、Hudi、Paimon 等)。
+:::
+
+## 工作原理
+
+### 支持的查询模式
+
+Query Cache 专为聚合查询设计。具体来说,只有执行计划树(Plan Tree)匹配以下模式的 Fragment 才有资格使用缓存:
+
+- `AggregationNode → OlapScanNode`(直接在扫描上进行的单阶段聚合)
+- `AggregationNode → AggregationNode → OlapScanNode`(在扫描上进行的两阶段聚合)
+
+聚合节点和扫描节点之间允许存在 `FilterNode` 和 `ProjectNode` 等中间节点。但是,缓存子树中**不能**包含
`JoinNode`、`SortNode`、`UnionNode`、`WindowNode` 或 `ExchangeNode`。
+
+### 缓存键
+
+缓存键由三部分组成:
+
+1. **SQL 摘要** — 基于归一化后的执行计划树(聚合函数、分组表达式、非分区过滤谓词、投影列以及影响结果的 Session 变量)计算得到的
SHA-256 哈希值。归一化过程会为所有内部标识符分配规范化的 ID,因此两个语义相同的查询即使内部计划节点或 Slot ID 不同,也会产生相同的摘要。
+
+2. **Tablet ID 列表** — 分配给当前 Pipeline 实例的排序后的 Tablet ID 列表。
+
+3. **Tablet 范围** — 每个 Tablet 的有效扫描范围,由分区谓词推导而来(详见[分区与过滤行为](#分区与过滤行为))。
+
+### 缓存失效
+
+缓存条目在以下任何情况发生时失效:
+
+- **数据变更**:INSERT、DELETE、UPDATE 或 Compaction 操作导致 Tablet 版本号递增。后续查询时系统会将
Tablet 的当前版本与缓存版本进行比较,版本不匹配即缓存未命中。
+- **Schema 变更**: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 分区**的表,分区谓词会被特殊处理:
+
+- 分区谓词被**从摘要中提取出来**。取而代之的是,系统会计算谓词范围与每个分区实际范围边界的交集,并将其作为 Tablet 范围字符串附加到缓存键中。
+- 这意味着两个仅在分区过滤范围上有差异的查询,可以在共同的 Tablet 上共享缓存条目。
+
+**示例:**
+
+假设表 `orders` 按 `dt` 列进行每日分区:
+
+```sql
+-- 查询 A
+SELECT region, SUM(revenue) FROM orders
+WHERE dt >= '2024-01-01' AND dt < '2024-01-03' GROUP BY region;
+
+-- 查询 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` 的 Tablet。
+- 查询 B 扫描分区 `2024-01-02` 和 `2024-01-03` 的 Tablet。
+- 分区 `2024-01-02` 的 Tablet 具有相同的摘要和相同的 Tablet 范围,因此**查询 B 可以复用查询 A 在
`2024-01-02` 分区上的缓存**,只需要重新计算 `2024-01-03` 分区的数据。
+
+对于**多列 RANGE 分区**、**LIST
分区**或**未分区**的表,分区谓词无法被提取,会被直接包含在摘要中。在这种情况下,即使分区谓词只有微小差异,也会产生不同的摘要导致缓存未命中。
+
+### 非分区过滤表达式
+
+非分区过滤表达式(例如 `WHERE status =
'active'`)会被包含在归一化的执行计划摘要中。只有当两个查询的非分区过滤表达式在归一化后语义完全相同时,它们才能共享缓存条目。
+
+- `WHERE status = 'active'` 和 `WHERE status = 'active'` — 相同摘要,缓存命中。
+- `WHERE status = 'active'` 和 `WHERE status = 'inactive'` — 不同摘要,缓存未命中。
+- `WHERE status = 'active' AND region = 'ASIA'` 和 `WHERE region = 'ASIA' AND
status = 'active'` — 归一化过程会对条件进行排序,因此它们会产生相同的摘要,可以命中缓存。
+
+### Session 变量
+
+影响查询结果的 Session 变量(如 `time_zone`、`sql_mode`、`sql_select_limit`
等)会被包含在摘要中。在两次查询之间更改这些变量中的任何一个,都会产生不同的缓存键,导致缓存未命中。
+
+### 导致 Query Cache 被禁用的条件
+
+以下条件会导致规划器对某个 Fragment 完全跳过 Query Cache:
+
+| 条件 | 原因 |
+|------|------|
+| Fragment 是 Runtime Filter 的目标 | Runtime Filter 的值是动态的,在规划时未知;缓存会产生错误结果 |
+| 包含非确定性表达式(`rand()`、`now()`、`uuid()`、UDF 等) | 即使输入相同,结果也会因执行次数不同而变化 |
+| 缓存子树中包含 JOIN、SORT、UNION 或 WINDOW 节点 | 仅支持聚合-扫描模式 |
+| 扫描节点不是 `OlapScanNode`(例如外部表扫描) | 缓存依赖于 Tablet ID 和版本,外部表不存在这些概念 |
+
+## Query Cache 不支持外部表的原因
+
+Query Cache 依赖于内部 OLAP 表的三个特有属性:
+
+1. **基于 Tablet 的数据组织** — 缓存键包含 Tablet ID 和每个 Tablet
的扫描范围。外部表将数据存储在外部系统(HDFS、S3、JDBC 等)中,没有 Tablet 概念。
+
+2. **基于版本的失效机制** — 每个内部 Tablet
都有一个单调递增的版本号,数据修改时版本号会递增。缓存使用此版本号来检测数据是否过期。外部表不向 Doris 暴露这种版本机制。
+
+3. **OlapScanNode 要求** — 执行计划归一化逻辑只识别 `OlapScanNode`
作为聚合缓存点下方的有效扫描节点,不识别外部表的扫描节点。
+
+对于外部表的缓存需求,请考虑使用 [SQL Cache](./sql-cache-manual.md)。
+
+## 配置参数
+
+### Session 变量(FE)
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `enable_query_cache` | 启用或禁用 Query Cache 的总开关 | `false` |
+| `query_cache_force_refresh` | 设为 `true` 时,忽略缓存结果并重新执行查询;新结果仍会写入缓存 | `false` |
+| `query_cache_entry_max_bytes` | 单个缓存条目的最大字节数。如果聚合结果超过此限制,该 Fragment
的结果将不会被缓存 | `5242880`(5 MB) |
+| `query_cache_entry_max_rows` | 单个缓存条目的最大行数。如果聚合结果超过此限制,该 Fragment 的结果将不会被缓存
| `500000` |
+
+### BE 配置(be.conf)
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `query_cache_size` | 每个 BE 上 Query Cache 的总内存容量,单位为 MB | `512` |
+
+:::note
+`be.conf` 中的参数 `query_cache_max_size_mb` 和 `query_cache_elasticity_size_mb`
控制的是旧版 SQL Result Cache,而非本文描述的流水线级别 Query Cache。请勿混淆。
+:::
+
+## 使用示例
+
+### 开启 Query Cache
+
+```sql
+SET enable_query_cache = true;
+```
+
+### 典型场景
+
+```sql
+-- 第一次执行:缓存未命中,计算结果并写入缓存
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+
+-- 第二次执行:缓存命中,直接从缓存返回结果
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+```
+
+### 通过 Profile 验证缓存命中
+
+执行查询后,检查查询 Profile。查找 `CacheSourceOperator` 部分:
+
+- `HitCache: true` — 查询从缓存中获取了结果。
+- `HitCache: false`,`InsertCache: true` — 查询未命中缓存,但成功将结果写入了缓存。
+- `HitCache: false`,`InsertCache: false` — 查询未命中缓存,且结果太大无法缓存。
+
+Profile 中还会显示 `CacheTabletId`,指示涉及的 Tablet。
+
+### 强制刷新
+
+```sql
+-- 强制下一次查询跳过缓存并重新计算结果
+SET query_cache_force_refresh = true;
+
+SELECT region, SUM(revenue) FROM orders WHERE dt = '2024-01-15' GROUP BY
region;
+
+-- 重置
+SET query_cache_force_refresh = false;
+```
+
+## 适用场景
+
+Query Cache 在以下场景中最为有效:
+
+- **重复的聚合查询**:仪表板查询、报表查询,或 BI 工具反复发出相同聚合 SQL 的场景。
+- **T+1 报表**:数据每天加载一次,当天的后续查询可以命中缓存。
+- **分区范围重叠的查询**:查询覆盖重叠日期范围时,可以在分区/Tablet 级别部分共享缓存条目。
+
+Query Cache **不适用于**以下场景:
+
+- **非聚合查询**:普通 SELECT 扫描、JOIN、排序、窗口函数等。
+- **外部表**:Hive、JDBC、Iceberg、Hudi、Paimon 等。
+- **频繁更新的表**:高频数据写入导致 Tablet 版本快速变化,降低缓存命中率。
+- **包含非确定性函数的查询**:`now()`、`rand()`、`uuid()` 和 UDF 会导致缓存被禁用。
+- **依赖 Runtime Filter 的查询**:产生 Runtime Filter 的 JOIN 查询会导致扫描 Fragment 上的缓存被禁用。
+
+## 注意事项
+
+- **缓存非持久化**:Query Cache 驻留在 BE 内存中,BE 重启后缓存会被清空。
+- **内存消耗**:缓存的数据块会占用 BE 内存。请监控内存使用情况,并根据需要调整 `query_cache_size`。
+- **LRU-K 准入机制**:当缓存已满时,新条目必须至少被访问两次才能被准入(LRU-K,K=2),这可以防止低频查询污染缓存。
+
+## 总结
+
+Query Cache 是 Doris 中一种流水线级别的优化机制,按 Tablet 粒度缓存中间聚合结果。其主要特点:
+
+- **仅适用于**内部 OLAP 表上的**聚合查询**
+- 基于 Tablet 版本自动进行缓存失效
+- 智能地将分区谓词从缓存摘要中分离,使具有重叠分区范围的查询能够共享缓存
+- 提供单条目大小和行数限制,防止过大的结果消耗缓存内存
+- 使用 LRU-K 淘汰策略维护高质量的缓存
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-4.x/query-acceleration/query-cache.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-4.x/query-acceleration/query-cache.md
new file mode 100644
index 00000000000..25c3aa9a805
--- /dev/null
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-4.x/query-acceleration/query-cache.md
@@ -0,0 +1,238 @@
+---
+{
+ "title": "Query Cache",
+ "language": "zh-CN"
+}
+---
+
+# Query Cache(查询缓存)
+
+## 概述
+
+在分析型查询场景中,同一个聚合查询经常会被重复执行,而底层数据并未发生变化。例如:
+
+```sql
+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 和 I/O 资源。
+
+为了解决这一问题,Apache Doris 提供了 **Query Cache**
机制。它缓存流水线执行引擎中产生的中间聚合结果,当后续查询具有相同的执行上下文时,直接返回缓存的结果,从而大幅降低查询延迟。
+
+:::caution 重要限制
+- Query Cache **仅适用于**内部 OLAP 表上的**聚合查询**。非聚合查询(普通扫描、JOIN、排序等)不会使用 Query Cache。
+- Query Cache **不支持外部表**(Hive、JDBC、Iceberg、Hudi、Paimon 等)。
+:::
+
+## 工作原理
+
+### 支持的查询模式
+
+Query Cache 专为聚合查询设计。具体来说,只有执行计划树(Plan Tree)匹配以下模式的 Fragment 才有资格使用缓存:
+
+- `AggregationNode → OlapScanNode`(直接在扫描上进行的单阶段聚合)
+- `AggregationNode → AggregationNode → OlapScanNode`(在扫描上进行的两阶段聚合)
+
+聚合节点和扫描节点之间允许存在 `FilterNode` 和 `ProjectNode` 等中间节点。但是,缓存子树中**不能**包含
`JoinNode`、`SortNode`、`UnionNode`、`WindowNode` 或 `ExchangeNode`。
+
+### 缓存键
+
+缓存键由三部分组成:
+
+1. **SQL 摘要** — 基于归一化后的执行计划树(聚合函数、分组表达式、非分区过滤谓词、投影列以及影响结果的 Session 变量)计算得到的
SHA-256 哈希值。归一化过程会为所有内部标识符分配规范化的 ID,因此两个语义相同的查询即使内部计划节点或 Slot ID 不同,也会产生相同的摘要。
+
+2. **Tablet ID 列表** — 分配给当前 Pipeline 实例的排序后的 Tablet ID 列表。
+
+3. **Tablet 范围** — 每个 Tablet 的有效扫描范围,由分区谓词推导而来(详见[分区与过滤行为](#分区与过滤行为))。
+
+### 缓存失效
+
+缓存条目在以下任何情况发生时失效:
+
+- **数据变更**:INSERT、DELETE、UPDATE 或 Compaction 操作导致 Tablet 版本号递增。后续查询时系统会将
Tablet 的当前版本与缓存版本进行比较,版本不匹配即缓存未命中。
+- **Schema 变更**: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 分区**的表,分区谓词会被特殊处理:
+
+- 分区谓词被**从摘要中提取出来**。取而代之的是,系统会计算谓词范围与每个分区实际范围边界的交集,并将其作为 Tablet 范围字符串附加到缓存键中。
+- 这意味着两个仅在分区过滤范围上有差异的查询,可以在共同的 Tablet 上共享缓存条目。
+
+**示例:**
+
+假设表 `orders` 按 `dt` 列进行每日分区:
+
+```sql
+-- 查询 A
+SELECT region, SUM(revenue) FROM orders
+WHERE dt >= '2024-01-01' AND dt < '2024-01-03' GROUP BY region;
+
+-- 查询 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` 的 Tablet。
+- 查询 B 扫描分区 `2024-01-02` 和 `2024-01-03` 的 Tablet。
+- 分区 `2024-01-02` 的 Tablet 具有相同的摘要和相同的 Tablet 范围,因此**查询 B 可以复用查询 A 在
`2024-01-02` 分区上的缓存**,只需要重新计算 `2024-01-03` 分区的数据。
+
+对于**多列 RANGE 分区**、**LIST
分区**或**未分区**的表,分区谓词无法被提取,会被直接包含在摘要中。在这种情况下,即使分区谓词只有微小差异,也会产生不同的摘要导致缓存未命中。
+
+### 非分区过滤表达式
+
+非分区过滤表达式(例如 `WHERE status =
'active'`)会被包含在归一化的执行计划摘要中。只有当两个查询的非分区过滤表达式在归一化后语义完全相同时,它们才能共享缓存条目。
+
+- `WHERE status = 'active'` 和 `WHERE status = 'active'` — 相同摘要,缓存命中。
+- `WHERE status = 'active'` 和 `WHERE status = 'inactive'` — 不同摘要,缓存未命中。
+- `WHERE status = 'active' AND region = 'ASIA'` 和 `WHERE region = 'ASIA' AND
status = 'active'` — 归一化过程会对条件进行排序,因此它们会产生相同的摘要,可以命中缓存。
+
+### Session 变量
+
+影响查询结果的 Session 变量(如 `time_zone`、`sql_mode`、`sql_select_limit`
等)会被包含在摘要中。在两次查询之间更改这些变量中的任何一个,都会产生不同的缓存键,导致缓存未命中。
+
+### 导致 Query Cache 被禁用的条件
+
+以下条件会导致规划器对某个 Fragment 完全跳过 Query Cache:
+
+| 条件 | 原因 |
+|------|------|
+| Fragment 是 Runtime Filter 的目标 | Runtime Filter 的值是动态的,在规划时未知;缓存会产生错误结果 |
+| 包含非确定性表达式(`rand()`、`now()`、`uuid()`、UDF 等) | 即使输入相同,结果也会因执行次数不同而变化 |
+| 缓存子树中包含 JOIN、SORT、UNION 或 WINDOW 节点 | 仅支持聚合-扫描模式 |
+| 扫描节点不是 `OlapScanNode`(例如外部表扫描) | 缓存依赖于 Tablet ID 和版本,外部表不存在这些概念 |
+
+## Query Cache 不支持外部表的原因
+
+Query Cache 依赖于内部 OLAP 表的三个特有属性:
+
+1. **基于 Tablet 的数据组织** — 缓存键包含 Tablet ID 和每个 Tablet
的扫描范围。外部表将数据存储在外部系统(HDFS、S3、JDBC 等)中,没有 Tablet 概念。
+
+2. **基于版本的失效机制** — 每个内部 Tablet
都有一个单调递增的版本号,数据修改时版本号会递增。缓存使用此版本号来检测数据是否过期。外部表不向 Doris 暴露这种版本机制。
+
+3. **OlapScanNode 要求** — 执行计划归一化逻辑只识别 `OlapScanNode`
作为聚合缓存点下方的有效扫描节点,不识别外部表的扫描节点。
+
+对于外部表的缓存需求,请考虑使用 [SQL Cache](./sql-cache-manual.md)。
+
+## 配置参数
+
+### Session 变量(FE)
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `enable_query_cache` | 启用或禁用 Query Cache 的总开关 | `false` |
+| `query_cache_force_refresh` | 设为 `true` 时,忽略缓存结果并重新执行查询;新结果仍会写入缓存 | `false` |
+| `query_cache_entry_max_bytes` | 单个缓存条目的最大字节数。如果聚合结果超过此限制,该 Fragment
的结果将不会被缓存 | `5242880`(5 MB) |
+| `query_cache_entry_max_rows` | 单个缓存条目的最大行数。如果聚合结果超过此限制,该 Fragment 的结果将不会被缓存
| `500000` |
+
+### BE 配置(be.conf)
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `query_cache_size` | 每个 BE 上 Query Cache 的总内存容量,单位为 MB | `512` |
+
+:::note
+`be.conf` 中的参数 `query_cache_max_size_mb` 和 `query_cache_elasticity_size_mb`
控制的是旧版 SQL Result Cache,而非本文描述的流水线级别 Query Cache。请勿混淆。
+:::
+
+## 使用示例
+
+### 开启 Query Cache
+
+```sql
+SET enable_query_cache = true;
+```
+
+### 典型场景
+
+```sql
+-- 第一次执行:缓存未命中,计算结果并写入缓存
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+
+-- 第二次执行:缓存命中,直接从缓存返回结果
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+```
+
+### 通过 Profile 验证缓存命中
+
+执行查询后,检查查询 Profile。查找 `CacheSourceOperator` 部分:
+
+- `HitCache: true` — 查询从缓存中获取了结果。
+- `HitCache: false`,`InsertCache: true` — 查询未命中缓存,但成功将结果写入了缓存。
+- `HitCache: false`,`InsertCache: false` — 查询未命中缓存,且结果太大无法缓存。
+
+Profile 中还会显示 `CacheTabletId`,指示涉及的 Tablet。
+
+### 强制刷新
+
+```sql
+-- 强制下一次查询跳过缓存并重新计算结果
+SET query_cache_force_refresh = true;
+
+SELECT region, SUM(revenue) FROM orders WHERE dt = '2024-01-15' GROUP BY
region;
+
+-- 重置
+SET query_cache_force_refresh = false;
+```
+
+## 适用场景
+
+Query Cache 在以下场景中最为有效:
+
+- **重复的聚合查询**:仪表板查询、报表查询,或 BI 工具反复发出相同聚合 SQL 的场景。
+- **T+1 报表**:数据每天加载一次,当天的后续查询可以命中缓存。
+- **分区范围重叠的查询**:查询覆盖重叠日期范围时,可以在分区/Tablet 级别部分共享缓存条目。
+
+Query Cache **不适用于**以下场景:
+
+- **非聚合查询**:普通 SELECT 扫描、JOIN、排序、窗口函数等。
+- **外部表**:Hive、JDBC、Iceberg、Hudi、Paimon 等。
+- **频繁更新的表**:高频数据写入导致 Tablet 版本快速变化,降低缓存命中率。
+- **包含非确定性函数的查询**:`now()`、`rand()`、`uuid()` 和 UDF 会导致缓存被禁用。
+- **依赖 Runtime Filter 的查询**:产生 Runtime Filter 的 JOIN 查询会导致扫描 Fragment 上的缓存被禁用。
+
+## 注意事项
+
+- **缓存非持久化**:Query Cache 驻留在 BE 内存中,BE 重启后缓存会被清空。
+- **内存消耗**:缓存的数据块会占用 BE 内存。请监控内存使用情况,并根据需要调整 `query_cache_size`。
+- **LRU-K 准入机制**:当缓存已满时,新条目必须至少被访问两次才能被准入(LRU-K,K=2),这可以防止低频查询污染缓存。
+
+## 总结
+
+Query Cache 是 Doris 中一种流水线级别的优化机制,按 Tablet 粒度缓存中间聚合结果。其主要特点:
+
+- **仅适用于**内部 OLAP 表上的**聚合查询**
+- 基于 Tablet 版本自动进行缓存失效
+- 智能地将分区谓词从缓存摘要中分离,使具有重叠分区范围的查询能够共享缓存
+- 提供单条目大小和行数限制,防止过大的结果消耗缓存内存
+- 使用 LRU-K 淘汰策略维护高质量的缓存
diff --git a/versioned_docs/version-4.x/query-acceleration/query-cache.md
b/versioned_docs/version-4.x/query-acceleration/query-cache.md
new file mode 100644
index 00000000000..baaea5d540d
--- /dev/null
+++ b/versioned_docs/version-4.x/query-acceleration/query-cache.md
@@ -0,0 +1,231 @@
+# Query Cache
+
+## Introduction
+
+In analytical workloads, the same aggregation query is often executed
repeatedly on data that has not changed, for example:
+
+```sql
+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;
+```
+
+Each execution re-scans identical tablets and re-computes identical
aggregation results, wasting CPU and I/O resources.
+
+To address this, Apache Doris provides a **Query Cache** mechanism. It caches
the intermediate aggregation results produced inside the pipeline execution
engine and serves them directly to subsequent queries that share the same
execution context, significantly reducing query latency.
+
+:::caution Important Limitations
+- Query Cache applies **only to aggregation queries** on **internal OLAP
tables**. Non-aggregation queries (plain scans, joins, sorts, etc.) do not use
Query Cache.
+- Query Cache **does not work on external tables** (Hive, JDBC, Iceberg, Hudi,
Paimon, etc.).
+:::
+
+## Working Principle
+
+### Applicable Query Patterns
+
+Query Cache is designed for aggregation queries. Specifically, only fragments
whose plan tree matches one of the following patterns are eligible:
+
+- `AggregationNode → OlapScanNode` (single-phase aggregation directly on a
scan)
+- `AggregationNode → AggregationNode → OlapScanNode` (two-phase aggregation on
a scan)
+
+Intermediate nodes such as `FilterNode` and `ProjectNode` are allowed between
the aggregation and scan nodes. However, the plan tree must **not** contain
`JoinNode`, `SortNode`, `UnionNode`, `WindowNode`, or `ExchangeNode` within the
cache-eligible subtree.
+
+### Cache Key
+
+The cache key is composed of three parts:
+
+1. **SQL Digest** — A SHA-256 hash computed from the normalized plan tree
(aggregation functions, grouping expressions, non-partition filter predicates,
projections, and result-affecting session variables). The normalization process
assigns canonical IDs to all internal identifiers, so two semantically
identical queries produce the same digest even if they have different internal
plan node / slot IDs.
+
+2. **Tablet IDs** — The sorted list of tablet IDs assigned to the current
pipeline instance.
+
+3. **Tablet Range** — The effective scan range for each tablet, derived from
partition predicates (see [Partition and Filter
Behavior](#partition-and-filter-behavior)).
+
+### Cache Invalidation
+
+A cache entry becomes invalid when any of the following occurs:
+
+- **Data changes**: INSERT, DELETE, UPDATE, or Compaction causes the tablet
version to increment. On the next query, the tablet version is compared against
the cached version; a mismatch means a cache miss.
+- **Schema changes**: ALTER TABLE operations change the table structure, which
changes the plan and thus the digest.
+- **LRU eviction**: When the cache memory exceeds the configured limit, least
recently used entries are evicted. The cache uses an LRU-K (K=2) algorithm — a
new entry must be accessed at least twice before it is admitted into the cache
when the cache is full.
+- **Stale sweep**: Entries older than 24 hours are automatically removed by
periodic pruning.
+- **Force refresh**: When `query_cache_force_refresh = true`, cached results
are ignored and the query re-executes.
+
+### Execution Flow
+
+**First execution (cache miss)**:
+
+1. The scan operator reads data from tablets normally.
+2. The aggregation operator computes results.
+3. The results are sent to the downstream consumer and simultaneously
accumulated for cache insertion.
+4. On completion, if the accumulated result does not exceed the per-entry
size/row limits, the result is inserted into the cache.
+
+**Subsequent execution (cache hit)**:
+
+1. The scan operator detects a cache hit and skips adding any scan ranges — no
tablet data is read.
+2. The aggregation operator produces nothing (no input data).
+3. The cache source operator serves the cached blocks directly.
+4. If the column order differs from the cached entry (e.g., `SELECT a, b` vs.
`SELECT b, a` with the same digest), columns are reordered automatically.
+
+## Partition and Filter Behavior
+
+Understanding how partition predicates and filter expressions interact with
Query Cache is essential for achieving good hit rates.
+
+### Partition Predicates
+
+For tables with **single-column RANGE partitioning**, partition predicates
receive special treatment:
+
+- The partition predicate is **extracted from the digest**. Instead, the
effective range (the intersection of the predicate range with each partition's
actual range boundary) is computed and appended to the cache key as the tablet
range string.
+- This means two queries that differ **only** in their partition filter range
can share cache entries for the tablets they have in common.
+
+**Example:**
+
+Consider a table `orders` partitioned by `dt` with daily partitions:
+
+```sql
+-- 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;
+```
+
+- Query A scans tablets from partitions `2024-01-01` and `2024-01-02`.
+- Query B scans tablets from partitions `2024-01-02` and `2024-01-03`.
+- The tablets for partition `2024-01-02` have the same digest and the same
tablet range, so **Query B can reuse Query A's cache for the `2024-01-02`
partition**. Only partition `2024-01-03` needs to be computed fresh.
+
+For **multi-column RANGE partitioning**, **LIST partitioning**, or
**UNPARTITIONED** tables, partition predicates cannot be extracted and are
included directly in the digest. In this case, even minor differences in
partition predicates produce different digests and cache misses.
+
+### Non-Partition Filter Expressions
+
+Non-partition filter expressions (e.g., `WHERE status = 'active'`) are
included in the normalized plan digest. Two queries can share a cache entry
only when their non-partition filter expressions are semantically identical
after normalization.
+
+- `WHERE status = 'active'` and `WHERE status = 'active'` — same digest, cache
hit.
+- `WHERE status = 'active'` and `WHERE status = 'inactive'` — different
digest, cache miss.
+- `WHERE status = 'active' AND region = 'ASIA'` and `WHERE region = 'ASIA' AND
status = 'active'` — the normalization process sorts conjuncts, so they produce
the same digest and can hit the cache.
+
+### Session Variables
+
+Session variables that affect query results (such as `time_zone`, `sql_mode`,
`sql_select_limit`, etc.) are included in the digest. Changing any of these
variables between queries produces a different cache key and causes a cache
miss.
+
+### Conditions That Disable Query Cache
+
+The following conditions cause the planner to skip Query Cache entirely for a
fragment:
+
+| Condition | Reason |
+|-----------|--------|
+| Fragment is a target of runtime filters | Runtime filter values are dynamic
and unknown at plan time; caching would produce incorrect results |
+| Non-deterministic expressions (`rand()`, `now()`, `uuid()`, UDFs, etc.) |
Results vary across executions even with identical input |
+| Plan contains JOIN, SORT, UNION, or WINDOW nodes in the cache subtree | Only
aggregation-over-scan patterns are supported |
+| Scan node is not `OlapScanNode` (e.g., external table scan) | Cache depends
on tablet IDs and versions, which do not exist for external tables |
+
+## Why Query Cache Does Not Work on External Tables
+
+Query Cache relies on three properties unique to internal OLAP tables:
+
+1. **Tablet-based data organization** — The cache key includes tablet IDs and
per-tablet scan ranges. External tables store data in external systems (HDFS,
S3, JDBC, etc.) and have no tablet concept.
+
+2. **Version-based invalidation** — Each internal tablet has a monotonically
increasing version number that changes on data modification. The cache uses
this version to detect staleness. External tables do not expose such versioning
to Doris.
+
+3. **OlapScanNode requirement** — The plan normalization logic only recognizes
`OlapScanNode` as a valid scan node beneath the aggregation cache point.
External table scan nodes are not recognized.
+
+For caching needs on external tables, consider using [SQL
Cache](./sql-cache-manual.md) instead.
+
+## Configuration
+
+### Session Variables (FE)
+
+| Parameter | Description | Default |
+|-----------|-------------|---------|
+| `enable_query_cache` | Master switch to enable or disable Query Cache |
`false` |
+| `query_cache_force_refresh` | When `true`, ignores cached results and
re-executes the query; the new result is still written to cache | `false` |
+| `query_cache_entry_max_bytes` | Maximum size (in bytes) of a single cache
entry. If the aggregation result exceeds this limit, caching is abandoned for
that fragment | `5242880` (5 MB) |
+| `query_cache_entry_max_rows` | Maximum number of rows for a single cache
entry. If the aggregation result exceeds this limit, caching is abandoned for
that fragment | `500000` |
+
+### BE Configuration (be.conf)
+
+| Parameter | Description | Default |
+|-----------|-------------|---------|
+| `query_cache_size` | Total memory capacity of the Query Cache on each BE, in
MB | `512` |
+
+:::note
+The parameters `query_cache_max_size_mb` and `query_cache_elasticity_size_mb`
in `be.conf` control the older SQL Result Cache, not the pipeline-level Query
Cache described here. Do not confuse the two.
+:::
+
+## Usage Example
+
+### Enable Query Cache
+
+```sql
+SET enable_query_cache = true;
+```
+
+### Typical Scenario
+
+```sql
+-- First execution: cache miss, results are computed and cached
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+
+-- Second execution: cache hit, results are served directly from cache
+SELECT region, SUM(revenue), COUNT(*)
+FROM orders
+WHERE dt = '2024-01-15' AND status = 'completed'
+GROUP BY region;
+```
+
+### Verify Cache Hit in Profile
+
+After executing a query, examine the query profile. Look for the
`CacheSourceOperator` section:
+
+- `HitCache: true` — The query served results from the cache.
+- `HitCache: false`, `InsertCache: true` — The query missed the cache but
successfully inserted results.
+- `HitCache: false`, `InsertCache: false` — The query missed the cache and the
result was too large to cache.
+
+The profile also shows `CacheTabletId` to indicate which tablets were involved.
+
+### Force Refresh
+
+```sql
+-- Force the next query to bypass cache and re-compute results
+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;
+```
+
+## Applicable Scenarios
+
+Query Cache is most effective in the following cases:
+
+- **Repeated aggregation queries**: Dashboard queries, reporting queries, or
BI tools that issue the same aggregation SQL repeatedly.
+- **T+1 reporting**: Data is loaded once daily; subsequent queries on the same
day hit the cache.
+- **Partition-based queries with overlapping ranges**: Queries on overlapping
date ranges can partially share cache entries at the partition/tablet level.
+
+Query Cache is **not** suitable for:
+
+- **Non-aggregation queries**: Plain SELECT scans, JOINs, SORT, WINDOW
functions.
+- **External tables**: Hive, JDBC, Iceberg, Hudi, Paimon, etc.
+- **Frequently updated tables**: High ingestion rates cause tablet versions to
change rapidly, reducing cache hit rates.
+- **Queries with non-deterministic functions**: `now()`, `rand()`, `uuid()`,
and UDFs disable caching.
+- **Queries that depend on runtime filters**: Joins that produce runtime
filters for the scan fragment disable caching on that fragment.
+
+## Notes
+
+- **Cache is not persistent**: Query Cache resides in BE memory and is cleared
on BE restart.
+- **Memory consumption**: Cached blocks consume BE memory. Monitor usage and
adjust `query_cache_size` as needed.
+- **LRU-K admission**: When the cache is full, a new entry must be accessed at
least twice to be admitted (LRU-K with K=2), which prevents low-frequency
queries from polluting the cache.
+
+## Summary
+
+Query Cache is a pipeline-level optimization mechanism in Doris that caches
intermediate aggregation results per tablet. Its key characteristics:
+
+- Applies **only to aggregation queries** on **internal OLAP tables**
+- Uses tablet version for automatic cache invalidation
+- Intelligently separates partition predicates from the cache digest, enabling
cache sharing across queries with overlapping partition ranges
+- Provides per-entry size and row limits to prevent oversized results from
consuming cache memory
+- Uses LRU-K eviction to maintain a high-quality cache
diff --git a/versioned_sidebars/version-4.x-sidebars.json
b/versioned_sidebars/version-4.x-sidebars.json
index f013b74f227..ce760fcff35 100644
--- a/versioned_sidebars/version-4.x-sidebars.json
+++ b/versioned_sidebars/version-4.x-sidebars.json
@@ -632,6 +632,7 @@
"label": "Caching",
"items": [
"query-acceleration/sql-cache-manual",
+ "query-acceleration/query-cache",
"query-acceleration/condition-cache",
"query-acceleration/tuning/tuning-plan/accelerating-queries-with-sql-cache"
]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]