diff --git a/docs/.vuepress/configs/navbar/zh.ts b/docs/.vuepress/configs/navbar/zh.ts index 2480e2a7469..44aeb0c292f 100644 --- a/docs/.vuepress/configs/navbar/zh.ts +++ b/docs/.vuepress/configs/navbar/zh.ts @@ -67,32 +67,20 @@ export const zh: NavbarConfig = [ ], }, { - text: "内核功能增强", + text: "内核增强功能", children: [ { text: "文档入口", - link: "/zh/features/v11/", + link: "/zh/features/", }, { text: "PolarDB for PostgreSQL 11", link: "/zh/features/v11/", children: [ - { - text: "高性能", - link: "/zh/features/v11/performance/", - }, - { - text: "高可用", - link: "/zh/features/v11/availability/", - }, - { - text: "安全", - link: "/zh/features/v11/security/", - }, - { - text: "HTAP", - link: "/zh/features/v11/htap/", - }, + "/zh/features/v11/performance/", + "/zh/features/v11/availability/", + "/zh/features/v11/security/", + "/zh/features/v11/epq/", ], }, ], diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts index e38f609e81a..dc53cfdbf72 100644 --- a/docs/.vuepress/configs/sidebar/zh.ts +++ b/docs/.vuepress/configs/sidebar/zh.ts @@ -76,8 +76,8 @@ export const zh: SidebarConfig = { ], "/zh/features": [ { - text: "内核功能增强", - link: "/zh/features/v11/", + text: "内核增强功能", + link: "/zh/features/", children: [ { text: "PolarDB for PostgreSQL 11", @@ -86,18 +86,41 @@ export const zh: SidebarConfig = { { text: "高性能", link: "/zh/features/v11/performance/", + children: [ + "/zh/features/v11/performance/bulk-read-and-extend.md", + "/zh/features/v11/performance/rel-size-cache.md", + "/zh/features/v11/performance/shared-server.md", + ], }, { text: "高可用", link: "/zh/features/v11/availability/", + children: [ + "/zh/features/v11/availability/avail-online-promote.md", + "/zh/features/v11/availability/avail-parallel-replay.md", + "/zh/features/v11/availability/datamax.md", + "/zh/features/v11/availability/resource-manager.md", + "/zh/features/v11/availability/flashback-table.md", + ], }, { text: "安全", link: "/zh/features/v11/security/", + children: ["/zh/features/v11/security/tde.md"], }, { - text: "HTAP", - link: "/zh/features/v11/htap/", + text: "弹性跨机并行查询(ePQ)", + link: "/zh/features/v11/epq/", + children: [ + "/zh/features/v11/epq/epq-explain-analyze.md", + "/zh/features/v11/epq/epq-node-and-dop.md", + "/zh/features/v11/epq/epq-partitioned-table.md", + "/zh/features/v11/epq/epq-create-btree-index.md", + "/zh/features/v11/epq/cluster-info.md", + "/zh/features/v11/epq/adaptive-scan.md", + "/zh/features/v11/epq/parallel-dml.md", + "/zh/features/v11/epq/epq-ctas-mtview-bulk-insert.md", + ], }, ], }, diff --git a/docs/.vuepress/styles/index.scss b/docs/.vuepress/styles/index.scss index 24830785500..299f7dc6a29 100644 --- a/docs/.vuepress/styles/index.scss +++ b/docs/.vuepress/styles/index.scss @@ -6,6 +6,8 @@ --c-brand-light: #fc5207; --c-tip: #fc5207; + + --content-width: 1020px; } html.dark { diff --git a/docs/zh/README.md b/docs/zh/README.md index 9570116d01d..87de3bec18b 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -49,12 +49,12 @@ postgres=# SELECT version();
-

内核功能增强

+

内核增强功能

diff --git a/docs/zh/features/README.md b/docs/zh/features/README.md new file mode 100644 index 00000000000..de168df84bd --- /dev/null +++ b/docs/zh/features/README.md @@ -0,0 +1,122 @@ +# 内核增强功能 + +- [PolarDB for PostgreSQL 11](./v11/README.md) + +## 功能 / 版本映射矩阵 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
功能 / 版本PostgreSQLPolarDB for PostgreSQL 11
高性能......
预读 / 预扩展/
表大小缓存/
Shared Server/
高可用......
只读节点 Online Promote/
WAL 日志并行回放/
DataMax 日志节点/
Resource Manager/
闪回表和闪回日志/
安全......
透明数据加密/
弹性跨机并行查询(ePQ)......
ePQ 执行计划查看与分析/
ePQ 计算节点范围选择与并行度控制/
ePQ 支持分区表查询/
ePQ 支持创建 B-Tree 索引并行加速/
集群拓扑视图/
自适应扫描/
并行 INSERT/
ePQ 支持创建/刷新物化视图并行加速和批量写入/
diff --git a/docs/zh/features/v11/README.md b/docs/zh/features/v11/README.md index 439ff054f0e..8ac287a04b3 100644 --- a/docs/zh/features/v11/README.md +++ b/docs/zh/features/v11/README.md @@ -1,6 +1,6 @@ -# 内核功能增强 +# 内核增强功能 - [高性能](./performance/README.md) - [高可用](./availability/README.md) - [安全](./security/README.md) -- [HTAP](./htap/README.md) +- [弹性跨机并行查询(ePQ)](./epq/README.md) diff --git a/docs/zh/features/v11/availability/README.md b/docs/zh/features/v11/availability/README.md index a9ddd5610a4..ce6875f93a7 100644 --- a/docs/zh/features/v11/availability/README.md +++ b/docs/zh/features/v11/availability/README.md @@ -1,6 +1,7 @@ # 高可用 - [只读节点 Online Promote](./avail-online-promote.md) +- [WAL 日志并行回放](./avail-parallel-replay.md) - [DataMax 日志节点](./datamax.md) - [Resource Manager](./resource-manager.md) - [闪回表和闪回日志](./flashback-table.md) diff --git a/docs/zh/features/v11/availability/avail-parallel-replay.md b/docs/zh/features/v11/availability/avail-parallel-replay.md new file mode 100644 index 00000000000..aeb8d86f1e8 --- /dev/null +++ b/docs/zh/features/v11/availability/avail-parallel-replay.md @@ -0,0 +1,146 @@ +--- +author: 学弈 +date: 2022/09/20 +minute: 30 +--- + +# WAL 日志并行回放 + + + + + +[[toc]] + +## 背景 + +在 PolarDB for PostgreSQL 的一写多读架构下,只读节点(Replica 节点)运行过程中,LogIndex 后台回放进程(LogIndex Background Worker)和会话进程(Backend)分别使用 LogIndex 数据在不同的 Buffer 上回放 WAL 日志,本质上达到了一种并行回放 WAL 日志的效果。 + +鉴于 WAL 日志回放在 PolarDB 集群的高可用中起到至关重要的作用,将并行回放 WAL 日志的思想用到常规的日志回放路径上,是一种很好的优化思路。 + +并行回放 WAL 日志至少可以在以下三个场景下发挥优势: + +1. 主库节点、只读节点以及备库节点崩溃恢复(Crash Recovery)的过程; +2. 只读节点 LogIndex BGW 进程持续回放 WAL 日志的过程; +3. 备库节点 Startup 进程持续回放 WAL 日志的过程。 + +## 术语 + +- Block:数据块 +- WAL:Write-Ahead Logging,预写日志 +- Task Node:并行执行框架中的子任务执行节点,可以接收并执行一个子任务 +- Task Tag:子任务的分类标识,同一类的子任务执行顺序有先后关系 +- Hold List:并行执行框架中,每个子进程调度执行回放子任务所使用的链表 + +## 原理 + +### 概述 + +一条 WAL 日志可能修改多个数据块 Block,因此可以使用如下定义来表示 WAL 日志的回放过程: + +- 假设第 `i` 条 WAL 日志 LSN 为 $LSN_i$,其修改了 `m` 个数据块,则定义第 `i` 条 WAL 日志修改的数据块列表 $Block_i = [Block_{i,0}, Block_{i,1}, ..., Block_{i,m}]$; +- 定义最小的回放子任务为 $Task_{i,j}={LSN_i -> Block_{i,j}}$,表示在数据块 $Block_{i,j}$ 上回放第 `i` 条 WAL 日志; +- 因此,一条修改了 `k` 个 Block 的 WAL 日志就可以表示成 `k` 个回放子任务的集合:$TASK_{i,*} = [Task_{i,0}, Task_{i,1}, ..., Task_{i,k}]$; +- 进而,多条 WAL 日志就可以表示成一系列回放子任务的集合:$TASK_{*,*} = [Task_{0,*}, Task_{1,*}, ..., Task_{N,*}]$; + +在日志回放子任务集合 $Task_{*,*}$ 中,每个子任务的执行,有时并不依赖于前序子任务的执行结果。假设回放子任务集合如下:$TASK_{*,*} = [Task_{0,*}, Task_{1,*}, Task_{2,*}]$,其中: + +- $Task_{0,*}=[Task_{0,0}, Task_{0,1}, Task_{0,2}]$ +- $Task_{1,*}=[Task_{1,0}, Task_{1,1}]$, +- $Task_{2,*}=[Task_{2,0}]$ + +并且 $Block_{0,0} = Block_{1,0}$,$Block_{0,1} = Block_{1,1}$,$Block_{0,2} = Block_{2,0}$ + +则可以并行回放的子任务集合有三个:$[Task_{0,0},Task_{1,0}]$、$[Task_{0,1},Task_{1,1}]$、$[Task_{0,2},Task_{2,0}]$ + +综上所述,在整个 WAL 日志所表示的回放子任务集合中,存在很多子任务序列可以并行执行,而且不会影响最终回放结果的一致性。PolarDB 借助这种思想,提出了一种并行任务执行框架,并成功运用到了 WAL 日志回放的过程中。 + +### 并行任务执行框架 + +将一段共享内存根据并发进程数目进行等分,每一段作为一个环形队列,分配给一个进程。通过配置参数设定每个环形队列的深度: + +![image.png](../../../imgs/pr_parallel_execute_1.png) + +- Dispatcher 进程 + - 通过将任务分发给指定的进程来控制并发调度; + - 负责将进程执行完的任务从队列中删除; +- 进程组 + - 组内每一个进程从相应的环形队列中获取需要执行的任务,根据任务的状态决定是否执行。 + +![image.png](../../../imgs/pr_parallel_execute_2.png) + +#### 任务 + +环形队列的内容由 Task Node 组成,每个 Task Node 包含五个状态:Idle、Running、Hold、Finished、Removed。 + +- `Idle`:表示该 Task Node 未分配任务; +- `Running`:表示该 Task Node 已经分配任务,正在等待进程执行,或已经在执行; +- `Hold`:表示该 Task Node 有前向依赖的任务,需要等待依赖的任务执行完再执行; +- `Finished`:表示进程组中的进程已经执行完该任务; +- `Removed`:当 Dispatcher 进程发现一个任务的状态已经为 `Finished`,那么该任务所有的前置依赖任务也都应该为 `Finished` 状态,`Removed` 状态表示 Dispatcher 进程已经将该任务以及该任务所有前置任务都从管理结构体中删除;可以通过该机制保证 Dispatcher 进程按顺序处理有依赖关系的任务执行结果。 + +![image.png](../../../imgs/pr_parallel_execute_task.png) + +上述状态机的状态转移过程中,黑色线标识的状态转移过程在 Dispatcher 进程中完成,橙色线标识的状态转移过程在并行回放进程组中完成。 + +#### Dispatcher 进程 + +Dispatcher 进程有三个关键数据结构:Task HashMap、Task Running Queue 以及 Task Idle Nodes。 + +- **Task HashMap** 负责记录 Task Tag 和相应的执行任务列表的 hash 映射关系: + - 每个任务有一个指定的 Task Tag,如果两个任务间存在依赖关系,则它们的 Task Tag 相同; + - 在分发任务时,如果一个 Task Node 存在前置依赖任务,则状态标识为 `Hold`,需等待前置任务先执行。 +- **Task Running Queue** 负责记录当前正在执行的任务; +- **Task Idel Nodes** 负责记录进程组中不同进程,当前处于 `Idle` 状态的 Task Node; + +Dispatcher 调度策略如下: + +- 如果要执行的 Task Node 有相同 Task Tag 的任务在执行,则优先将该 Task Node 分配到该 Task Tag 链表最后一个 Task Node 所在的执行进程;目的是让有依赖关系的任务尽量被同一个进程执行,减少进程间同步的开销; +- 如果期望优先分配的进程队列已满,或者没有相同的 Task Tag 在执行,则在进程组中按顺序选择一个进程,从中获取状态为 `Idle` 的 Task Node 来调度任务执行;目的是让任务尽量平均分配到不同的进程进行执行。 + +![image.png](../../../imgs/pr_parallel_execute_dispatcher.png) + +#### 进程组 + +该并行执行针对的是相同类型的任务,它们具有相同的 Task Node 数据结构;在进程组初始化时配置 `SchedContext`,指定负责执行具体任务的函数指针: + +- `TaskStartup` 表示进程执行任务前需要进行的初始化动作 +- `TaskHandler` 根据传入的 Task Node,负责执行具体的任务 +- `TaskCleanup` 表示执行进程退出前需要执行的回收动作 + +![image.png](../../../imgs/pr_parallel_execute_procs_1.png) + +进程组中的进程从环形队列中获取一个 Task Node,如果 Task Node 当前的状态是 `Hold`,则将该 Task Node 插入到 Hold List 的尾部;如果 Task Node 的状态为 Running,则调用 TaskHandler 执行;如果 TaskHandler 执行失败,则设置该 Task Node 重新执行需要等待调用的次数,默认为 3,将该 Task Node 插入到 Hold List 的头部。 + +![image.png](../../../imgs/pr_parallel_execute_procs_2.png) + +进程优先从 Hold List 头部搜索,获取可执行的 Task;如果 Task 状态为 Running,且等待调用次数为 0,则执行该 Task;如果 Task 状态为 Running,但等待调用次数大于 0,则将等待调用次数减去 1。 + +![image.png](../../../imgs/pr_parallel_execute_procs_3.png) + +### WAL 日志并行回放 + +根据 LogIndex 章节介绍,LogIndex 数据中记录了 WAL 日志和其修改的数据块之间的对应关系,而且 LogIndex 数据支持使用 LSN 进行检索,鉴于此,PolarDB 数据库在 Standby 节点持续回放 WAL 日志过程中,引入了上述并行任务执行框架,并结合 LogIndex 数据将 WAL 日志的回放任务并行化,提高了 Standby 节点数据同步的速度。 + +#### 工作流程 + +- Startup 进程:解析 WAL 日志后,仅构建 LogIndex 数据而不真正回放 WAL 日志; +- LogIndex BGW 后台回放进程:成为上述并行任务执行框架的 Dispatcher 进程,利用 LSN 来检索 LogIndex 数据,构建日志回放的子任务,并分配给并行回放进程组; +- 并行回放进程组内的进程:执行日志回放子任务,对数据块执行单个日志的回放操作; +- Backend 进程:主动读取数据块时,根据 PageTag 来检索 LogIndex 数据,获得修改该数据块的 LSN 日志链表,对数据块执行完整日志链的回放操作。 + +![image.png](../../../imgs/pr_parallel_replay_1.png) + +- Dispatcher 进程利用 LSN 来检索 LogIndex 数据,按 LogIndex 插入顺序枚举 PageTag 和对应 LSN,构建{LSN -> PageTag},组成相应的 Task Node; +- PageTag 作为 Task Node 的 Task Tag; +- 将枚举组成的 Task Node 分发给并行执行框架中进程组的子进程进行回放; + +![image.png](../../../imgs/pr_parallel_replay_2.png) + +## 使用方法 + +在 Standby 节点的 `postgresql.conf` 中添加以下参数开启功能: + +```ini:no-line-numbers +polar_enable_parallel_replay_standby_mode = ON +``` diff --git a/docs/zh/features/v11/epq/README.md b/docs/zh/features/v11/epq/README.md new file mode 100644 index 00000000000..423f73e514e --- /dev/null +++ b/docs/zh/features/v11/epq/README.md @@ -0,0 +1,10 @@ +# 弹性跨机并行查询(ePQ) + +- [ePQ 执行计划查看与分析](./epq-explain-analyze.md) +- [ePQ 计算节点范围选择与并行度控制](./epq-node-and-dop.md) +- [ePQ 支持分区表查询](./epq-partitioned-table.md) +- [ePQ 支持创建 B-Tree 索引并行加速](./epq-create-btree-index.md) +- [集群拓扑视图](./cluster-info.md) +- [自适应扫描](./adaptive-scan.md) +- [并行 INSERT](./parallel-dml.md) +- [ePQ 支持创建/刷新物化视图并行加速和批量写入](./epq-ctas-mtview-bulk-insert.md) diff --git a/docs/zh/features/v11/htap/adaptive-scan.md b/docs/zh/features/v11/epq/adaptive-scan.md similarity index 72% rename from docs/zh/features/v11/htap/adaptive-scan.md rename to docs/zh/features/v11/epq/adaptive-scan.md index 5c85396a73f..4a62f72ab02 100644 --- a/docs/zh/features/v11/htap/adaptive-scan.md +++ b/docs/zh/features/v11/epq/adaptive-scan.md @@ -14,26 +14,26 @@ minute: 25 ## 背景介绍 -PolarDB for PostgreSQL 提供了一款强大的分析型查询引擎——PX(Parallel eXecution),通过利用集群中多个节点的计算能力,来实现跨节点的并行查询功能。PX 可以支持顺序扫描、索引扫描等多种物理算子的跨节点并行化。其中,对顺序扫描算子,PX 提供了两种扫描模式,分别为 **自适应扫描模式** 与 **非自适应扫描模式**。 +PolarDB for PostgreSQL 支持 ePQ 弹性跨机并行查询特性,通过利用集群中多个节点的计算能力,来实现跨节点的并行查询功能。ePQ 可以支持顺序扫描、索引扫描等多种物理算子的跨节点并行化。其中,对顺序扫描算子,ePQ 提供了两种扫描模式,分别为 **自适应扫描模式** 与 **非自适应扫描模式**。 ## 术语 -- QC:Query Coordinator,发起 PX 并行查询的进程角色。 -- PX Worker:参与 PX 跨节点并行查询的工作进程角色。 +- QC:Query Coordinator,发起 ePQ 并行查询的进程角色。 +- PX Worker:参与 ePQ 跨节点并行查询的工作进程角色。 - Worker ID:唯一标识一个 PX Worker 的编号。 -- Disk Unit ID:PX 跨节点并行扫描的最小存储单元,默认为 4MB 大小。 +- Disk Unit ID:ePQ 跨节点并行扫描的最小存储单元,默认为 4MB 大小。 ## 功能介绍 ### 非自适应扫描 -非自适应扫描模式是 PX 顺序扫描算子(Sequential Scan)的默认扫描方式。每一个参与并行查询的 PX Worker 在执行过程中都会被分配一个唯一的 Worker ID。非自适应扫描模式将会依据 Worker ID 划分数据表在物理存储上的 Disk Unit ID,从而实现每个 PX Worker 可以均匀扫描数据表在共享存储上的存储单元,所有 PX Worker 的扫描结果最终汇总形成全量的数据。 +非自适应扫描模式是 ePQ 顺序扫描算子(Sequential Scan)的默认扫描方式。每一个参与并行查询的 PX Worker 在执行过程中都会被分配一个唯一的 Worker ID。非自适应扫描模式将会依据 Worker ID 划分数据表在物理存储上的 Disk Unit ID,从而实现每个 PX Worker 可以均匀扫描数据表在共享存储上的存储单元,所有 PX Worker 的扫描结果最终汇总形成全量的数据。 ### 自适应扫描 在非自适应扫描模式下,扫描单元会均匀划分给每个 PX Worker。当存在个别只读节点计算资源不足的情况下,可能会导致扫描过程发生计算倾斜:用户发起的单次并行查询迟迟不能完成,查询受限于计算资源不足的节点长时间不能完成扫描任务。 -PX 提供的自适应扫描模式可以解决这个问题。自适应扫描模式不再限定每个 PX Worker 扫描特定的 Disk Unit ID,而是采用 **请求-响应(Request-Response)模式**,通过 QC 进程与 PX Worker 进程之间的特定 RPC 通信机制,由 QC 进程负责告知每个 PX Worker 进程可以执行的扫描任务,从而消除计算倾斜的问题。 +ePQ 提供的自适应扫描模式可以解决这个问题。自适应扫描模式不再限定每个 PX Worker 扫描特定的 Disk Unit ID,而是采用 **请求-响应(Request-Response)模式**,通过 QC 进程与 PX Worker 进程之间的特定 RPC 通信机制,由 QC 进程负责告知每个 PX Worker 进程可以执行的扫描任务,从而消除计算倾斜的问题。 ## 功能设计 @@ -65,7 +65,7 @@ PX Worker 进程在执行顺序扫描算子时,会首先向 QC 进程发起询 #### 可变颗粒度 -为了减少请求带来的网络交互次数,PX 实现了可变的任务颗粒度。当扫描任务量剩余较多时,PX Worker 进程单次领取的扫描物理块数较多;当扫描任务量剩余较少时,PX Worker 进程单次领取的扫描物理块数相应减少。通过这种方法,可以平衡 **网络开销** 与 **负载均衡** 两者之间的关系。 +为了减少请求带来的网络交互次数,ePQ 实现了可变的任务颗粒度。当扫描任务量剩余较多时,PX Worker 进程单次领取的扫描物理块数较多;当扫描任务量剩余较少时,PX Worker 进程单次领取的扫描物理块数相应减少。通过这种方法,可以平衡 **网络开销** 与 **负载均衡** 两者之间的关系。 #### 缓存友好 @@ -105,7 +105,7 @@ INSERT 0 100 ### 非自适应扫描 -开启 PX 并行查询功能,并设置单节点并发度为 3。通过 `EXPLAIN` 可以看到执行计划来自 PX 优化器。由于参与测试的只读节点有两个,所以从执行计划中可以看到整体并发度为 6。 +开启 ePQ 并行查询功能,并设置单节点并发度为 3。通过 `EXPLAIN` 可以看到执行计划来自 PX 优化器。由于参与测试的只读节点有两个,所以从执行计划中可以看到整体并发度为 6。 ```sql postgres=# SET polar_enable_px = 1; diff --git a/docs/zh/features/v11/epq/cluster-info.md b/docs/zh/features/v11/epq/cluster-info.md new file mode 100644 index 00000000000..d36ea6c17f9 --- /dev/null +++ b/docs/zh/features/v11/epq/cluster-info.md @@ -0,0 +1,124 @@ +--- +author: 烛远 +date: 2022/09/20 +minute: 20 +--- + +# 集群拓扑视图 + + + + + +[[toc]] + +## 功能介绍 + +PolarDB for PostgreSQL 的 ePQ 弹性跨机并行查询功能可以将一个大查询分散到多个节点上执行,从而加快查询速度。该功能会涉及到各个节点之间的通信,包括执行计划的分发、执行的控制、结果的获取等等。因此设计了 **集群拓扑视图** 功能,用于为 ePQ 组件收集并展示集群的拓扑信息,实现跨节点查询。 + +## 术语 + +- RW / Primary:读写节点,后统称为 Primary +- RO / Replica:只读节点,后统称为 Replica +- Standby:灾备节点 +- Replication Slot:流复制槽,PostgreSQL 中用于持久化流复制关系的机制 + +## 功能使用 + +集群拓扑视图的维护是完全透明的,用户只需要按照部署文档搭建一写多读的集群,集群拓扑视图即可正确维护起来。关键在于需要搭建带有流复制槽的 Replica / Standby 节点。 + +使用以下接口可以获取集群拓扑视图(执行结果来自于 PolarDB for PostgreSQL 11): + +```sql:no-line-numbers +postgres=# SELECT * FROM polar_cluster_info; + name | host | port | release_date | version | slot_name | type | state | cpu | cpu_quota | memory | memory_quota | iops | iops_quota | connection | connection_quota | px_connection | px_connection_quota | px_node +-------+-----------+------+--------------+---------+-----------+---------+-------+-----+-----------+--------+--------------+------+------------+------------+------------------+---------------+---------------------+--------- + node0 | 127.0.0.1 | 5432 | 20220930 | 1.1.27 | | RW | Ready | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | f + node1 | 127.0.0.1 | 5433 | 20220930 | 1.1.27 | replica1 | RO | Ready | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | t + node2 | 127.0.0.1 | 5434 | 20220930 | 1.1.27 | replica2 | RO | Ready | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | t + node3 | 127.0.0.1 | 5431 | 20220930 | 1.1.27 | standby1 | Standby | Ready | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | f +(4 rows) +``` + +- `name` 是节点的名称,是自动生成的。 +- `host` / `port` 表示了节点的连接信息。在这里,都是本地地址。 +- `release_date` 和 `version` 标识了 PolarDB 的版本信息。 +- `slot_name` 是节点连接所使用的流复制槽,只有使用流复制槽连接上来的节点才会被统计在该视图中(除 Primary 节点外)。 +- `type` 表示节点的类型,有三类: + - PolarDB for PostgreSQL 11:RW / RO / Standby + - PolarDB for PostgreSQL 14:Primary / Replica / Standby +- `state` 表示节点的状态。有 Offline / Going Offline / Disabled / Initialized / Pending / Ready / Unknown 这些状态,其中只有 Ready 才有可能参与 PX 计算,其他的都无法参与 PX 计算。 +- `px_node` 表示是否参与 PX 计算。 +- 后续字段都是性能采集相关的字段,目前都是留空的。 + +对于 ePQ 查询来说,默认只有 Replica 节点参与。可以通过参数控制使用 Primary 节点或者 Standby 节点参与计算: + +```sql:no-line-numbers +-- 使 Primary 节点参与计算 +SET polar_px_use_master = ON; + +-- 使 Standby 节点参与计算 +SET polar_px_use_standby = ON; +``` + +::: tip +从 PolarDB for PostgreSQL 14 起,`polar_px_use_master` 参数改名为 `polar_px_use_primary`。 +::: + +还可以使用 `polar_px_nodes` 指定哪些节点参与 PX 计算。例如使用上述集群拓扑视图,可以执行如下命令,让 PX 查询只在 replica1 上执行。 + +```sql:no-line-numbers +SET polar_px_nodes = 'node1'; +``` + +## 设计实现 + +### 信息采集 + +集群拓扑视图信息的采集是通过流复制来传递信息的。该功能对流复制协议增加了新的消息类型用于集群拓扑视图的传递。分为以下两个步骤: + +- Replica / Standby 将状态传递给 Primary +- Primary 汇总集群拓扑视图,返回给 Replica / Standby + +### 更新频率 + +集群拓扑视图并非定时更新与发送,因为视图并非一直变化。只有当节点刚启动时,或发生关键状态变化时再进行更新发送。 + +在具体实现上,Primary 节点收集的全局状态带有版本 generation,只有在接收到节点拓扑变化才会递增;当全局状态版本更新后,才会发送到其他节点,其他节点接收到后,设置到自己的节点上。 + +![生成集群拓扑视图](../../../imgs/cluster_info_generate.png) + +### 采集维度 + +状态指标: + +- 节点 name +- 节点 host / port +- 节点 slot_name +- 节点负载(CPU / MEM / 连接 / IOPS) +- 节点状态 + - Offline + - Going Offline + - Disabled + - Initialized + - Pending + - Ready + - Unknown + +### 消息格式 + +同 WAL Sender / WAL Reciver 的其他消息的做法,新增 `'m'` 和 `'M'` 消息类型,用于收集节点信息和广播集群拓扑视图。 + +### 内部使用 + +提供接口获取 Replica 列表,提供 IP / port 等信息,用于 PX 查询。 + +预留了较多的负载接口,可以根据负载来实现动态调整并行度。(尚未接入) + +同时增加了参数 `polar_px_use_master` / `polar_px_use_standby`,将 Primary / Standby 加入到 PX 计算中,默认不打开(可能会有正确性问题,因为快照格式、Vacuum 等原因,快照有可能不可用)。 + +ePQ 会使用上述信息生成节点的连接信息并缓存下来,并在 ePQ 查询中使用该视图。当 generation 更新或者设置了 `polar_px_nodes` / `polar_px_use_master` / `polar_px_use_standby` 时,该缓存会被重置,并在下次使用时重新生成缓存。 + +### 结果展示 + +通过 `polar_monitor` 插件提供视图,将上述集群拓扑视图提供出去,在任意节点均可获取。 diff --git a/docs/zh/features/v11/epq/epq-create-btree-index.md b/docs/zh/features/v11/epq/epq-create-btree-index.md new file mode 100644 index 00000000000..207e63345b4 --- /dev/null +++ b/docs/zh/features/v11/epq/epq-create-btree-index.md @@ -0,0 +1,81 @@ +--- +author: 棠羽 +date: 2023/09/20 +minute: 20 +--- + +# ePQ 支持创建 B-Tree 索引并行加速 + + + + + +[[toc]] + +## 背景 + +在使用 PostgreSQL 时,如果想要在一张表中查询符合某个条件的行,默认情况下需要扫描整张表的数据,然后对每一行数据依次判断过滤条件。如果符合条件的行数非常少,而表的数据总量非常大,这显然是一个非常低效的操作。与阅读书籍类似,想要阅读某个特定的章节时,读者通常会通过书籍开头处的索引查询到对应章节的页码,然后直接从指定的页码开始阅读;在数据库中,通常会对被频繁查找的列创建索引,以避免进行开销极大的全表扫描:通过索引可以精确定位到被查找的数据位于哪些数据页面上。 + +PostgreSQL 支持创建多种类型的索引,其中使用得最多的是 [B-Tree](https://www.postgresql.org/docs/current/indexes-types.html#INDEXES-TYPES-BTREE) 索引,也是 PostgreSQL 默认创建的索引类型。在一张数据量较大的表上创建索引是一件非常耗时的事,因为其中涉及到的工作包含: + +1. 顺序扫描表中的每一行数据 +2. 根据要创建索引的列值(Scan Key)顺序,对每行数据在表中的物理位置进行排序 +3. 构建索引元组,按 B-Tree 的结构组织并写入索引页面 + +PostgreSQL 支持并行(多进程扫描/排序)和并发(不阻塞 DML)创建索引,但只能在创建索引的过程中使用单个计算节点的资源。 + +PolarDB-PG 的 ePQ 弹性跨机并行查询特性支持对 B-Tree 类型的索引创建进行加速。ePQ 能够利用多个计算节点的 I/O 带宽并行扫描全表数据,并利用多个计算节点的 CPU 和内存资源对每行数据在表中的物理位置按索引列值进行排序,构建索引元组。最终,将有序的索引元组归并到创建索引的进程中,写入索引页面,完成索引的创建。 + +## 使用方法 + +### 数据准备 + +创建一张包含三个列,数据量为 1000000 行的表: + +```sql:no-line-numbers +CREATE TABLE t (id INT, age INT, msg TEXT); + +INSERT INTO t +SELECT + random() * 1000000, + random() * 10000, + md5(random()::text) +FROM generate_series(1, 1000000); +``` + +### 创建索引 + +使用 ePQ 创建索引需要以下三个步骤: + +1. 设置参数 `polar_enable_px` 为 `ON`,打开 ePQ 的开关 +2. 按需设置参数 `polar_px_dop_per_node` 调整查询并行度 +3. 在创建索引时显式声明 `px_build` 属性为 `ON` + +```sql:no-line-numbers +SET polar_enable_px TO ON; +SET polar_px_dop_per_node TO 8; +CREATE INDEX t_idx1 ON t(id, msg) WITH(px_build = ON); +``` + +在创建索引的过程中,数据库会对正在创建索引的表施加 [`ShareLock`](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-TABLES) 锁。这个级别的锁将会阻塞其它进程对表的 DML 操作(`INSERT` / `UPDATE` / `DELETE`)。 + +### 并发创建索引 + +类似地,ePQ 支持并发创建索引,只需要在 `CREATE INDEX` 后加上 `CONCURRENTLY` 关键字即可: + +```sql:no-line-numbers +SET polar_enable_px TO ON; +SET polar_px_dop_per_node TO 8; +CREATE INDEX CONCURRENTLY t_idx2 ON t(id, msg) WITH(px_build = ON); +``` + +在创建索引的过程中,数据库会对正在创建索引的表施加 [`ShareUpdateExclusiveLock`](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-TABLES) 锁。这个级别的锁将不会阻塞其它进程对表的 DML 操作。 + +## 使用限制 + +ePQ 加速创建索引暂不支持以下场景: + +- 创建 `UNIQUE` 索引 +- 创建索引时附带 `INCLUDING` 列 +- 创建索引时指定 `TABLESPACE` +- 创建索引时带有 `WHERE` 而成为部分索引(Partial Index) diff --git a/docs/zh/features/v11/epq/epq-ctas-mtview-bulk-insert.md b/docs/zh/features/v11/epq/epq-ctas-mtview-bulk-insert.md new file mode 100644 index 00000000000..5a296ed89f5 --- /dev/null +++ b/docs/zh/features/v11/epq/epq-ctas-mtview-bulk-insert.md @@ -0,0 +1,53 @@ +--- +author: 棠羽 +date: 2023/02/08 +minute: 10 +--- + +# ePQ 支持创建/刷新物化视图并行加速和批量写入 + + + + + +[[toc]] + +## 背景 + +[物化视图 (Materialized View)](https://en.wikipedia.org/wiki/Materialized_view) 是一个包含查询结果的数据库对象。与普通的视图不同,物化视图不仅保存视图的定义,还保存了 [创建物化视图](https://www.postgresql.org/docs/current/sql-creatematerializedview.html) 时的数据副本。当物化视图的数据与视图定义中的数据不一致时,可以进行 [物化视图刷新 (Refresh)](https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html) 保持物化视图中的数据与视图定义一致。物化视图本质上是对视图定义中的查询做预计算,以便于在查询时复用。 + +[`CREATE TABLE AS`](https://www.postgresql.org/docs/current/sql-createtableas.html) 语法用于将一个查询所对应的数据构建为一个新的表,其表结构与查询的输出列完全相同。 + +[`SELECT INTO`](https://www.postgresql.org/docs/current/sql-selectinto.html) 语法用于建立一张新表,并将查询所对应的数据写入表中,而不是将查询到的数据返回给客户端。其表结构与查询的输出列完全相同。 + +## 功能原理介绍 + +对于物化视图的创建和刷新,以及 `CREATE TABLE AS` / `SELECT INTO` 语法,由于在数据库层面需要完成的工作步骤十分相似,因此 PostgreSQL 内核使用同一套代码逻辑来处理这几种语法。内核执行过程中的主要步骤包含: + +1. 数据扫描:执行视图定义或 `CREATE TABLE AS` / `SELECT INTO` 语法中定义的查询,扫描符合查询条件的数据 +2. 数据写入:将上述步骤中扫描到的数据写入到一个新的物化视图 / 表中 + +PolarDB for PostgreSQL 对上述两个步骤分别引入了 ePQ 并行扫描和批量数据写入的优化。在需要扫描或写入的数据量较大时,能够显著提升上述 DDL 语法的性能,缩短执行时间: + +1. ePQ 并行扫描:通过 ePQ 功能,利用多个计算节点的 I/O 带宽和计算资源并行执行视图定义中的查询,提升计算资源和带宽的利用率 +2. 批量写入:不再将扫描到的每一个元组依次写入表或物化视图,而是在内存中攒够一定数量的元组后,一次性批量写入表或物化视图中,减少记录 WAL 日志的开销,降低对页面的锁定频率 + +## 使用说明 + +### ePQ 并行扫描 + +将以下参数设置为 `ON` 即可启用 ePQ 并行扫描来加速上述语法中的查询过程,目前其默认值为 `ON`。该参数生效的前置条件是 ePQ 特性的总开关 `polar_enable_px` 被打开。 + +```sql:no-line-numbers +SET polar_px_enable_create_table_as = ON; +``` + +由于 ePQ 特性的限制,该优化不支持 `CREATE TABLE AS ... WITH OIDS` 语法。对于该语法的处理流程中将会回退使用 PostgreSQL 内置优化器为 DDL 定义中的查询生成执行计划,并通过 PostgreSQL 的单机执行器完成查询。 + +### 批量写入 + +将以下参数设置为 `ON` 即可启用批量写入来加速上述语法中的写入过程,目前其默认值为 `ON`。 + +```sql:no-line-numbers +SET polar_enable_create_table_as_bulk_insert = ON; +``` diff --git a/docs/zh/features/v11/epq/epq-explain-analyze.md b/docs/zh/features/v11/epq/epq-explain-analyze.md new file mode 100644 index 00000000000..cd9fdf086a8 --- /dev/null +++ b/docs/zh/features/v11/epq/epq-explain-analyze.md @@ -0,0 +1,71 @@ +--- +author: 渊云、秦疏 +date: 2023/09/06 +minute: 30 +--- + +# ePQ 执行计划查看与分析 + + + + + +[[toc]] + +## 背景 + +PostgreSQL 提供了 `EXPLAIN` 命令用于 SQL 语句的性能分析。它能够输出 SQL 对应的查询计划,以及在执行过程中的具体耗时、资源消耗等信息,可用于排查 SQL 的性能瓶颈。 + +`EXPLAIN` 命令原先只适用于单机执行的 SQL 性能分析。PolarDB-PG 的 ePQ 弹性跨机并行查询扩展了 `EXPLAIN` 的功能,使其可以打印 ePQ 的跨机并行执行计划,还能够统计 ePQ 执行计划在各个算子上的执行时间、数据扫描量、内存使用量等信息,并以统一的视角返回给客户端。 + +## 功能介绍 + +### 执行计划查看 + +ePQ 的执行计划是分片的。每个计划分片(Slice)由计算节点上的虚拟执行单元(Segment)启动的一组进程(Gang)负责执行,完成 SQL 的一部分计算。ePQ 在执行计划中引入了 Motion 算子,用于在执行不同计划分片的进程组之间进行数据传递。因此,Motion 算子就是计划分片的边界。 + +ePQ 中总共引入了三种 Motion 算子: + +- `PX Coordinator`:源端数据发送到同一个目标端(汇聚) +- `PX Broadcast`:源端数据发送到每一个目标端(广播) +- `PX Hash`:源端数据经过哈希计算后发送到某一个目标端(重分布) + +以一个简单查询作为例子: + +```sql:no-line-numbers +=> CREATE TABLE t (id INT); +=> SET polar_enable_px TO ON; +=> EXPLAIN (COSTS OFF) SELECT * FROM t LIMIT 1; + QUERY PLAN +------------------------------------------------- + Limit + -> PX Coordinator 6:1 (slice1; segments: 6) + -> Partial Seq Scan on t + Optimizer: PolarDB PX Optimizer +(4 rows) +``` + +以上执行计划以 Motion 算子为界,被分为了两个分片:一个是接收最终结果的分片 `slice0`,一个是扫描数据的分片`slice1`。对于 `slice1` 这个计划分片,ePQ 将使用六个执行单元(`segments: 6`)分别启动一个进程来执行,这六个进程各自负责扫描表的一部分数据(`Partial Seq Scan`),通过 Motion 算子将六个进程的数据汇聚到一个目标端(`PX Coordinator 6:1`),传递给 `Limit` 算子。 + +如果查询逐渐复杂,则执行计划中的计划分片和 Motion 算子会越来越多: + +```sql:no-line-numbers +=> CREATE TABLE t1 (a INT, b INT, c INT); +=> SET polar_enable_px TO ON; +=> EXPLAIN (COSTS OFF) SELECT SUM(b) FROM t1 GROUP BY a LIMIT 1; + QUERY PLAN +------------------------------------------------------------ + Limit + -> PX Coordinator 6:1 (slice1; segments: 6) + -> GroupAggregate + Group Key: a + -> Sort + Sort Key: a + -> PX Hash 6:6 (slice2; segments: 6) + Hash Key: a + -> Partial Seq Scan on t1 + Optimizer: PolarDB PX Optimizer +(10 rows) +``` + +以上执行计划中总共有三个计划分片。将会有六个进程(`segments: 6`)负责执行 `slice2` 分片,分别扫描表的一部分数据,然后通过 Motion 算子(`PX Hash 6:6`)将数据重分布到另外六个(`segments: 6`)负责执行 `slice1` 分片的进程上,各自完成排序(`Sort`)和聚合(`GroupAggregate`),最终通过 Motion 算子(`PX Coordinator 6:1`)将数据汇聚到结果分片 `slice0`。 diff --git a/docs/zh/features/v11/epq/epq-node-and-dop.md b/docs/zh/features/v11/epq/epq-node-and-dop.md new file mode 100644 index 00000000000..b89805c7349 --- /dev/null +++ b/docs/zh/features/v11/epq/epq-node-and-dop.md @@ -0,0 +1,142 @@ +--- +author: 渊云 +date: 2023/09/06 +minute: 20 +--- + +# ePQ 计算节点范围选择与并行度控制 + + + + + +[[toc]] + +## 背景介绍 + +PolarDB-PG 的 ePQ 弹性跨机并行查询特性提供了精细的粒度控制方法,可以合理使用集群内的计算资源。在最大程度利用闲置计算资源进行并行查询,提升资源利用率的同时,避免了对其它业务负载产生影响: + +1. ePQ 可以动态调整集群中参与并行查询的计算节点范围,避免使用负载较高的计算节点 +2. ePQ 支持为每条查询动态调整在计算节点上的并行度,避免 ePQ 并行查询进程对计算资源的消耗影响到相同节点上的其它进程 + +## 计算节点范围选择 + +参数 `polar_px_nodes` 指定了参与 ePQ 的计算节点范围,默认值为空,表示所有只读节点都参与 ePQ 并行查询: + +```sql:no-line-numbers +=> SHOW polar_px_nodes; + polar_px_nodes +---------------- + +(1 row) +``` + +如果希望读写节点也参与 ePQ 并行,则可以设置如下参数: + +```sql:no-line-numbers +SET polar_px_use_primary TO ON; +``` + +如果部分只读节点负载较高,则可以通过修改 `polar_px_nodes` 参数设置仅特定几个而非所有只读节点参与 ePQ 并行查询。参数 `polar_px_nodes` 的合法格式是一个以英文逗号分隔的节点名称列表。获取节点名称需要安装 `polar_monitor` 插件: + +```sql:no-line-numbers +CREATE EXTENSION IF NOT EXISTS polar_monitor; +``` + +通过 `polar_monitor` 插件提供的集群拓扑视图,可以查询到集群中所有计算节点的名称: + +```sql:no-line-numbers +=> SELECT name,slot_name,type FROM polar_cluster_info; + name | slot_name | type +-------+-----------+--------- + node0 | | Primary + node1 | standby1 | Standby + node2 | replica1 | Replica + node3 | replica2 | Replica +(4 rows) +``` + +其中: + +- `Primary` 表示读写节点 +- `Replica` 表示只读节点 +- `Standby` 表示备库节点 + +通用的最佳实践是使用负载较低的只读节点参与 ePQ 并行查询: + +```sql:no-line-numbers +=> SET polar_px_nodes = 'node2,node3'; +=> SHOW polar_px_nodes; + polar_px_nodes +---------------- + node2,node3 +(1 row) +``` + +## 并行度控制 + +参数 `polar_px_dop_per_node` 用于设置当前会话中的 ePQ 查询在每个计算节点上的执行单元(Segment)数量,每个执行单元会为其需要执行的每一个计划分片(Slice)启动一个进程。 + +该参数默认值为 `3`,通用最佳实践值为当前计算节点 CPU 核心数的一半。如果计算节点的 CPU 负载较高,可以酌情递减该参数,控制计算节点的 CPU 占用率至 80% 以下;如果查询性能不佳时,可以酌情递增该参数,也需要保持计算节点的 CPU 水位不高于 80%。否则可能会拖慢其它的后台进程。 + +## 并行度计算方法示例 + +创建一张表: + +```sql:no-line-numbers +CREATE TABLE test(id INT); +``` + +假设集群内有两个只读节点,`polar_px_nodes` 为空,此时 ePQ 将使用集群内的所有只读节点参与并行查询;参数 `polar_px_dop_per_node` 的值为 `3`,表示每个计算节点上将会有三个执行单元。执行计划如下: + +```sql:no-line-numbers +=> SHOW polar_px_nodes; + polar_px_nodes +---------------- + +(1 row) + +=> SHOW polar_px_dop_per_node; + polar_px_dop_per_node +----------------------- + 3 +(1 row) + +=> EXPLAIN SELECT * FROM test; + QUERY PLAN +------------------------------------------------------------------------------- + PX Coordinator 6:1 (slice1; segments: 6) (cost=0.00..431.00 rows=1 width=4) + -> Partial Seq Scan on test (cost=0.00..431.00 rows=1 width=4) + Optimizer: PolarDB PX Optimizer +(3 rows) +``` + +从执行计划中可以看出,两个只读节点上总计有六个执行单元(`segments: 6`)将会执行这个计划中唯一的计划分片 `slice1`。这意味着总计会有六个进程并行执行当前查询。 + +此时,调整 `polar_px_dop_per_node` 为 `4`,再次执行查询,两个只读节点上总计会有八个执行单元参与当前查询。由于执行计划中只有一个计划分片 `slice1`,这意味着总计会有八个进程并行执行当前查询: + +```sql:no-line-numbers +=> SET polar_px_dop_per_node TO 4; +SET +=> EXPLAIN SELECT * FROM test; + QUERY PLAN +------------------------------------------------------------------------------- + PX Coordinator 8:1 (slice1; segments: 8) (cost=0.00..431.00 rows=1 width=4) + -> Partial Seq Scan on test (cost=0.00..431.00 rows=1 width=4) + Optimizer: PolarDB PX Optimizer +(3 rows) +``` + +此时,如果设置 `polar_px_use_primary` 参数,让读写节点也参与查询,那么读写节点上也将会有四个执行单元参与 ePQ 并行执行,集群内总计 12 个进程参与并行执行: + +```sql:no-line-numbers +=> SET polar_px_use_primary TO ON; +SET +=> EXPLAIN SELECT * FROM test; + QUERY PLAN +--------------------------------------------------------------------------------- + PX Coordinator 12:1 (slice1; segments: 12) (cost=0.00..431.00 rows=1 width=4) + -> Partial Seq Scan on test (cost=0.00..431.00 rows=1 width=4) + Optimizer: PolarDB PX Optimizer +(3 rows) +``` diff --git a/docs/zh/features/v11/epq/epq-partitioned-table.md b/docs/zh/features/v11/epq/epq-partitioned-table.md new file mode 100644 index 00000000000..3f22a76e59e --- /dev/null +++ b/docs/zh/features/v11/epq/epq-partitioned-table.md @@ -0,0 +1,227 @@ +--- +author: 渊云 +date: 2023/09/06 +minute: 20 +--- + +# ePQ 支持分区表查询 + + + + + +[[toc]] + +## 背景 + +随着数据量的不断增长,表的规模将会越来越大。为了方便管理和提高查询性能,比较好的实践是使用分区表,将大表拆分成多个子分区表。甚至每个子分区表还可以进一步拆成二级子分区表,从而形成了多级分区表。 + +PolarDB-PG 支持 ePQ 弹性跨机并行查询,能够利用集群中多个计算节点提升只读查询的性能。ePQ 不仅能够对普通表进行高效的跨机并行查询,对分区表也实现了跨机并行查询。 + +ePQ 对分区表的基础功能支持包含: + +- 对分区策略为 Range / List / Hash 的分区表进行并行扫描 +- 对分区表进行索引扫描 +- 对分区表进行连接查询 + +此外,ePQ 还支持了部分与分区表相关的高级功能: + +- 分区裁剪 +- 智能分区连接(Partition Wise Join) +- 对多级分区表进行并行查询 + +ePQ 暂不支持对具有多列分区键的分区表进行并行查询。 + +## 使用指南 + +### 分区表并行查询 + +创建一张分区策略为 Range 的分区表,并创建三个子分区: + +```sql:no-line-numbers +CREATE TABLE t1 (id INT) PARTITION BY RANGE(id); +CREATE TABLE t1_p1 PARTITION OF t1 FOR VALUES FROM (0) TO (200); +CREATE TABLE t1_p2 PARTITION OF t1 FOR VALUES FROM (200) TO (400); +CREATE TABLE t1_p3 PARTITION OF t1 FOR VALUES FROM (400) TO (600); +``` + +设置参数打开 ePQ 开关和 ePQ 分区表扫描功能的开关: + +```sql:no-line-numbers +SET polar_enable_px TO ON; +SET polar_px_enable_partition TO ON; +``` + +查看对分区表进行全表扫描的执行计划: + +```sql:no-line-numbers +=> EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +------------------------------------------- + PX Coordinator 6:1 (slice1; segments: 6) + -> Append + -> Partial Seq Scan on t1_p1 + -> Partial Seq Scan on t1_p2 + -> Partial Seq Scan on t1_p3 + Optimizer: PolarDB PX Optimizer +(6 rows) +``` + +ePQ 将会启动一组进程并行扫描分区表的每一个子表。每一个扫描进程都会通过 `Append` 算子依次扫描每一个子表的一部分数据(`Partial Seq Scan`),并通过 Motion 算子(`PX Coordinator`)将所有进程的扫描结果汇聚到发起查询的进程并返回。 + +### 分区静态裁剪 + +当查询的过滤条件中包含分区键时,ePQ 优化器可以根据过滤条件对将要扫描的分区表进行裁剪,避免扫描不需要的子分区,节省系统资源,提升查询性能。以上述 `t1` 表为例,查看以下查询的执行计划: + +```sql:no-line-numbers +=> EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE id < 100; + QUERY PLAN +------------------------------------------- + PX Coordinator 6:1 (slice1; segments: 6) + -> Append + -> Partial Seq Scan on t1_p1 + Filter: (id < 100) + Optimizer: PolarDB PX Optimizer +(5 rows) +``` + +由于查询的过滤条件 `id < 100` 包含分区键,因此 ePQ 优化器可以根据分区表的分区边界,在产生执行计划时去除不符合过滤条件的子分区(`t1_p2`、`t1_p3`),只保留符合过滤条件的子分区(`t1_p1`)。 + +### 智能分区连接 + +在进行分区表之间的连接操作时,如果分区策略和边界相同,并且连接条件为分区键时,ePQ 优化器可以产生以子分区为单位进行连接的执行计划,避免两张分区表的进行笛卡尔积式的连接,节省系统资源,提升查询性能。 + +以两张 Range 分区表的连接为例。使用以下 SQL 创建两张分区策略和边界都相同的分区表 `t2` 和 `t3`: + +```sql:no-line-numbers +CREATE TABLE t2 (id INT) PARTITION BY RANGE(id); +CREATE TABLE t2_p1 PARTITION OF t2 FOR VALUES FROM (0) TO (200); +CREATE TABLE t2_p2 PARTITION OF t2 FOR VALUES FROM (200) TO (400); +CREATE TABLE t2_p3 PARTITION OF t2 FOR VALUES FROM (400) TO (600); + +CREATE TABLE t3 (id INT) PARTITION BY RANGE(id); +CREATE TABLE t3_p1 PARTITION OF t3 FOR VALUES FROM (0) TO (200); +CREATE TABLE t3_p2 PARTITION OF t3 FOR VALUES FROM (200) TO (400); +CREATE TABLE t3_p3 PARTITION OF t3 FOR VALUES FROM (400) TO (600); +``` + +打开以下参数启用 ePQ 对分区表的支持: + +```sql:no-line-numbers +SET polar_enable_px TO ON; +SET polar_px_enable_partition TO ON; +``` + +当 Partition Wise join 关闭时,两表在分区键上等值连接的执行计划如下: + +```sql:no-line-numbers +=> SET polar_px_enable_partitionwise_join TO OFF; +=> EXPLAIN (COSTS OFF) SELECT * FROM t2 JOIN t3 ON t2.id = t3.id; + QUERY PLAN +----------------------------------------------------------- + PX Coordinator 6:1 (slice1; segments: 6) + -> Hash Join + Hash Cond: (t2_p1.id = t3_p1.id) + -> Append + -> Partial Seq Scan on t2_p1 + -> Partial Seq Scan on t2_p2 + -> Partial Seq Scan on t2_p3 + -> Hash + -> PX Broadcast 6:6 (slice2; segments: 6) + -> Append + -> Partial Seq Scan on t3_p1 + -> Partial Seq Scan on t3_p2 + -> Partial Seq Scan on t3_p3 + Optimizer: PolarDB PX Optimizer +(14 rows) +``` + +从执行计划中可以看出,执行 `slice1` 计划分片的六个进程会分别通过 `Append` 算子依次扫描分区表 `t2` 每一个子分区的一部分数据,并通过 Motion 算子(`PX Broadcast`)接收来自执行 `slice2` 的六个进程广播的 `t3` 全表数据,在本地完成哈希连接(`Hash Join`)后,通过 Motion 算子(`PX Coordinator`)汇聚结果并返回。本质上,分区表 `t2` 的每一行数据都与 `t3` 的每一行数据做了一次连接。 + +打开参数 `polar_px_enable_partitionwise_join` 启用 Partition Wise join 后,再次查看执行计划: + +```sql:no-line-numbers +=> SET polar_px_enable_partitionwise_join TO ON; +=> EXPLAIN (COSTS OFF) SELECT * FROM t2 JOIN t3 ON t2.id = t3.id; + QUERY PLAN +------------------------------------------------ + PX Coordinator 6:1 (slice1; segments: 6) + -> Append + -> Hash Join + Hash Cond: (t2_p1.id = t3_p1.id) + -> Partial Seq Scan on t2_p1 + -> Hash + -> Full Seq Scan on t3_p1 + -> Hash Join + Hash Cond: (t2_p2.id = t3_p2.id) + -> Partial Seq Scan on t2_p2 + -> Hash + -> Full Seq Scan on t3_p2 + -> Hash Join + Hash Cond: (t2_p3.id = t3_p3.id) + -> Partial Seq Scan on t2_p3 + -> Hash + -> Full Seq Scan on t3_p3 + Optimizer: PolarDB PX Optimizer +(18 rows) +``` + +在上述执行计划中,执行 `slice1` 计划分片的六个进程将通过 `Append` 算子依次扫描分区表 `t2` 每个子分区中的一部分数据,以及分区表 `t3` **相对应子分区** 的全部数据,将两份数据进行哈希连接(`Hash Join`),最终通过 Motion 算子(`PX Coordinator`)汇聚结果并返回。在上述执行过程中,分区表 `t2` 的每一个子分区 `t2_p1`、`t2_p2`、`t2_p3` 分别只与分区表 `t3` 对应的 `t3_p1`、`t3_p2`、`t3_p3` 做了连接,并没有与其它不相关的分区连接,节省了不必要的工作。 + +### 多级分区表并行查询 + +在多级分区表中,每级分区表的分区维度(分区键)可以不同:比如一级分区表按照时间维度分区,二级分区表按照地域维度分区。当查询 SQL 的过滤条件中包含每一级分区表中的分区键时,ePQ 优化器支持对多级分区表进行静态分区裁剪,从而过滤掉不需要被扫描的子分区。 + +以下图为例:当查询过滤条件 `WHERE date = '202201' AND region = 'beijing'` 中包含一级分区键 `date` 和二级分区键 `region` 时,ePQ 优化器能够裁剪掉所有不相关的分区,产生的执行计划中只包含符合条件的子分区。由此,执行器只对需要扫描的子分区进行扫描即可。 + +![multi-level-partition](../../../imgs/htap-multi-level-partition-1.png) + +使用以下 SQL 为例,创建一张多级分区表: + +```sql:no-line-numbers +CREATE TABLE r1 (a INT, b TIMESTAMP) PARTITION BY RANGE (b); + +CREATE TABLE r1_p1 PARTITION OF r1 FOR VALUES FROM ('2000-01-01') TO ('2010-01-01') PARTITION BY RANGE (a); +CREATE TABLE r1_p1_p1 PARTITION OF r1_p1 FOR VALUES FROM (1) TO (1000000); +CREATE TABLE r1_p1_p2 PARTITION OF r1_p1 FOR VALUES FROM (1000000) TO (2000000); + +CREATE TABLE r1_p2 PARTITION OF r1 FOR VALUES FROM ('2010-01-01') TO ('2020-01-01') PARTITION BY RANGE (a); +CREATE TABLE r1_p2_p1 PARTITION OF r1_p2 FOR VALUES FROM (1) TO (1000000); +CREATE TABLE r1_p2_p2 PARTITION OF r1_p2 FOR VALUES FROM (1000000) TO (2000000); +``` + +打开以下参数启用 ePQ 对分区表的支持: + +```sql:no-line-numbers +SET polar_enable_px TO ON; +SET polar_px_enable_partition TO ON; +``` + +执行一条以两级分区键作为过滤条件的 SQL,并关闭 ePQ 的多级分区扫描功能,将得到 PostgreSQL 内置优化器经过多级分区静态裁剪后的执行计划: + +```sql:no-line-numbers +=> SET polar_px_optimizer_multilevel_partitioning TO OFF; +=> EXPLAIN (COSTS OFF) SELECT * FROM r1 WHERE a < 1000000 AND b < '2009-01-01 00:00:00'; + QUERY PLAN +---------------------------------------------------------------------------------------- + Seq Scan on r1_p1_p1 r1 + Filter: ((a < 1000000) AND (b < '2009-01-01 00:00:00'::timestamp without time zone)) +(2 rows) +``` + +启用 ePQ 的多级分区扫描功能,再次查看执行计划: + +```sql:no-line-numbers +=> SET polar_px_optimizer_multilevel_partitioning TO ON; +=> EXPLAIN (COSTS OFF) SELECT * FROM r1 WHERE a < 1000000 AND b < '2009-01-01 00:00:00'; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + PX Coordinator 6:1 (slice1; segments: 6) + -> Append + -> Partial Seq Scan on r1_p1_p1 + Filter: ((a < 1000000) AND (b < '2009-01-01 00:00:00'::timestamp without time zone)) + Optimizer: PolarDB PX Optimizer +(5 rows) +``` + +在上述计划中,ePQ 优化器进行了对多级分区表的静态裁剪。执行 `slice1` 计划分片的六个进程只需对符合过滤条件的子分区 `r1_p1_p1` 进行并行扫描(`Partial Seq Scan`)即可,并将扫描到的数据通过 Motion 算子(`PX Coordinator`)汇聚并返回。 diff --git a/docs/zh/features/v11/epq/parallel-dml.md b/docs/zh/features/v11/epq/parallel-dml.md new file mode 100644 index 00000000000..3bee16edce8 --- /dev/null +++ b/docs/zh/features/v11/epq/parallel-dml.md @@ -0,0 +1,99 @@ +--- +author: 渊云 +date: 2022/09/27 +minute: 30 +--- + +# 并行 INSERT + + + + + +[[toc]] + +## 背景介绍 + +PolarDB-PG 支持 ePQ 弹性跨机并行查询,能够利用集群中多个计算节点提升只读查询的性能。此外,ePQ 也支持在读写节点上通过多进程并行写入,实现对 `INSERT` 语句的加速。 + +## 功能介绍 + +ePQ 的并行 `INSERT` 功能可以用于加速 `INSERT INTO ... SELECT ...` 这种读写兼备的 SQL。对于 SQL 中的 `SELECT` 部分,ePQ 将启动多个进程并行执行查询;对于 SQL 中的 `INSERT` 部分,ePQ 将在读写节点上启动多个进程并行执行写入。执行写入的进程与执行查询的进程之间通过 **Motion 算子** 进行数据传递。 + +能够支持并行 `INSERT` 的表类型有: + +- 普通表 +- 分区表 +- (部分)外部表 + +并行 `INSERT` 支持动态调整写入并行度(写入进程数量),在查询不成为瓶颈的条件下性能最高能提升三倍。 + +## 使用方法 + +创建两张表 `t1` 和 `t2`,向 `t1` 中插入一些数据: + +```sql:no-line-numbers +CREATE TABLE t1 (id INT); +CREATE TABLE t2 (id INT); +INSERT INTO t1 SELECT generate_series(1,100000); +``` + +打开 ePQ 及并行 `INSERT` 的开关: + +```sql:no-line-numbers +SET polar_enable_px TO ON; +SET polar_px_enable_insert_select TO ON; +``` + +通过 `INSERT` 语句将 `t1` 表中的所有数据插入到 `t2` 表中。查看并行 `INSERT` 的执行计划: + +```sql:no-line-numbers +=> EXPLAIN INSERT INTO t2 SELECT * FROM t1; + QUERY PLAN +----------------------------------------------------------------------------------------- + Insert on t2 (cost=0.00..952.87 rows=33334 width=4) + -> Result (cost=0.00..0.00 rows=0 width=0) + -> PX Hash 6:3 (slice1; segments: 6) (cost=0.00..432.04 rows=100000 width=8) + -> Partial Seq Scan on t1 (cost=0.00..431.37 rows=16667 width=4) + Optimizer: PolarDB PX Optimizer +(5 rows) +``` + +其中的 `PX Hash 6:3` 表示 6 个并行查询 `t1` 的进程通过 Motion 算子将数据传递给 3 个并行写入 `t2` 的进程。 + +通过参数 `polar_px_insert_dop_num` 可以动态调整写入并行度,比如: + +```sql:no-line-numbers +=> SET polar_px_insert_dop_num TO 12; +=> EXPLAIN INSERT INTO t2 SELECT * FROM t1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Insert on t2 (cost=0.00..952.87 rows=8334 width=4) + -> Result (cost=0.00..0.00 rows=0 width=0) + -> PX Hash 6:12 (slice1; segments: 6) (cost=0.00..432.04 rows=100000 width=8) + -> Partial Seq Scan on t1 (cost=0.00..431.37 rows=16667 width=4) + Optimizer: PolarDB PX Optimizer +(5 rows) +``` + +执行计划中的 `PX Hash 6:12` 显示,并行查询 `t1` 的进程数量不变,并行写入 `t2` 的进程数量变更为 `12`。 + +## 使用说明 + +调整 `polar_px_dop_per_node` 和 `polar_px_insert_dop_num` 可以分别修改 `INSERT INTO ... SELECT ...` 中查询和写入的并行度。 + +1. 当查询并行度较低时,逐步提升写入并行度,SQL 执行时间将会逐渐下降并趋于平缓;趋于平缓的原因是查询速度跟不上写入速度而成为瓶颈 +2. 当查询并行度较高时,逐步提升写入并行度,SQL 执行时间将会逐渐下降并趋于平缓;趋于平缓的原因是并行写入只能在读写节点上进行,写入速度因多个写入进程对表页面扩展锁的争抢而跟不上查询速度,成为瓶颈 + +## 原理介绍 + +ePQ 对并行 `INSERT` 的处理如下: + +1. ePQ 优化器以查询解析得到的语法树作为输入,产生计划树 +2. ePQ 执行器将计划树分发到各计算节点,并创建并行查询/并行写入进程,开始执行各自负责执行的子计划 +3. 并行查询进程从存储中并行读取各自负责的数据分片,并将数据发送到 Motion 算子 +4. 并行写入进程从 Motion 算子中获取数据,向存储并行写入数据 + +并行查询和并行写入是以流水线的形式同时进行的。上述执行过程如图所示: + +![parallel_insert_data_flow](../../../imgs/parallel_data_flow.png) diff --git a/docs/zh/features/v11/htap/README.md b/docs/zh/features/v11/htap/README.md deleted file mode 100644 index d53df9db40a..00000000000 --- a/docs/zh/features/v11/htap/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# HTAP - -- [自适应扫描](./adaptive-scan.md) -- [并行 DML](./parallel-dml.md) -- [多级分区表静态裁剪与并行扫描](./multi-level-partition.md) diff --git a/docs/zh/features/v11/htap/multi-level-partition.md b/docs/zh/features/v11/htap/multi-level-partition.md deleted file mode 100644 index 9c6db097559..00000000000 --- a/docs/zh/features/v11/htap/multi-level-partition.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -author: 秦疏 -date: 2022/11/28 -minute: 20 ---- - -# 多级分区表静态裁剪与并行扫描 - - - - - -[[toc]] - -## 背景 - -随着数据量的不断增长,表的规模将会越来越大。为了方便管理和提高查询性能,用户一般会使用分区表,将大表拆分成多个子分区表,每个子分区表又进一步可以拆成二级子分区表,从而形成了多级分区表。 - -PolarDB for PostgreSQL 支持多级分区表的静态分区裁剪,避免对无关分区进行扫描。同时,针对被裁剪后的分区表,可以进一步开启并行查询能力,从而加快分区表的查询性能。 - -## 术语 - -- QC:Query Coordinator,发起 PX 并行查询的进程角色。 -- PX Worker:参与 PX 跨节点并行查询的工作进程角色。 -- Worker ID:唯一标识一个 PX Worker 的编号。 - -## 原理 - -在多级分区表中,每一级分区表的分区维度可以不同,如下图所示:比如一级分区表按照时间(date)维度分区,二级分区表按照地域(region)维度分区。当 QC 发起查询时,优化器可以根据查询条件(如 `date = '202201' AND region = 'beijing'`)与每一级分区表的分区键进行匹配,从而过滤掉不需要被扫描的子分区,只保留符合条件的分区表。 - -如果满足条件的分区表数量较多,或者分区表中数据较多,那么可以结合 PolarDB for PostgreSQL 的并行查询(PX)能力,并行扫描对应的数据页面。在 PolarDB for PostgreSQL 共享存储的架构下,读写节点和只读节点对所有表数据都是可见的,因此可以在多个只读节点中启动 PX Worker 并行扫描,最后将结果汇总到 QC 进程。 - -![multi-level-partition](../../../imgs/htap-multi-level-partition-1.png) - -## 使用指南 - -### GUC 参数 - -多级分区表并行查询功能依赖如下两个 GUC 参数: - -| GUC 参数名 | 参数说明 | -| -------------------------------------------- | -------------------------------------- | -| `polar_enable_px` | 开启 PolarDB PostgreSQL 的并行查询功能 | -| `polar_px_optimizer_multilevel_partitioning` | 开启多级分区表并行查询功能 | - -具体开启方式如下: - -```sql:no-line-numbers -SET polar_enable_px = ON; -SET polar_px_optimizer_multilevel_partitioning = ON; -``` - -### 创建多级分区表 - -```sql:no-line-numbers --- 主表 -CREATE TABLE range_list (a int,b timestamp,c varchar(10)) PARTITION BY RANGE (b); - --- 创建两个一级分区表 -CREATE TABLE range_pa1 PARTITION OF range_list FOR VALUES FROM ('2000-01-01') TO ('2010-01-01') PARTITION BY RANGE (a); -CREATE TABLE range_pa2 PARTITION OF range_list FOR VALUES FROM ('2010-01-01') TO ('2020-01-01') PARTITION BY RANGE (a); - --- 分别为每个一级分区表创建两个二级子分区表 -CREATE TABLE range_list_2000_2010_1_10 PARTITION OF range_pa1 FOR VALUES from (1) TO (1000000); -CREATE TABLE range_list_2000_2010_10_20 PARTITION OF range_pa1 FOR VALUES from (1000000) TO (2000000); -CREATE TABLE range_list_2010_2020_1_10 PARTITION OF range_pa2 FOR VALUES from (1) TO (1000000); -CREATE TABLE range_list_2010_2020_10_20 PARTITION OF range_pa2 FOR VALUES from (1000000) TO (2000000); -``` - -### 插入示例数据 - -```sql:no-line-numbers -INSERT INTO range_list SELECT round(random()*8) + 1, '2005-01-01' FROM generate_series(1,100); -INSERT INTO range_list SELECT round(random()*8) + 1000000, '2005-01-01' FROM generate_series(1,100); -INSERT INTO range_list SELECT round(random()*8) + 1, '2019-01-01' FROM generate_series(1,100); -INSERT INTO range_list SELECT round(random()*8) + 1000000, '2019-01-01' FROM generate_series(1,100); -``` - -### 关闭多级分区表并行功能 - -```sql:no-line-numbers -SET polar_enable_px = ON; -SET polar_px_optimizer_multilevel_partitioning = OFF; -``` - -此时,虽然可以进行多级分区表的静态裁剪(只会扫描 `range_list_2000_2010_1_10` 这张分区表),但是并不能使用并行查询功能: - -```sql:no-line-numbers -EXPLAIN SELECT * FROM range_list WHERE a < 1000000 AND b < '2009-01-01 00:00:00'; - QUERY PLAN ----------------------------------------------------------------------------------------------- - Append (cost=0.00..26.18 rows=116 width=50) - -> Seq Scan on range_list_2000_2010_1_10 (cost=0.00..25.60 rows=116 width=50) - Filter: ((a < 1000000) AND (b < '2009-01-01 00:00:00'::timestamp without time zone)) -(3 rows) -``` - -### 开启多级分区表并行功能 - -```sql:no-line-numbers -SET polar_enable_px = ON; -SET polar_px_optimizer_multilevel_partitioning = ON; -``` - -此时,可以进行多级分区表的静态裁剪(只会扫描 `range_list_2000_2010_1_10` 这张分区表),同时也可以使用并行查询功能(6 个并行度): - -```sql -EXPLAIN SELECT count(*) FROM range_list WHERE a < 1000000 AND b < '2009-01-01 00:00:00'; - QUERY PLAN ----------------------------------------------------------------------------------------------------- - PX Coordinator 6:1 (slice1; segments: 6) (cost=0.00..431.00 rows=1 width=22) - -> Append (cost=0.00..431.00 rows=1 width=22) - -> Partial Seq Scan on range_list_2000_2010_1_10 (cost=0.00..431.00 rows=1 width=22) - Filter: ((a < 1000000) AND (b < '2009-01-01 00:00:00'::timestamp without time zone)) - Optimizer: PolarDB PX Optimizer -(5 rows) -``` diff --git a/docs/zh/features/v11/htap/parallel-dml.md b/docs/zh/features/v11/htap/parallel-dml.md deleted file mode 100644 index 57d7ac3f75b..00000000000 --- a/docs/zh/features/v11/htap/parallel-dml.md +++ /dev/null @@ -1,362 +0,0 @@ ---- -author: 渊云 -date: 2022/09/27 -minute: 60 ---- - -# 并行 DML - - - - - -[[toc]] - -## 背景介绍 - -PolarDB for PostgreSQL 提供了一款强大的分析型查询引擎——PX(Parallel eXecution),通过利用集群中多个只读节点来提升查询性能。同时,PX 针对 DML(`INSERT` / `UPDATE` / `DELETE`)也可以做到并行读并行写的加速。其中: - -- **并行读** 是指借助多个只读节点上的多进程来加速 DML 中的查找操作 -- **并行写** 是指在一个 PolarDB 唯一的读写节点上利用多进程实现并行写入 - -## 术语 - -- QC:Query Coordinator,发起 PX 并行查询的进程角色。 -- PX Worker:参与 PX 跨节点并行查询的工作进程角色。 -- DML:数据操作语句,包含 `INSERT` / `UPDATE` / `DELETE`。 -- Slice:指每个 PX Worker 负责执行的计划分片。 -- RW / RO:读写节点 / 只读节点。 - -## 功能介绍 - -### Parallel Insert - -为了加速 `INSERT ... SELECT ...` 这种既有读取又有写入的 DML SQL,PolarDB for PG 使用 Parallel Insert 来提升性能。对于 `SELECT` 子查询,PolarDB 使用多个 PX Worker 并行加速查询;对于 `INSERT` 的写入操作,由于 PolarDB 只有一个 RW 节点,我们会在 RW 节点上启动多个执行写入的 PX Worker 进程,通过 **Motion 算子** 来接收 RO 节点上读取的数据,实现加速并行写入。 - -这里需要注意的是,RO 节点上的 PX Worker 只能执行只读操作,但是在 RW 节点上的 PX Worker 可以执行写入操作。Parallel Insert 在读写数据量均衡的情况下,最高能提升 3 倍的性能。Parallel Insert 已支持: - -- 普通表 -- 分区表 -- 强制有序 -- 并行度动态调整 - -### Parallel Update - -与 Parallel Insert 类似,针对 `UPDATE ... SET ...`,PolarDB 使用多个 PX Worker 来执行并行查询,实现加速筛选需要更新的行;同时,在 RW 节点上启动多个 PX Worker 进程来执行更新操作。在读写数据量均衡的情况下,最高能提升 3 倍的性能。Parallel Update 不支持分区表,支持并行度动态调整。 - -### Parallel Delete - -与 Parallel Update 基本相同,针对 `DELETE FROM ...`,PolarDB 通过多个 PX Worker 来执行并行查询,实现加速筛选需要删除的行;同时,在 RW 节点启动多个 PX Worker 来执行删除操作。Parallel Delete 不支持分区表,支持并行度动态调整。 - -## 功能设计 - -### Parallel Insert - -Parallel Insert 的总体框架如下所示: - -![parallel_insert_arch](../../../imgs/parallel_insert_architecture.png) - -Parallel Insert 的处理步骤如下: - -1. QC 进程接收到 `INSERT ... SEELCT` -2. QC 进程对 SQL 进行解析、重写,生成查询树,通过 PX 优化器生成计划树 -3. 通过 bitmap 标志来指定每个 PX Worker 负责执行哪部分执行计划 -4. 将完整的计划树分发到 RO 节点和 RW 节点,并创建 PX Worker 进程,不同的 PX Workers 根据自己的 ID 在 bitmap 中查找自己负责执行的计划 -5. RO 节点上的 PX Workers 执行查询计划,从存储中并行读取各自负责的数据分片; -6. RO 节点上的 PX Workers 通过 Motion 算子将查询数据发送给 RW 节点上的 PX Workers; -7. RW 节点上的 PX Workers 并行向存储写入数据。 - -其中 5、6、7 三个步骤是全流水线执行的。 - -下面以最简单的并行 DML `INSERT INTO t1 SELECT * FROM t2` 为例。表 `t1` 和 `t2` 都是只有两列的表。 - -```sql - QUERY PLAN -------------------------------------------------- - Insert on public.t1 - -> Result - Output: t2.c1, t2.c2 - -> PX Hash 6:6 (slice1; segments: 6) - Output: t2.c1, t2.c2, (1) - -> Partial Seq Scan on public.t2 - Output: t2.c1, t2.c2, 1 - Optimizer: PolarDB PX Optimizer -(8 rows) -``` - -在执行计划中,`Partial Seq Scan` 代表每个 PX Workers 并行读取的数据分片,`PX Hash 6:6` 说明有 6 个负责读取的 PX Workers 和 6 个负责写入的 PX Workers。计划中的 `Hash` 代表负责读取的 PX Worker 所读取到的数据会 hash 重分布到 RW 节点上负责写入的 PX Worker 上。 - -Parallel Insert 也支持单个写 Worker,多个读 Worker 的执行计划: - -```sql - QUERY PLAN -------------------------------------------------------- - Insert on public.t1 - -> Result - Output: t2.c1, t2.c2 - -> PX Coordinator 6:1 (slice1; segments: 6) - Output: t2.c1, t2.c2 - -> Partial Seq Scan on public.t2 - Output: t2.c1, t2.c2 - Optimizer: PolarDB PX Optimizer -(8 rows) -``` - -由于只有一个写 Worker,所以计划中显示的是 `PX Coordinator 6:1`,将 RO 节点上的数据汇聚到 RW 节点上。 - -下图是以数据流的方式展示 Parallel Insert 的执行过程: - -![parallel_insert_data_flow](../../../imgs/parallel_data_flow.png) - -执行过程如下: - -1. 每个负责读取的 PX Worker 执行一部分的顺序扫描操作,读取数据,进入到 `RedistributeMotionRandom`,将读取到的每条数据重分布,发送给各个负责写入的 PX Worker; -2. 通过 `SendMotion` 来向 RW 节点上的 PX Worker 发送数据,RO 节点上的每个 PX Worker 会从所有 RW 节点上的 PX Worker 中选择一个进行数据重分布,重分布的策略有哈希分布和随机分布两种; -3. RW 节点上被选中的 PX Worker 通过 `RecvMotion` 来接收数据,然后将数据通过 `ModifyTable` 算子写入存储。 - -### Parallel Update - -由于 Parallel Update 和 Delete 在 SQL 解析、重写的过程和 Parallel Insert 相同,下面只说明 Parallel Update 的执行计划和数据流动方式。 - -不带子查询的并行 Update 计划: - -```sql - QUERY PLAN --------------------------------------------------------------------------------------------------------- - Update (segment: 6) on public.t1 - -> Result - Output: t1_1.c1, t1_1.c2, (DMLAction), t1_1.ctid - -> PX Hash 6:6 (slice1; segments: 6) - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (DMLAction), ('16397'::oid) - -> Result - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (DMLAction), '16397'::oid - -> Split - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, DMLAction - -> Partial Seq Scan on public.t1 t1_1 - Output: t1_1.c1, t1_1.c2, 3, t1_1.ctid, t1_1._px_worker_id - Optimizer: PolarDB PX Optimizer -(12 rows) -``` - -从执行计划中可以看出,从 RO 节点读取数据到 RW 节点写入数据之前存在一个 Split 算子。算子中还包含了一个 `DMLAction` 的标志,用于表示当前正在进行的 DML 操作类型(`DML_INSERT` / `DML_DELETE`)。Split 算子用于把 `UPDATE` 拆分为 `DELETE` 和 `INSERT` 两个阶段,表明要删除哪些行、插入哪些行。 - -对于带有子查询的 `UPDATE` 计划,除写入计划分片之外加入了自查询的执行计划分片。示例如下: - -```sql - QUERY PLAN ------------------------------------------------------------------------------------------------------------- - Update (segment: 6) on public.t1 - -> Result - Output: t1_1.c1, t1_1.c2, (DMLAction), t1_1.ctid - -> PX Hash 6:6 (slice1; segments: 6) - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (DMLAction), ('16397'::oid) - -> Result - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (DMLAction), '16397'::oid - -> Split - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, DMLAction - -> Partial Seq Scan on public.t1 t1_1 - Output: t1_1.c1, t1_1.c2, int4((SubPlan 1)), t1_1.ctid, t1_1._px_worker_id - SubPlan 1 - -> Materialize - Output: (count()) - -> PX Broadcast 1:6 (slice2) - Output: (count()) - -> Aggregate - Output: count() - -> PX Coordinator 6:1 (slice3; segments: 6) - -> Partial Seq Scan on public.t2 - Optimizer: PolarDB PX Optimizer -(21 rows) -``` - -Parallel Update 处理数据流图如下图所示: - -![parallel_update_dataflow](../../../imgs/parallel_dml_update_dataflow.png) - -- 对于不带子查询的情况,如 `UPDATE t1 SET c1=3` - 1. 每个负责写入的 PX Worker 并行查找要更新的行 - 2. 通过 Split 算子,拆分成 `DELETE` 和 `INSERT` 操作 - 3. 执行 `ExecDelete` 和 `ExecInsert` -- 带子查询的情况,如 `UPDATE t1 SET c1=(SELECT COUNT(*) FROM t2)` - 1. 每个负责读取的 PX Worker 从共享存储上并行读取自己负责的数据分片,然后通过 `SendMotion` 将自己读到的数据汇聚到 QC 进程 - 2. QC 进程将数据(过滤条件)广播给 RW 节点上的各个负责写入的 PX Worker - 3. 各个负责写入的 PX Worker 分别扫描各自负责的数据分片,查找待更新的数据 - 4. 通过 Split 算子,拆分成 `DELETE` 和 `INSERT` 操作 - 5. 执行 `ExecDelete` 和 `ExecInsert` - -### Parallel Delete - -不带子查询的并行 Delete 计划: - -```sql - QUERY PLAN --------------------------------------------------------------------------------- - Delete (segment: 6) on public.t1 - -> Result - Output: t1_1.c1, t1_1.c2, t1_1.ctid - -> PX Hash 6:6 (slice1; segments: 6) - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (0) - -> Partial Seq Scan on public.t1 t1_1 - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, 0 - Filter: (t1_1.c1 < 10) - Optimizer: PolarDB PX Optimizer -(9 rows) -``` - -带有子查询的并行 Delete 计划: - -```sql - QUERY PLAN ------------------------------------------------------------------------------------ - Delete (segment: 6) on public.t1 - -> Result - Output: t1_1.c1, t1_1.c2, t1_1.ctid - -> PX Hash 6:6 (slice1; segments: 6) - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (0) - -> Hash Semi Join - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, 0 - Hash Cond: (t1_1.c1 = t2.c1) - -> Partial Seq Scan on public.t1 t1_1 - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id - -> Hash - Output: t2.c1 - -> Full Seq Scan on public.t2 - Output: t2.c1 - Optimizer: PolarDB PX Optimizer -(15 rows) -``` - -负责读写的 PX Workers 数量: - -```sql - QUERY PLAN ------------------------------------------------------------------------------------ - Delete (segment: 10) on public.t1 - -> Result - Output: t1_1.c1, t1_1.c2, t1_1.ctid - -> PX Hash 6:10 (slice1; segments: 6) - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, (0) - -> Hash Semi Join - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id, 0 - Hash Cond: (t1_1.c1 = t2.c1) - -> Partial Seq Scan on public.t1 t1_1 - Output: t1_1.c1, t1_1.c2, t1_1.ctid, t1_1._px_worker_id - -> Hash - Output: t2.c1 - -> Full Seq Scan on public.t2 - Output: t2.c1 - Optimizer: PolarDB PX Optimizer -(15 rows) -``` - -可以看到 Parallel Delete 的计划与 Parallel Update 类似,区别在于: - -1. 由于 Parallel Delete 只执行删除操作,不执行插入操作,所以不需要 Split 算子 -2. 顶层的 DML 算子由 Update 变为 Delete 算子 - -并行 Delete 的数据流图如下所示: - -![parallel_dml_delete_dataflow](../../../imgs/parallel_dml_delete_dataflow.png) - -1. 每个负责读取的 PX Workers 扫描属于自己的数据分片,找出要删除的行 -2. 将待删除的行通过 Motion 算子传输给每个负责写入的 PX Workers,并行执行 Delete 操作 - -## 使用说明 - -### Parallel Insert - -Parallel Insert 默认关闭,需要打开开关来使用: - -```sql:no-line-numbers --- 使用 Parallel Insert 前,需要打开 PX -SET polar_enable_px = ON; - --- 开启 Parallel Insert 功能 -SET polar_px_enable_insert_select = ON; - --- 开启 Parallel Insert 写入分区表,默认关闭 -SET polar_px_enable_insert_partition_table = ON; - --- 写入并行度控制,默认为 6,表示 RW 节点上会启动 6 个 PX Workers 来执行写入 -SET polar_px_insert_dop_num = 6; - --- 支持无表查询的开关,默认关闭 -SET polar_px_enable_insert_from_tableless = ON; -``` - -由于 Parallel Insert 无法保证写入顺序,提供以下开关以强制保证写入结果有序: - -```sql:no-line-numbers --- 默认打开,关闭后则不保证并行 Insert 结果有序 -SET polar_px_enable_insert_order_sensitive = ON; -``` - -### Parallel Update - -参数 `polar_px_enable_update` 控制是否开启 Parallel Update 功能,默认关闭。 - -```sql:no-line-numbers -SET polar_px_enable_update = ON; -``` - -参数 `polar_px_update_dop_num` 控制 Parallel Update 的写入并行度。默认为 `6`,范围为 `1~128`。 - -```sql:no-line-numbers --- 启动 6 个 PX Workers 进行写入 -SET polar_px_update_dop_num = 6; -``` - -### Parallel Delete - -参数 `polar_px_enable_delete` 控制是否开启 Parallel Delete,默认关闭。 - -```sql:no-line-numbers -SET polar_px_enable_delete = ON; -``` - -参数 `polar_px_delete_dop_num` 控制 Parallel Delete 的写入并行度。默认值为 `6`,取值范围为 `1~128`。 - -```sql:no-line-numbers --- 启动 6 个 PX Workers 进行删除 -SET polar_px_delete_dop_num = 6; -``` - -## 性能表现 - -下面将简单说明一下 PDML 的性能表现。 - -### Parallel Insert - -在读写数据量相同的情况下,总数据量为 75GB 时,Parallel Insert 的性能表现如下图所示: - -![parallel_dml_insert_result_equal](../../../imgs/parallel_dml_insert_result_1.png) - -当读数据量远大于写数据量的情况下,总数据量为 75GB 时,写入数据量占读数据量的 0.25% 时,Parallel Insert 的性能表现如下图所示: - -![parallel_dml_insert_result_](../../../imgs/parallel_dml_insert_result_read.png) - -由两张图可知: - -1. 在读写数据量相同的情况下,Parallel Insert 最高能提升 3 倍的性能 -2. 读数据量越大,Parallel Insert 性能提升幅度越大,最高能有 4 倍左右的提升 -3. 提升写入并行度对性能提升不大,主要原因是 PX Worker 必须在 RW 上执行并行写入,数据库中的表扩展锁成为性能瓶颈 - -### Parallel Update - -在读写数据量相同的情况下,总数据量为 75GB 时,并行 Update 的性能表现: - -![parallel_dml_update_result](../../../imgs/parallel_dml_update_result.png) - -在读数据量远大于写数据量的情况下,读写数据比例为 100:1 时,并行 Update 的性能表现: - -![parallel_dml_update_read_result](../../../imgs/parallel_dml_update_read_result.png) - -由这两张性能表现图可知: - -1. 当读写数据量相同的情况下,Parallel Update 最高能提升 3 倍的性能 -2. 读数据量越大,Parallel Update 性能提升幅度越大,最高能到达 10 倍的提升 -3. 提升写入并行度对性能提升不大,原因同上 - -### Parallel Delete - -Parallel Delete 的性能表现和结论与 Parallel Update 基本一致,不再赘述。 diff --git a/docs/zh/imgs/cluster_info_generate.png b/docs/zh/imgs/cluster_info_generate.png new file mode 100644 index 00000000000..2442fbd75af Binary files /dev/null and b/docs/zh/imgs/cluster_info_generate.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_1.png b/docs/zh/imgs/pr_parallel_execute_1.png new file mode 100644 index 00000000000..0861d45b981 Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_1.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_2.png b/docs/zh/imgs/pr_parallel_execute_2.png new file mode 100644 index 00000000000..a505d26040b Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_2.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_dispatcher.png b/docs/zh/imgs/pr_parallel_execute_dispatcher.png new file mode 100644 index 00000000000..77273b49879 Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_dispatcher.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_procs_1.png b/docs/zh/imgs/pr_parallel_execute_procs_1.png new file mode 100644 index 00000000000..759689ee109 Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_procs_1.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_procs_2.png b/docs/zh/imgs/pr_parallel_execute_procs_2.png new file mode 100644 index 00000000000..f8a79ce0a39 Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_procs_2.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_procs_3.png b/docs/zh/imgs/pr_parallel_execute_procs_3.png new file mode 100644 index 00000000000..466184289ba Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_procs_3.png differ diff --git a/docs/zh/imgs/pr_parallel_execute_task.png b/docs/zh/imgs/pr_parallel_execute_task.png new file mode 100644 index 00000000000..e187b55934f Binary files /dev/null and b/docs/zh/imgs/pr_parallel_execute_task.png differ diff --git a/docs/zh/imgs/pr_parallel_replay_1.png b/docs/zh/imgs/pr_parallel_replay_1.png new file mode 100644 index 00000000000..db3c6f0685b Binary files /dev/null and b/docs/zh/imgs/pr_parallel_replay_1.png differ diff --git a/docs/zh/imgs/pr_parallel_replay_2.png b/docs/zh/imgs/pr_parallel_replay_2.png new file mode 100644 index 00000000000..21d0705fe6a Binary files /dev/null and b/docs/zh/imgs/pr_parallel_replay_2.png differ