只读查询分流

本文档介绍如何用 hot standby 让备节点 standby 承接报表、BI 等只读查询,把连接、SQL 解析和查询调度的压力从主节点分流出去。适用于 YMatrix 6.8 及以上版本。

1 适用场景

报表、BI、分析类业务通常是"连接多、单个查询不重",压力主要压在协调节点上:连接排队、SQL 解析和查询调度把主节点的 CPU 吃满,而数据节点还闲着。hot standby 就是把这个压力挪一部分到备节点上的开关。

适合开启的迹象:

  • 报表、看板、即席查询这类只读业务连接数多,主节点上的连接数先打满,而 primary segments 的 CPU、IO 并不高;
  • 高峰期主节点的负载明显高于数据节点,查询在调度阶段就排队;
  • 写入和维护操作不频繁,对"提交后立刻在备节点读到"没有强要求;有强要求时见第 6 节的 remote_apply;
  • 集群已经部署了备节点。没有的话先按 mxinitstandby 添加。

不适合开启的情况:

  • 慢在单条大查询上——那是执行层的问题,把查询挪到备节点不会变快;
  • 只读业务本来就少,主节点连接远未打满,开了只是多一套要维护的配置;
  • 应用无法改造连接串、也没有重试机制——备节点发生切换时会中断查询,见第 5 节。

2 工作原理

YMatrix 集群里有两类节点,职责不同:

节点 角色 负责的事
master 主节点,主协调节点 接收连接、生成执行计划、调度查询
standby 备节点,备协调节点 与主节点保持同步;开启 hot standby 后也能接收只读连接
primary segment 主数据节点 存储数据、执行查询
mirror 备数据节点 primary 的副本,primary 故障时接管

开启后,连接分布大致是这样:

        只读应用                     写入、维护
           │                             │
           ▼                             ▼
      standby 备节点  ←—— 复制 ——→  master 主节点
           │                             │
           └─────────────┬───────────────┘
                         ▼
                 primary segments 执行查询
                    │              │
                 mirror         mirror

两条关键结论,决定了后面所有注意事项:

  1. 备节点只分担协调工作,不承载业务数据分片。 它省下的是连接、解析、调度,真正的查询执行仍然落在数据节点上。所以开启后 primary segments 的负载会上升,主节点的负载会下降。
  2. 备节点上收到的分布式查询在哪执行,由 mx_hot_standby_use_primary_qe 决定。 该参数开启时,查询发往当前的 primary segments,因此某个 mirror 不可用也不会导致查询读不到数据,建议同时开启,见第 4 节。

hot_standby 默认关闭,它只改变"恢复中的实例能否接受只读连接",不改变集群的主备切换机制。

3 开启前的准备

按顺序确认四件事,可以避免重启后返工:

  1. 确认已有备节点。 在主节点上执行:
select content, role, hostname, port, status
from gp_segment_configuration
where content = -1;

输出两行:role = p 是主节点,role = m 是备节点。status = u 表示实例正常。记下备节点的 hostname 和 port,4.4 节要用。

此时直接连备节点会报 FATAL: the database system is in recovery mode——这是 hot standby 关闭时的正常响应,不代表实例有问题。

  1. 确认参数当前值。
gpconfig -s hot_standby
gpconfig -s mx_hot_standby_use_primary_qe

如果已经是 on,说明之前开过,可以直接跳到 4.3 节确认。

  1. 安排维护窗口。 hot_standby 是 postmaster 级参数,设置分类为 restart,改完必须重启集群才能生效;重启会断开所有连接,并回滚未提交的事务。

  2. 准备应用侧。 只读应用要能改连接地址,并且在连接中断时能重试。这一步不做,重启完业务就连不上了。

4 开启步骤

4.1 设置参数

在主节点上以安装用户执行,通常是 mxadmin:

gpconfig -c hot_standby -v on
gpconfig -c mx_hot_standby_use_primary_qe -v on

gpconfig 默认把参数写入所有实例的 postgresql.conf:主节点、备节点、数据节点和镜像,不需要逐个节点改。

