【GaussDB】解析GaussDB热补丁机制
前言
热补丁(Hot Patching)是指在程序运行过程中,无需停止或重启程序,直接对其进行修补或更新的一种技术。
作为企业级数据库软件的GaussDB,也同样有热补丁的能力。
熟悉openGauss的可能比较好奇,openGauss绝大部分的内核代码实质上都编译进了可执行程序这一个文件里,其他的动态库so文件并没有太多东西,想要修改内核行为,必然需要产生新的可执行程序。但是GaussDB应用热补丁后,可执行二进程程序文件是没有变化的,这是怎么做到的呢?
引用官方文档
特性简介
热补丁将补丁以patch文件的形式加载到正在运行的数据库进程中,达到零中断修复线上系统的目的。
客户价值
热补丁最大的优势是业务零中断加载补丁,可以在不影响业务的前提下在线解决一部分数据库内核的紧急问题。
其价值主要体现在如下两点:
- 缩短版本发布时间,紧急问题从版本回归验证轻量化为补丁回归验证,提高了线上紧急问题的响应速度。
- 热补丁的加载,卸载对业务无感知,提高了客户满意度。
特性描述
热补丁基于发布的代码版本生成补丁文件,然后以模块的形式插入到数据库内核运行地址空间中,通过寻找热补丁目标函数的地址,并动态地,原子地替换入口地址,重定向函数代码段至补丁文件代码段达到修复线上系统缺陷的目的。
- 热补丁的制作通过修复特定缺陷函数,制作成模块,动态地加载到运行中的内核系统。
- 热补丁找到目标函数,并在目标函数的入口处加入跳转指令,当目标函数被调用时,跳转到补丁区执行补丁函数。
- 目标函数的替换和还原是原子操作CPU寄存器,热补丁可以随时随地加载和卸载,线上系统无需中断,即随时可运行最新的代码。
分析热补丁包文件
正好我拿到了一个GaussDB的热补丁包,就尝试解开来看看
DBS-GaussDB-kernel_9.0.0.HP0105.20251215165913.123656517_all.tar.gz
常规解法,
- DBS-GaussDB-kernel_9.0.0.HP0105.20251215165913.123656517_all\DBS-GaussDB-kernel_9.0.0.HP0105.20251215165913.123656517_noarch.tar.gz
- repo\DBS-GaussDB-kernel-src-9.0.0.HP0105.20251215165913.123656517-1.noarch.rpm
- DBS-GaussDB-kernel-src-9.0.0.HP0105.20251215165913.123656517-1.noarch.cpio
- opt\cloud\GaussDB-kernel\v9.0.0.HP0105-GaussDBV5-install-kylin-ha-x86_64_464fcff72a80c567890b584106ffce9613b1b511defcfda44e14a32cbb7ccfbf.tar.gz
- GaussDB-Kernel_506.0.0.SPC0100.HP0105.pat
一路解压,得到了一个pat文件,查看二进制文件头,1F8B08,似乎是gzip,于是当成tar.gz解压,得到了
GaussDB-Kernel_KERNELHP_506.0.0.SPC0100.0105.pat
查看这个文件的可见字符(LINUX下直接string就行)
PS D:\test> $b=[System.IO.File]::ReadAllBytes('GaussDB-Kernel_KERNELHP_506.0.0.SPC0100.0105.pat'); $s= -join ($b | ForEach-Object {[char]$_}); $matches=[regex]::Matches($s,'[ -~]{6,}'); $matches.Value | Select-Object -First 1000
CodeLen = 2776;DataLen = 192;FuncAmount = 2;CreationTime(GMT) = 2025-12-10 09:14:56;ExecInfo=gaussdb,{ProgVersion=GAUSSDBV500R002C10;InnerPatNo=1;CodeAddr=0;DataAddr=0;PatchVersion=b4a4e2e7da3e9be0b59027ff6ed9bf1;PatAreaAddr=0;ProgType=58;NetType=1;ProgID=A247;};
AWAVAUATI
8[A\A]A^A_]
AWAVAUATI
8[A\A]A^A_]
1. fix index skip scan
gaussdb
GAUSSDBV500R002C10
_ZN12UBTreeSearch22find_first_index_tupleEP17IndexScanDescData13ScanDirectionRt
_Z23_find_first_index_tupleP17IndexScanDescData13ScanDirectionRt
_Z16_bt_get_endpointP12RelationDatajb
t_thrd
__tls_get_addr
_Z19_bt_preprocess_keysP17IndexScanDescData
u_sess
_Z10_bt_getbufP12RelationDataji
_Z10_bt_relbufP12RelationDatai
_Z14buffer_releasei
_Z10_skip_freeP17IndexScanDescData
_Z13_bt_walk_leftP12RelationDataij
_ZN12UBTreeSearch13get_end_pointEP12RelationDatajb
_ZN12UBTreeSearch9skip_freeEP17IndexScanDescData
.text._Z23_find_first_index_tupleP17IndexScanDescData13ScanDirectionRt
.text._ZN12UBTreeSearch22find_first_index_tupleEP17IndexScanDescData13ScanDirectionRt
2023-07-04 21:00:00
可以观察到这个里面包含了补丁的说明信息以及c++的一些符号,基本能判断是用于修复索引跳扫的一个BUG。
(本文仅对内核的热补丁进行分析,如果是CM/OM的,解压到这一层后需要再次当成tar.gz解压,能直接得到用于替换的完整文件)
那么补丁是怎么生效的呢?
找一个已经打了补丁的环境,在gaussdb的app目录直接搜索pat文件,可以找到
./hotpatch/pat目录,里面有解压前的pat文件和解压后的pat文件。但是gaussdb进程对这些文件并没有持有句柄。
查看文档,补丁还有一个激活过程,可以使用gs_om/gs_hotpatch/gs_ctl来激活热补丁,当然前面两个最终还是调用的gs_ctl
gs_om -t hotpatch -n NAME -c [load | unload | active | deactive | info | recover | cleanLock]
gs_hotpatch -t [load|unload|active|deactive|info|list|recover|cleanLock|setLock] -n PATCH_NAME [-X XMLFILE] [-S SOURCE_IP]
gs_ctl hotpatch [-D DATADIR] [-a ACTION] [-n NAME]
热补丁要生效,还需要加载进内存,再激活,这就一个动态注入(游戏开外挂了)。
参考openGauss源码
其实在openGauss里,虽然文档没有说支持热补丁功能,但是源码里依然能找到hotpatch的相关代码,其核心代码就在pg_ctl.cpp里。
但是制作热补丁的方法似乎并没有开源,因此openGauss的热补丁代码在这里就没有意义了。
不过还是可以用AI来分析一下openGauss里现有这部分代码的实现机制
User: 分析整理hotpatch相关的原理机制,注意使用中文回复
GitHub Copilot: Searched for regex
hotpatch|hot patch|hot_patch|HotPatch(**/src/**), 200 resultsSearching for regex
check_and_process_hotpatch|hotpatch_remove_signal_file(**/src/**)Searched for files matching
**/src/**/hotpatch*.*, 7 matchesSearched for regex
under_patching|hotpatch cmd|hotpatch.ret|hotpatch.cmd(**/src/**), 21 resultsSearched for text
check_and_process_hotpatch(**/src/**), 2 resultsSearched for text
hotpatch(**/src/gausskernel/process/postmaster/**), 12 resultsSearched for text
hotpatch_remove_signal_file(**/src/**), 2 resultsSearched for text
hotpatch(**/src/gausskernel/**), 51 resultsSearched for text
hotpatch(**/src/gausskernel/storage/replication/walreceiver.cpp), 2 results热补丁机制概览
- 编译开关:
SUPPORT_HOTPATCH控制是否启用,启用后链接外部distribute/lib/hotpatch提供的闭源实现,并在gaussdb链接时添加专用链接脚本(原子性/跳转表);见 Makefile。- 库接口:头文件 hotpatch.h 暴露
hotpatch_init、exec_hotpatch_command等 API;状态值HP_STATE_UNLOAD/DEACTIVE/ACTIVED和错误码枚举定义在同文件。客户端辅助接口在 hotpatch_client.h。- 进程启动时初始化:Postmaster 在启动阶段调用
hotpatch_remove_signal_file清理历史信号文件后,执行hotpatch_init(DataDir, gs_hotpatch_log_callback)(见 postmaster.cpp)。日志回调将库输出转成ereport。- 运行时入口:Postmaster 主循环每轮调用
check_and_process_hotpatch()(同文件约L11278),它由热补丁库实现,用于检查是否有新的补丁命令需要处理。触发方式是SIGUSR1信号;信号由客户端工具gs_ctl hotpatch发送。- 客户端工作流(pg_ctl.cpp):
- 校验动作
list/load/unload/active/deactive/info;list会转换成内部动作check。- 写指令文件
hotpatch.cmd.tmp(包含发起进程 pid、补丁包路径、动作),fsync后原子重命名为hotpatch.cmd。- 通过
hotpatch.lock加锁清理上一次的结果文件hotpatch.ret,防止并发补丁。- 向 postmaster 发送
SIGUSR1。- 轮询等待
hotpatch.ret(最多 2000 次,1ms 间隔)。匹配到自身 pid 后读取结果字符串。若是list,再读取hotpatch/patch.info中的补丁列表并打印状态。
- 结果与状态文件:
hotpatch.ret:由后台写入,首字段是 pid,后跟结果字符串([PATCH-SUCCESS]前缀代表成功)。hotpatch/patch.info:由热补丁库维护,头部记录最大补丁条目,后续记录每个补丁的名称和状态,客户端用来实现list。解析逻辑见 hotpatch_client.cpp。- 补丁包与工具:测试用例(hotpatch.source)显示补丁包扩展名为
.pat,典型流程为load -> active -> info/list -> deactive/unload。构建脚本提示需要外部仓库GaussDBKernel-hotpatch提供的工具makepatch、fastcheck等,并通过PATCHLIB_HOME软链接到distribute/lib/hotpatch/tool/与sample/。- 约束与并发控制:
- 通过锁文件确保一次只能有一个补丁操作;若无法删除旧结果文件则直接报错退出。
- 等待超时(约 2s)会报
[PATCH-ERROR]: Hotpatch timeout!。- 能见度与缺失:热补丁实际注入/回滚逻辑位于预编译的
hotpatch.o/libhotpatchcommon.a等闭源组件,源码仓库未包含其实现;从接口和文件协议可知其工作模式是“命令文件 + 信号 + 结果文件”的 out-of-band 控制通道。可选后续
- 如果需要进一步理解注入细节,需查看
distribute/lib/hotpatch提供的源或文档(原子跳转、符号重定位策略等)。- 如需验证流程,可在测试环境运行
gs_ctl hotpatch -D <data> -a load -n <patch.pat>并观察hotpatch.cmd/ret/patch.info的生成与状态变化。- 若要新增监控或告警,可在
hotpatch_wait_and_get_replyinfo_from_node周围增加日志或延长等待窗口,避免 2s 超时导致的误报。
的确有启动时加载和运行时加载的逻辑,不过最核心的注入逻辑闭源了(个人感觉如果开源,风险极大)。
总结
本次通过手动解压热补丁包、参考openGauss源码以及GaussDB官方文档,基本了解了GaussDB热补丁原理,可供大家参考。
