- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
本文围绕 GTID、Aurora 与 Admin 覆盖设计文档 展开,系统讲解 ProxySQL 如何通过 TAP 测试提升
MyHGC.cpp与ProxySQL_Admin_Tests2.cpp的有效 GCOV 覆盖率。你将理解三大覆盖策略的取舍——复用既有 GTID 因果负载、用TEST_AURORA模拟器驱动 Aurora 延迟路由、用PROXYSQLTEST内建命令验证 Admin 行为——并掌握对应的测试注册、聚焦运行与 LCOV 验证方法。
一、目标:让覆盖率命中真实可观测路径
设计文档的核心目标非常明确:通过对可观测生产路径执行 TAP 测试,提升MyHGC.cpp与ProxySQL_Admin_Tests2.cpp中有意义的 GCOV 覆盖率。
这句话包含两层含义:
- "有意义"(meaningful):覆盖率不追求数字堆砌,而是要求 GCOV 记录到的分支确实由真实业务路径触发。若一个 TAP 虽然"跑了",但从未被纳入上传 GCOV 数据的 CI 组,那么它的执行对覆盖率报告毫无贡献——这正是 GTID 测试当时面临的状况。
- "可观测生产路径"(observable production paths):覆盖应当发生在真实的前端连接、真实的查询路由、真实的 Admin SQL 接口之上,而不是通过单元测试直接调用内部函数绕过接口。
实现计划(2026-08-15-gtid-aurora-admin-coverage.md)进一步明确了三条全局约束,它们是整个设计的事实基线:
- 对 ProxySQL 的所有请求一律使用普通 libmysql 客户端流量,不手工构造客户端协议包;
TEST_AURORA仅用于提供 Aurora 监控状态,端点选择与状态影响必须由前端查询断言;- Admin 状态的恢复一律通过
LOAD ... FROM DISK与LOAD ... TO RUNTIME完成。
二、三大覆盖领域与决策概览
设计文档将工作拆分为三个互不重叠的领域,每个领域对应一个已经落地于 test/tap/groups/groups.json 的 TAP 测试:
| 覆盖领域 | 测试文件 | 注册的 GCOV 组 | 覆盖目标 |
|---|---|---|---|
| GTID 因果路由 | test_gtid_from_ok-t.cpp(14 项断言) | mysql84-g5(追加) | MyHGC.cpp中gtid_trxid候选分支 |
| Aurora 查询路由 | test_aurora_query_routing-t.cpp(9 项断言) | cluster_sim_aurora-g1 | MyHGC.cpp中 lag 过滤与 writer 移除路径 |
| 内置 Admin 测试 | test_admin_builtin_coverage-t.cpp(23 项断言) | mysql84-g5 | ProxySQL_Admin_Tests2.cpp中PROXYSQLTEST2/3/6/12/13/16 |
三者的共同点是:不修改任何生产代码(计划中明确 "No production code changes"),仅通过测试注册、模拟器状态注入与 TAP 行为断言来扩大 GCOV 的执行覆盖。实现计划中的执行证据(2026-08-15)显示:GTID 测试在 MySQL 8.4 聚焦运行通过并记录到目标分支,Aurora 测试 9 项断言全过,Admin 测试 23 项断言全过且 GCOV 记录了目标case。
三、GTID 因果路由覆盖:复用而非重写
3.1 问题所在:测试存在,但覆盖率缺失
GTID 因果路由的测试其实早就存在。test_gtid_from_ok-t.cpp 已经完成了完整的"真实写入 →min_gtid因果读取 → 验证只读主机组查询计数"工作负载。设计文档明确指出,这个测试当前只注册在不上传 GCOV 数据的 binlog 组中(即legacy-binlog-g1、mysql84-binlog-g1、mysql90-binlog-g1、mysql95-binlog-g1),因此它的执行无法反映到覆盖率报告里。
关键决策:先在 MySQL 8.4 GCOV 环境中"原样"运行这个未修改的 TAP。如果它通过、且覆盖率报告包含 GTID 候选分支,就把
mysql84-g5追加进它的组注册。既不复制工作量,也不用单元测试替换它。
落地后的注册(groups.json 第 583 行)为:
"test_gtid_from_ok-t" : [ "legacy-binlog-g1", "mysql84-binlog-g1", "mysql84-g5", "mysql90-binlog-g1", "mysql95-binlog-g1" ]3.2 测试如何构造"真实因果负载"
从源码看,测试搭建了一套专用的、高优先级的路由环境,避免与组基线的通用testuser规则冲突:
- 专用前端账号
gtid_from_ok_tap(注释明确说明该账号故意不属于任何组基线,测试会安装高优先级专用规则,否则会被覆盖率组里的通用规则抢占); - 专用主机组:写主机组
RW_HG=15983、只读主机组RO_HG=15984; - 专用规则区间
159830~159832:INSERT 与session_track_gtids查询走向写组,SELECT id FROM test.gtid_from_ok走向只读组; - 后端行为控制:将
mysql-update_gtid_from_ok先设为false,验证 min_gtid 读取被拒绝("disabled OK-packet ingestion rejects min_gtid read"),再设为true并LOAD MYSQL VARIABLES TO RUNTIME,验证因果读取生效。
测试的核心执行链(run_test依次串联):
resolve_writer_endpoint:从runtime_mysql_servers解析 ONLINE 写端点并转成数字 IP(BINLOG_WHG环境变量可指定优先的写主机组);install_dedicated_routing:插入前端用户、双主机组服务器(只读组gtid_port=1)与三条专用规则;configure_gtid_variables:保存并覆盖mysql-connect_timeout_server_max、mysql-client_session_track_gtid、mysql-default_session_track_gtids、mysql-server_capabilities(追加CLIENT_SESSION_TRACKING)与mysql-update_gtid_from_ok;verify_disabled_ingestion:SET SESSION session_track_gtids=OWN_GTID后 INSERT 第 1 行,从 OK 包取回 GTID,确认关闭状态下/*+ ;min_gtid=... */读取被拒绝;verify_untracked_insert:确认未请求跟踪的会话 INSERT 不回传 GTID、端点 GTID 记录保持为空;verify_tracked_causal_read:INSERT 第 3 行拿到 GTID 后,轮询stats_mysql_gtid_executed直至端点学习到该 GTID,再发起/*+ ;min_gtid=gtid3 */因果读取,断言读回id=3,并验证只读组15984的查询计数严格递增。
3.3 源码印证:候选分支在MyHGC.cpp
测试想要打中的 GCOV 分支位于 lib/MyHGC.cpp 的get_random_MySrvC(char *gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess)(定义于第 107 行)。在该函数的候选扫描循环中:
- 当
gtid_trxid非零时(即 GTID 因果读取场景),每个 ONLINE 服务器必须通过MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)校验才进入候选数组(第 167–173 行); - 同时
max_lag_ms >= 0时还要求(unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000(第 175–176 行)。
由于测试走真实前端写入并经 OK 包学习 GTID,再以min_gtid注释触发因果读,get_random_MySrvC的gtid_trxid分支会在运行时真正被执行,从而被编译器匹配的 GCOV 记录为已覆盖。
四、Aurora 查询路由覆盖:模拟器只做控制面
4.1 为什么需要TEST_AURORA模拟器
普通 MySQL 无法提供 Aurora 专属的REPLICA_HOST_STATUS监控源。设计文档明确说明:只有带REPLICA_HOST_STATUS的源才能让 ProxySQL 分配被测代码依赖的 Aurora lag 字段(aws_aurora_current_lag_us)。因此备选方案"用普通 MySQL 充当 Aurora 源"被直接否决。
而TEST_AURORA模拟器在此处的职责被严格限定:只用于控制监控状态(monitor state),不构造客户端流量、也不充当裸线客户端助手。真正的断言流量是普通 libmysql 查询,经由 ProxySQL 前端转发。
4.2 模拟器 fixture:三节点 Aurora 拓扑
状态注入由模拟器 payload 描述:query_routing.json 定义了三节点(实际为四台服务器)拓扑:
| 主机 | 角色 | 初始 lag(REPLICA_LAG_IN_MILLISECONDS) | 主机组 |
|---|---|---|---|
host.1.11.aws-test.com | 写节点(MASTER_SESSION_ID) | 0 | 1271 |
host.1.12.aws-test.com | 零延迟只读副本 | 0 | 1272 |
host.1.13.aws-test.com | 低延迟合格副本 | 4 | 1272 |
host.1.14.aws-test.com | 高延迟副本(高于查询注解的max_lag_ms) | 50 | 1272 |
配套的 Aurora 主机组配置(mysql_aws_aurora_hostgroups)为:写组 1271、读组 1272、domain_name=".aws-test.com"、监控阈值max_lag_ms=100(高延迟服务器保持在监控阈值之下 ONLINE,从而测试"服务器在线但被查询注解过滤"的场景)、writer_is_also_reader=1。
4.3 TAP 的执行链与断言
test_aurora_query_routing-t.cpp 的流程与设计文档的四步完全对应:
- 注入状态:以
cluster_simulator --mode verify -f <payload>运行模拟器(依赖环境变量CLUSTER_SIM_BINARY_PATH与CLUSTER_SIM_TESTS_ROOT),并校验其 JSON 输出err_type == "none"; - 等待收敛:通过
AuroraVariableRestore::set_one_replica_minimum()将mysql-aurora_max_lag_ms_only_read_from_replicas置为 1 并LOAD MYSQL VARIABLES TO RUNTIME(该 RAII 守卫同时保存旧值、在析构时恢复,防止部分 SET/LOAD 泄漏状态); - 发出真实前端查询:以
aurora1/pass1账号连接 ProxySQL 普通前端,执行带注解的查询:
SELECT @@version_comment LIMIT 1 /* ;max_lag_ms=10;create_new_connection=1 */- 断言后端身份与计数(共 9 项断言,plan(9)):
- 查询成功;
host.1.13(lag 4ms,处于注解阈值 10ms 内)的连接池计数ConnUsed+ConnFree增加;host.1.12(零延迟候选)与写节点host.1.11的连接池计数保持不变;stats_mysql_global中的get_aws_aurora_replicas_skipped_during_query计数器严格增加(证明host.1.14因超过max_lag_ms=10被跳过)。
测试开头注释精确概括了分工:"cluster simulator 被刻意限制在监控控制面:它发布一个写节点、一个低延迟副本、一个高延迟副本;断言流量本身是经过 ProxySQL 前端的普通 libmysql 流量。"
4.4 源码印证:lag 过滤、跳过计数与 writer 移除
MyHGC.cpp中与此测试对应的三条执行路径:
(1)lag 过滤与跳过计数(第 175–183 行):候选扫描中,max_lag_ms >= 0时比较注解阈值与服务器aws_aurora_current_lag_us/1000;不满足的服务器走else分支,递增st_var_aws_aurora_replicas_skipped_during_query——这正是 TAP 断言计数器增加所验证的代码:
if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { ... mysrvcCandidates[num_candidates]=mysrvc; num_candidates++; } else { sess->thread->status_variables.stvar[st_var_aws_aurora_replicas_skipped_during_query]++; }(2)writer 排除(第 271–296 行):max_lag_ms > 0时读取aurora_max_lag_ms_only_read_from_replicas(源码取名为min_num_replicas);当候选数 ≥ 该值、且候选的aws_aurora_current_lag_us总和非零(避免误判全零场景)时,将所有aws_aurora_current_lag_us==0的候选(即写节点)从候选数组中移除。TAP 断言"写节点连接数不增加",正是这条 writer-removal 路径被执行并生效的证明。
(3)TEST_AURORA统计插桩(第 115–122 行):#ifdef TEST_AURORA下维护array_mysrvc_total/array_mysrvc_cands用于模拟器环境下的候选统计,说明该模拟器代码路径本身即为覆盖率设计的一部分。
注册(groups.json 第 505 行):
"test_aurora_query_routing-t" : [ "cluster_sim_aurora-g1" ]cluster_sim_aurora-g1是会上传 GCOV 数据的工作流组,保证该测试的执行能进入覆盖率报告。
五、内置 Admin 测试覆盖:行为断言而非盲目派发
5.1 原则:扩展已有调用者的行为覆盖
PROXYSQLTEST是一条由 Admin 接口处理的测试命令,其派发逻辑位于 lib/ProxySQL_Admin_Tests2.cpp(第 1156–1158 行以sscanf解析PROXYSQLTEST <n> <arg1> <arg2> ...)。设计文档的原则是:"扩展它们的行为覆盖,而不是盲目地派发每一个编号"——即不追求把所有 case 都跑一遍,而是挑出已有 TAP 调用者从未覆盖、且具备可观测行为后置条件的命令,逐一断言其效果。
5.2 Digest 路径:命令 2 / 3 / 6
对应源码中的三个 case:
case 1(第 1161–1169 行):ProxySQL_Test___GenerateRandomQueryInDigestTable(test_arg1),生成test_arg1*1000条随机 digest 条目;case 2(第 1170–1176 行):ProxySQL_Test___GetDigestTable(false, false)——只快照digest map,不写 DB;case 3(第 1177–1183 行):ProxySQL_Test___GetDigestTable(true, false)——快照并 reset,不写 DB;case 6(第 1196–1201 行):ProxySQL_Test___PurgeDigestTable<...>(true, false, arg1)——异步 purgedigest map。
test_admin_builtin_coverage-t.cpp 依次验证(前 11 项断言覆盖该路径):
PROXYSQLTEST 3 0:清空任何既有内存 digest;PROXYSQLTEST 1 1:生成 1000 条(断言affected_rows == 1000);PROXYSQLTEST 2 0:快照,断言行数 > 0;PROXYSQLTEST 3 0:快照并 reset,断言返回行数等于快照行数;PROXYSQLTEST 2 0:再次快照,断言为 0(reset 确实生效);PROXYSQLTEST 1 1:重新生成 1000 条;PROXYSQLTEST 6 0:启动异步 purge,断言接受的条目数 > 0,随后有界轮询PROXYSQLTEST 2 0直至返回 0 行(10 秒超时,wait_for_empty_digest_table),证明异步 purge 真正完成。
这里对"异步完成"的验证是本测试区别于"仅接受命令成功"的关键——测试用mysql_affected_rows观察命令返回值,并用轮询观察 digest 表的终态,而不是简单调用后直接断言成功。
5.3 Fast-routing 路径:命令 12 / 13 / 16
对应源码中的 case:
case 12/case 16(第 1230–1248 行):ProxySQL_Test___GenerateRandom_mysql_query_rules_fast_routing(arg1, with_username)生成随机 fast-routing 表——12生成带 username的规则,16生成空 username(true参数)的规则;两者生成后都会执行load_mysql_query_rules_to_runtime()把表载入运行时;case 13(第 1249–1262 行):循环load_mysql_query_rules_to_runtime()指定次数。
TAP 的对应断言(第 12–23 项):
PROXYSQLTEST 12 64:断言影响行数 = 64,并分别统计配置表mysql_query_rules_fast_routing与运行时表runtime_mysql_query_rules_fast_routing中username <> ''的行数,二者均为 64——证明生成且已加载到 runtime;PROXYSQLTEST 13 2:重复加载两次后,runtime 中命名规则仍为 64 条——证明重复加载无损;PROXYSQLTEST 16 64:断言影响行数 = 64,配置表与运行时表中username = ''的行数均为 64。
5.4 Teardown:从磁盘恢复规则
测试用 RAII 守卫QueryRulesRestore在构造时生效、所有退出路径(含早期 return 与异常路径)上执行:
LOAD MYSQL QUERY RULES FROM DISK LOAD MYSQL QUERY RULES TO RUNTIME从而保证 fast-routing 生成器对规则表的污染在测试结束后被完全还原。设计文档还特别划定了不覆盖的边界:
- 命令 31 的 mode 2/3:存在未调查的
FIXME,不做覆盖尝试; - 无条件 early return 之后的代码、以及
#ifdef DEBUG保护下的代码:不属于正常 GCOV 目标,使其覆盖需要生产代码或构建矩阵层面的决策,而不是增加 TAP 命令。
注册(groups.json 第 501 行):
"test_admin_builtin_coverage-t" : [ "mysql84-g5" ]六、备选方案为何被否决
设计文档给出了三个被系统否决的备选方案,理解它们有助于把握设计意图:
- 为三个领域分别新增单元测试——否决。单元测试绕过了路由与 Admin 会话的集成接口,而恰恰是这些接口的集成覆盖存在缺口。
- 新增一个 GTID TAP——否决。现有测试已经具备所需的真实因果负载,缺陷仅仅是它没有在 GCOV 环境中执行;新增测试是重复工作量。
- 用普通 MySQL 充当 Aurora 源——否决。它缺少
REPLICA_HOST_STATUS,无法让 ProxySQL 分配被测的 Aurora lag 字段,因而根本无法驱动目标分支。
七、验证方法与 CI 运行
设计文档对验证有硬性要求:每个变更的 TAP 必须在它所属的隔离组中运行,并且聚焦运行的 LCOV 报告必须命中目标行。实现计划给出了可复制的运行方式。
7.1 用 GCOV 构建目标测试
以 GTID 测试为例(ubuntu22_dbg_build容器内):
git_version=$(git describe --long --abbrev=7) git_epoch=$(git show -s --format=%ct HEAD) docker compose run --rm --no-deps --entrypoint bash \ -e GIT_VERSION_BASE="$git_version" -e GIT_VERSION="$git_version" \ -e SOURCE_DATE_EPOCH="$git_epoch" -e WITHGCOV=1 \ -w /opt/proxysql ubuntu22_dbg_build -lc ' make -j8 WITHGCOV=1 GIT_VERSION_BASE="$GIT_VERSION_BASE" GIT_VERSION="$GIT_VERSION" debug make -C test/tap -j8 WITHGCOV=1 GIT_VERSION="$GIT_VERSION" tap make -C test/tap/tests -j8 WITHGCOV=1 GIT_VERSION="$GIT_VERSION" test_gtid_from_ok-t '7.2 在隔离组中聚焦运行并检查 LCOV
export INFRA_ID=coverage-gtid-proof export TAP_GROUP=mysql84-g5 export TEST_PY_TAP_INCL='test_gtid_from_ok-t' export COVERAGE=1 ./test/infra/control/ensure-infras.bash ./test/infra/control/run-tests-isolated.bash预期结果:TAP 通过,且 LCOV 报告把 MyHGC.cpp 中的 GTID 候选行标记为已执行。Aurora 组(TAP_GROUP=cluster_sim_aurora-g1)的预期是 LCOV 覆盖 lag 过滤与 writer 移除路径;Admin 组(mysql84-g5)的预期是ProxySQL_Admin_Tests2.cpp覆盖 case 2/3/6/12/13/16。
7.3 组注册校验与最终检查
所有 groups.json 变更须通过仓库 linter,全部变更文件须通过空白检查:
python3 test/tap/groups/lint_groups_json.py python3 test/tap/groups/check_groups.py --source python3 test/tap/groups/lint_group_coverage.py test/tap/groups/groups.json git diff --check还可直接在覆盖率报告产物中检索目标文件确认命中:
rg -n 'MyHGC\.cpp|ProxySQL_Admin_Tests2\.cpp' ci_infra_logs/*/coverage-report/*.info八、小结
这份设计给出了一个可复用的"覆盖率缺口修补"方法论:优先复用并重定位已有真实负载(GTID)、把模拟器严格限制在控制面(Aurora)、以行为后置条件驱动内建命令覆盖(Admin)。三者都坚持走真实前端/Admin 接口、坚持在 GCOV 组中聚焦运行、坚持用 LCOV 逐行验收。对希望为 ProxySQL 贡献测试覆盖的开发者而言,这套"设计 → 计划 → 聚焦验证 → 注册 → 校验"的流程与 设计文档、实现计划 中的完整命令,可以直接作为后续同类工作的模板。
- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
相关推荐
ProxySQL 覆盖率工程实战:GTID 因果读、Aurora 延迟路由与 Admin 内建命令的 GCOV 覆盖计划
ProxySQL 覆盖率工程实战:GTID 因果读、Aurora 延迟路由与 Admin 内建命令的 GCOV 覆盖计划 本文基于仓库计划文档 docs/sup
后端数据库负载均衡Voyager 路由覆盖指南:在 Laravel Admin 中安全重写内置路由
Voyager 路由覆盖指南:在 Laravel Admin 中安全重写内置路由 Voyager 是 Laravel 生态中著名的 "Missing Larav
后端CMSProxySQL CI 覆盖率采集器 GCOV 兼容性改造:用 GCOV 11 统一 TAP 覆盖率转换链路
ProxySQL CI 覆盖率采集器 GCOV 兼容性改造:用 GCOV 11 统一 TAP 覆盖率转换链路 本文围绕仓库内 docs/superpowers/
后端数据库负载均衡
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考