两个参数的生效方式不同:

  • hot_standby 是 postmaster 参数,设置分类为 restart,必须重启;
  • mx_hot_standby_use_primary_qe 是会话级参数,设置分类为 session,重启后新建的连接自动读到新值。

注意!
设置完立刻执行 gpconfig -s,两个参数仍会显示 off:它读的是运行中的值,不是配置文件里的值。重启后才会变成 on,不要误以为设置失败——配置文件里已经写入了 hot_standby=on。

4.2 重启集群

mxstop -a -r -M fast

各参数含义:-a 不提示确认,-r 停止后重新启动,-M fast 中断并回滚正在执行的事务。

注意!
不加 -M fast 时,mxstop 默认是 smart 模式:只要还有活动连接就拒绝停止。这也是"重启卡住不动"最常见的原因。
旧文档里的 mxstop -arf 是 gpstop 时代沿用下来的写法,它在 6.8 的 mxstop --help 里查不到 -f,建议按上面的显式写法执行。

重启期间集群不可用,建议在业务低峰操作。

4.3 确认生效

集群起来后,先看参数:

gpconfig -s hot_standby
gpconfig -s mx_hot_standby_use_primary_qe

期望两边都是 on:

Values on all segments are consistent
GUC              : hot_standby
Coordinator value: on
Segment     value: on

再登录备节点确认状态:

psql -h <备节点主机名> -p <备节点端口> -d <数据库名>
-- 返回 t,说明备节点处于恢复状态,这是正常现象
select pg_is_in_recovery();

show hot_standby;                       -- 期望 on
show mx_hot_standby_use_primary_qe;     -- 期望 on

最后在备节点上跑一条只读查询,例如 select count(*) from public.orders;,把表名换成你的业务表,确认能出结果;顺便试一条写入,应该失败并提示 cannot execute INSERT in a read-only transaction——报这个错是预期的,说明它确实只读。

4.4 把只读应用接到备节点

  • 连接地址改成备节点的 hostname 和 port,值见第 3 节第 1 步。写入和维护应用保持连主节点不变。
  • 放通访问来源。 编辑备节点主机上的 pg_hba.conf,按来源网段添加规则,然后执行:
mxstop -u

-u 会重载主节点、备节点和所有数据节点的 pg_hba.conf,以及 postgresql.conf 中的运行时参数,不停止数据库。

  • 确认权限。 用户和角色信息随复制同步到备节点,但要确保只读用户对目标表有 SELECT 权限。

5 开启后要预期的三件事

这三条不是故障,是 hot standby 的固有行为,提前知道就不会误判:

一、读查询会和维护操作抢锁。 备节点上的查询走 primary 执行时,遵守常规的锁冲突规则。例如查询正在读某个分区,持有 AccessShareLock,此时主节点对该分区执行 TRUNCATE,需要 AccessExclusiveLock,就必须等查询结束。反过来说,维护操作也可能被只读查询拖住。所以 TRUNCATE、VACUUM FULL 这类需要排他锁的操作,尽量和报表高峰错开。

二、故障切换会中断查询。 hot_standby 不改变集群的主备切换机制。切换发生时,备节点上正在执行的查询会中断;备节点提升为主节点之后,原来指向它的只读连接也需要重新建立。这也是第 3 节要求应用具备重试能力的原因。

三、元数据的可见性受回放进度影响。 写 synchronous_commit = on 时,如果 synchronous_standby_names 里配了同步备库,提交要等 WAL 落到这些备库的持久存储,但不等待回放;该参数为空时(YMatrix 的默认配置),on 与 local 等价,提交只等本地刷盘,与备节点无关。不过在 mx_hot_standby_use_primary_qe = on 的推荐配置下,行数据直接从 primary segments 读取,所以新插入的行不受备节点回放进度影响。

受影响的是元数据:回放没跟上时,备节点的系统目录还是旧的,新建的表、新加的列在备节点上看不到——查询会报 relation "..." does not exist,等回放追上来后恢复可见。所以做完 DDL 想在备节点上马上使用,要等回放跟上。

6 相关参数

参数 默认值 生效方式 作用与取舍
hot_standby off 重启 是否允许恢复中的实例接受只读连接
mx_hot_standby_use_primary_qe off 会话 备节点收到的分布式查询是否发往当前 primary segments。boolean 类型,可用 gpconfig -l 查
synchronous_commit on 会话 配了同步备库时:on 等提交 WAL 落到备库持久存储,不等回放;remote_apply 等到回放完成。synchronous_standby_names 为空时,on、remote_write、remote_apply、local 都只等本地刷盘
max_standby_streaming_delay 30s 重载 回放为避让查询冲突可以延迟的时间预算,用完即取消冲突查询;-1 表示无限等待
hot_standby_feedback off 重载 备库反馈查询需要保留的旧版本,减少因主库清理导致的查询取消,代价是主库表膨胀
wal_receiver_status_interval 10s 重载 反馈消息的发送周期上限,hot_standby_feedback 的反馈不会比这个更频繁

生效方式对应参数页的「设置分类」:重启是 restart,重载是 reload,会话是 session。各参数的取值范围请以 gpconfig -l 的输出为准。

调参建议:先保留默认值跑一段时间,再按实际情况动:

  • 业务要求"主库提交返回后,备节点上的新事务立刻能读到" → 设 synchronous_commit = remote_apply,提交会等回放完成;代价是备节点上的查询若阻碍回放,主库提交也会跟着等。前提是 synchronous_standby_names 里列出了这个备节点,否则 remote_apply 与 on 一样只等本地刷盘,不会改变提交行为。
  • 备节点上的查询频繁被取消,日志里能看到与回放冲突相关的取消 → 先调大 max_standby_streaming_delay,或者打开 hot_standby_feedback。前者增加回放延迟,后者增加主库膨胀,两者都会把代价转移到别处。
  • max_standby_streaming_delay 不是单条查询的超时时间,也不控制主节点上的普通锁等待,不要拿它当查询超时用。

7 关闭与回滚

参数改回默认并重启即可回到开启前的状态:

gpconfig -c hot_standby -v off
gpconfig -c mx_hot_standby_use_primary_qe -v off
mxstop -a -r -M fast

关闭后备节点恢复为纯备用节点,不再接受只读连接;记得把只读应用的连接地址改回主节点。

8 常见问题

Q:开了 hot standby,但应用连备节点连不上?

按顺序查:备节点的 status 是否为 u,查 gp_segment_configuration;备节点主机上的 pg_hba.conf 是否放通了来源网段、改完是否 mxstop -u;端口是否用的是备节点自己的;数据库用户是否存在且有权限。

Q:备节点上的查询比主节点上还慢?

先确认 mx_hot_standby_use_primary_qe 是否为 on。走 mirror 读取时,链路上的开销和可用性都更差;走 primary segments 才是推荐配置。另外备节点只承担协调工作,单条大查询不会因为挪到备节点而变快。

Q:备节点上执行查询报 primary segments can only process MPP protocol messages from primary QD?

说明当前会话的 mx_hot_standby_use_primary_qe 是关的:备节点的查询试图走 mirror,在没有 mirror 的集群上必然撞上这个错误。确认参数为 on 后重新连接再试。

Q:重启时命令没反应、一直等着?

大概率是没带 -M fast,smart 模式下有活动连接就会拒绝停止。确认业务可以断开后重新执行 mxstop -a -r -M fast。

Q:维护窗口里 TRUNCATE、VACUUM FULL 被卡住?

备节点上的只读查询在主节点上持有了表的共享锁,排他操作必须等它结束。先用 pg_stat_activity 和 pg_locks 定位持有 AccessShareLock 的会话,必要时用 pg_cancel_backend 结束它;长期做法是把维护操作挪到报表低峰,并在维护侧设置 lock_timeout,让操作失败而不是无限等待。

Q:发生切换后应用报错?

预期行为。备节点提升为主节点后旧连接全部失效,应用需要重连;并将只读应用的地址改到新的主节点,或新的备节点。

另见