技术原理
核心机制
- 进程间通信
内核与CM交互链路不应受制于网络问题、高负载问题,采用进程间通信方式交互,具体使用了Unix domain socket(后简称UDS)实现。
分析CM探活的场景,需考虑以下几点:
- 指标时效:CM探活指标获取频率较高,部分指标采集周期为1次/s,采用共享内存具有滞后性,且需要额外考虑对指标时效性的判断;使用UDS的实时性较高,较符合该场景。
- 参数可变:原CM探活是通过网络建连后执行SQL的方式,CM会动态的通过参数拼接SQL,内核侧无法提前预判入参并采集。在这种场景下,CM需将查询指令置于共享内存,内核读取后再执行指标查询操作,最终转化为更大的时效问题。
综合考虑采用UDS方式实现CM和内核的交互。
- 应急通道资源预留
内核应急通道预留资源,以在极端负载场景下仍能提供探活能力,且在执行探活指标获取/运维的过程中,尽可能少地依赖I/O。
- 预留CPU资源:绑定HA_BACKEND cgroup组;
- 预留内存资源:分析应急通道线程所使用的内存较少,但优先级极高,因此内存上下文用量不设上限,通过应急通道线程查询视图或执行运维操作前执行内存上下文切换。
- 内核、应急通道线程与CM(gsql)交互主流程
- CM、内核完成UDS的初始化。
- CM与内核建立长连接。
- CM内线程按需、定期向内核发送报文以查询指标或进行仲裁后的运维指令下发。
- 内核解析报文,反序列化参数,根据指令执行查询/运维操作。对于运维操作,根据具体命令逻辑确定是同步/异步接口,对于指标查询均为同步接口。内核获取结果集后进行序列化、报文封装及报文发送。特别地,内核在获取CM所需信息时,确保只加SpinLock和极少数必要的哈希表相关锁。
考虑该长连接可能由于异常原因断开或阻塞,为保证探活链路通畅需有如下机制:
- 内核探活线程需有保活机制,当异常退出后应及时重拉。
- CM与内核的链路若异常断开,CM需进行一定次数的建连尝试,若无法成功建连,则结合原网络建连探活的结果仲裁节点状态。
- CM与内核间的通信应设置超时与重试机制,当请求达到一定重试次数仍未返回结果时,则结合原网络建连探活的结果仲裁节点状态。
- 探活机制
CM通过网络链路探活的原核心流程为建连后发送“SELECT 1”,高负载下通过网络链路探活失败时,会通过应急通道探活作为代替。因此内核新增一个探活指令,其具体实现为:通过wlm_monitor线程异步执行SELECT 1操作并记录结果,覆盖除建连外的SQL执行过程链路的检测,应急通道线程对结果进行获取并反馈给CM。
- 简易运维命令
接口介绍
- GUC参数
- 应急通道的运维命令
gsql工具提供-O选项执行应急通道的运维命令,具体示例参见操作指导。gsql的使用请参见《参考》中“工具参考 > 数据库连接工具 > gsql连接数据库 > gsql使用指导”章节。
表2 gsql支持的Unix Domain Socket运维通道的运维命令 参数
参数说明
取值范围
-O, --emergency-command=EMERGENCYCOMMAND
Unix Domain Socket运维通道的运维命令。
在极端情况下,gsql工具无法与GaussDB建立连接时,可以使用该命令进行应急运维。此命令通过Unix Domain Socket运维通道与GaussDB通信,实现一键运维功能。
-O选项的参数格式具体如下:
"{port} {command} [{param} [...]]"{port}:数据库监听端口。
{command}:具体运维命令。
{param}:该运维命令的参数,可能有0个或多个。
说明:- 使用此选项时,gsql工具必须与GaussDB进程位于同一台服务器上。
- 如果运维命令的参数包含空格,请在参数两边加上引号。
- "{port} mem_log":记载全局的内存概况与全局级内存上下文、线程级内存上下文、session级内存上下文的top20内存上下文占用详情。该功能仅在GUC参数use_workload_manager开启时有效,范围同内存过载记录mem_log特性。无参数。示例:
-O "19995 mem_log"
- "{port} kill_wait_event {wait_event}":查杀指定等待事件会话。参数{wait_event}表示具体的锁、轻量级锁以及I/O的信息等。示例:
-O "19995 kill_wait_event relation"
- "{port} kill_business_sql":查杀非rdsAdmin用户的全部业务SQL语句。无参数。示例:
-O "19995 kill_business_sql"
- "{port} kill_slow_sql {username}":批量查杀非默认数据库下,指定用户的状态为非idle的执行超过1分钟的SQL语句。参数{username}表示用户名。示例:
-O "19995 kill_slow_sql omm"
- "{port} kill_slow_sql {username} {database}":批量查杀指定用户在指定数据库中的执行超过1分钟的SQL语句。参数{username}表示用户名,{database}表示数据库名。示例:
-O "19995 kill_slow_sql omm db1"
- "{port} kill_executing_sql {username}":查杀非默认数据库下,指定用户的状态为active的正在执行的SQL语句。参数{username}表示用户名。示例:
-O "19995 kill_executing_sql omm"
- "{port} kill_mem_topn {top n}":查杀内存占用top n的SQL语句。参数{top n}表示指定的top n。示例:
-O "19995 kill_mem_topn 10"
- "{port} kill_cpu_topn {top n}":查杀CPU TIME占用top n的SQL语句。参数{top n}表示指定的top n。示例:
-O "19995 kill_cpu_topn 10"
- gsql的简易运维元命令
gsql使用简易运维元命令使用示例参见操作指导,gsql的元命令使用请参见《参考》中“工具参考 > 数据库连接工具 > gsql连接数据库 > 元命令参考”章节。
表3 常用简易运维元命令 简易运维命令
参数解释
实际执行的SQL
\view stmt_track {unique_sql_id}
stmt_track:记录指定SQL的L2级别的FULLSQL信息。
unique_sql_id:需要记录的SQL的unique_sql_id。
SELECT * FROM pg_catalog.dynamic_func_control('LOCAL', 'STMT', 'TRACK', '{"{unique_sql_id}", "L2"}');
\view stmt_untrack {unique_sql_id}
stmt_untrack:取消记录指定SQL的L2级别的FULLSQL信息。
unique_sql_id:需要取消记录的SQL的unique_sql_id。
SELECT * FROM pg_catalog.dynamic_func_control('LOCAL', 'STMT', 'UNTRACK', '{"{unique_sql_id}"}');
\view stmt_list
stmt_list:查询记录列表。
SELECT * FROM pg_catalog.dynamic_func_control('LOCAL', 'STMT', 'LIST', '{}');
\view stmt_clean
stmt_clean:清理记录列表。
SELECT * FROM pg_catalog.dynamic_func_control('LOCAL', 'STMT', 'CLEAN', '{}');
\view explain_and_stack {pid}
explain_and_stack:查询指定会话的运行时计划及堆栈。
pid:指定会话的pid。
SELECT pg_catalog.gs_get_explain(pid), pg_catalog.gs_stack(pid) FROM pg_catalog.pg_stat_activity WHERE pid = {pid};
\view slow_sql {top_n}
slow_sql:查询实时慢SQL情况。
top_n:查询的行数。
SELECT pid, sessionid, pg_catalog.SUBSTRING(query, 0, 100), state, usename, pg_catalog.NOW() - query_start AS elp FROM pg_catalog.pg_stat_activity WHERE state != 'idle' AND usename NOT IN ('rdsAdmin', 'rdsMetric', 'rdsBackup', 'rdsRepl', 'dbmind_monitor_agent', 'dbmind_monitor') ORDER BY elp DESC LIMIT {top_n};
\view slow_trans {top_n}
slow_trans:查询实时长事务情况。
top_n:查询的行数。
SELECT pid, sessionid, pg_catalog.SUBSTRING(query, 0, 100), state, usename, pg_catalog.NOW() - xact_start AS elp FROM pg_catalog.pg_stat_activity WHERE state != 'idle' AND usename NOT IN ('rdsAdmin', 'rdsMetric', 'rdsBackup', 'rdsRepl', 'dbmind_monitor_agent', 'dbmind_monitor') ORDER BY elp DESC LIMIT {top_n};
\view wait_event
wait_event(不带参数):查询当前语句和等待事件。
SELECT a.pid, a.sessionid, a.datname, a.usename, a.client_addr, a.xact_start, a.query_start, (pg_catalog.NOW() - a.query_start)::text "query_runtime", w.wait_status, w.wait_event, w.locktag, w.lockmode, w.block_sessionid, a.query FROM pg_catalog.pg_stat_activity a JOIN pg_catalog.pg_thread_wait_status w ON a.sessionid = w.sessionid WHERE a.pid <> pg_catalog.pg_backend_pid() AND a.state = 'active' AND a.client_addr IS NOT NULL ORDER BY query_runtime DESC;
\view wait_event {unique_sql_id} {start_time} {end_time}
wait_event(带有三个参数):查询指定慢SQL的历史等待事件。
unique_sql_id:需要查询的慢SQL的unique_sql_id。
start_time:开始时间。如'2025-01-01 00:00'。
end_time:结束时间。如'2025-01-01 00:00'。
SELECT *, pg_catalog.statement_detail_decode(details, 'plaintext', true) FROM dbe_perf.statement_history WHERE unique_query_id = {unique_sql_id} AND start_time > {start_time} AND start_time < {end_time} ORDER BY db_time DESC;
\view thr_wait_event
thr_wait_event(不带参数):查询实时整体等待事件。
SELECT wait_status, wait_event, pg_catalog.COUNT(*) FROM pg_catalog.pg_thread_wait_status GROUP BY 1, 2 ORDER BY 3 DESC;
\view thr_wait_event {sessionid}
thr_wait_event(带有一个参数):查询指定语句的等待事件。
sessionid:指定的sessionid。
SELECT * FROM pg_catalog.pg_thread_wait_status WHERE sessionid = {sessionid};
\view gs_asp {start_time} {end_time}
gs_asp:查询历史指定时间段整体等待事件。
start_time:开始时间。如'2025-01-01 00:00'。
end_time:结束时间。如'2025-12-31 23:59'。
SELECT wait_status, event, pg_catalog.COUNT(*) FROM pg_catalog.gs_asp WHERE sample_time BETWEEN {start_time} AND {end_time} GROUP BY 1, 2 ORDER BY 3 DESC;
\view pg_stats {table_name} {att_name}
pg_stats:查询指定表的统计信息分布。
table_name:表名。
att_name:列名。
SELECT * FROM pg_catalog.pg_stats WHERE tablename = '{table_name}' AND attname = '{att_name}';
\view autovac {table_name}
autovac:查询统计信息更新阈值。
table_name:表名。
SELECT * FROM pg_catalog.pg_autovac_status('{table_name}'::regclass);
\view thr_pool
thr_pool:查询线程池使用情况。
SELECT node_name, worker_info FROM dbe_perf.global_threadpool_status;
\view failed_proc {proc_name}
failed_proc:查询失效的视图或存储过程。
proc_name:失效的函数。
SELECT re.ev_class, cl.relname FROM pg_catalog.pg_rewrite re, pg_catalog.pg_class cl WHERE re.ev_action LIKE '%{proc_name}%' AND cl.oid = re.ev_class;
\view failed_proc_pkg
failed_proc_pkg:查询失效存储过程对应的package。
SELECT DISTINCT object_type, pg_catalog.pg_get_userbyid(creator) AS owner, CASE object_type WHEN 'P' THEN ( SELECT DISTINCT pg_catalog.UPPER(pkgname) FROM pg_catalog.gs_package WHERE oid = ( SELECT propackageid FROM pg_catalog.pg_proc WHERE oid = object_oid ) ) WHEN 'B' THEN ( SELECT DISTINCT pg_catalog.UPPER(pkgname) FROM pg_catalog.gs_package WHERE oid = object_oid ) WHEN 'S' THEN ( SELECT DISTINCT pg_catalog.UPPER(pkgname) FROM pg_catalog.gs_package WHERE oid = object_oid ) END package_name FROM pg_catalog.pg_object WHERE valid = 'f' AND object_type IN ('P', 'B', 'S');
\view failed_index
failed_index:查询不可用的索引。
SELECT a.schemaname, a.relname, a.indexrelname, b.indisusable FROM pg_catalog.pg_stat_user_indexes a, pg_catalog.pg_index b WHERE a.indexrelid = b.indexrelid AND b.indisusable = 'f';
\view table_size
table_size:查询当前库下所有表的大小,按降序输出。
SELECT schemaname, relname, pg_catalog.pg_total_relation_size(pg_catalog.CONCAT(schemaname, '.', relname)) / 1024 / 1024 / 1024 "GB" FROM pg_catalog.pg_stat_user_tables ORDER BY 3 DESC;
\view dead_tup {top_n}
dead_tup:查询死元组的数量。
top_n:查询的行数。
SELECT relname, n_live_tup, n_dead_tup, (n_dead_tup / (n_live_tup + 1)) AS rating, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze FROM pg_catalog.pg_stat_user_tables ORDER BY rating DESC, n_dead_tup DESC LIMIT {top_n};
\view table_size {table_name}
table_size:查询指定表的大小。
table_name:表名。
SELECT * FROM pg_catalog.pg_size_pretty(pg_catalog.pg_total_relation_size('{table_name}'::regclass));
\view login {start_time} {end_time} {ip} {port}
login:查询指定IP和端口的登录即退出信息。
start_time:开始时间。如'2024-01-01 00:00:00'。
end_time:结束时间。如'2025-12-31 23:59:59'。
ip:IP地址。
port:端口。
SELECT *, pg_catalog.SUBSTRING( client_conninfo::varchar, pg_catalog.POSITION(client_conninfo::varchar, '@'::varchar) + 1, 15 ) AS ip FROM pg_catalog.pg_query_audit({start_time}, {end_time}) WHERE ip = '{ip}' AND remote_port = '{port}';
\view mem
mem:查询当前内存使用信息
SELECT * FROM pg_catalog.pg_total_memory_detail;
\view thr_mem {top_n}
thr_mem(带有一个参数):查询数据库所有线程的内存上下文使用情况。
top_n:查询的行数。
SELECT contextname, pg_catalog.SUM(totalsize) / 1024 / 1024 "totalsize", pg_catalog.SUM(freesize) / 1024 / 1024 "freesize", pg_catalog.COUNT(*) "sum" FROM pg_catalog.gs_thread_memory_context GROUP BY contextname ORDER BY sum DESC LIMIT {top_n};
\view thr_mem {context_name} {top_n}
thr_mem(带有两个参数):查询使用内存最多的线程的线程id。
context_name:内存上下文的名称。
top_n:查询的行数。
SELECT * FROM pg_catalog.pv_thread_memory_detail() WHERE contextname = '{context_name}' ORDER BY totalsize DESC LIMIT {top_n};
\view sess_mem {top_n}
sess_mem:查询数据库所有session的内存上下文使用情况。
top_n:查询的行数。
SELECT contextname, pg_catalog.SUM(totalsize) / 1024 / 1024 "totalsize", pg_catalog.SUM(freesize) / 1024 / 1024 "freesize", pg_catalog.COUNT(*) "sum" FROM pg_catalog.gs_session_memory_context GROUP BY contextname ORDER BY sum DESC LIMIT {top_n};
\view thr_mem_detail {tid} {context_name}
thr_mem_detail:查询指定线程的内存使用情况。
tid:线程id。
context_name:内存上下文的名称。
SELECT * FROM pg_catalog.gs_get_thread_memctx_detail({tid}, '{context_name}');
\view sess_mem_tp {context_name}
sess_mem_tp:查询指定内存上下文的内存使用情况。
context_name:内存上下文的名称。
SELECT * FROM pg_catalog.gs_get_session_memctx_detail('{context_name}');
\view shared_mem {top_n}
shared_mem:查询数据库全局共享内存上下文的使用情况。
top_n:查询的行数。
SELECT contextname, pg_catalog.SUM(totalsize) / 1024 / 1024 "totalsize MB", pg_catalog.SUM(freesize) / 1024 / 1024 "freesize MB", pg_catalog.COUNT(*) "sum" FROM pg_catalog.gs_shared_memory_detail GROUP BY contextname ORDER BY sum DESC LIMIT {top_n};
\view sess_mem_detail {top_n}
sess_mem_detail:查询session级内存上下文的使用情况。
top_n:查询的行数。
SELECT contextname, pg_catalog.SUM(totalsize) / 1024 / 1024 "totalsize MB", pg_catalog.COUNT(1) "count" FROM pg_catalog.pv_session_memory_detail() GROUP BY contextname ORDER BY 2 DESC LIMIT {top_n};
\view sess_mem_detail_tp {top_n}
sess_mem_detail_tp:查询session级内存上下文的使用情况(仅在线程池模式开启时生效)。
top_n:查询的行数。
SELECT sessid, pg_catalog.SUM(totalsize) / 1024 / 1024 "totalsize MB", pg_catalog.COUNT(1) "count" FROM pg_catalog.gs_session_memory_context GROUP BY sessid ORDER BY 2 DESC LIMIT {top_n};
\view thr_mem_pv {top_n}
thr_mem_pv:查询线程级内存上下文的使用情况。
top_n:查询的行数。
SELECT contextname, pg_catalog.SUM(totalsize) / 1024 / 1024 "totalsize", pg_catalog.SUM(freesize) / 1024 / 1024 "freesize", pg_catalog.COUNT(1) "count" FROM pg_catalog.pv_thread_memory_detail() GROUP BY 1 ORDER BY 2 DESC LIMIT {top_n};
\view total_mem {top_n}
total_mem:查询当前数据库节点的内存使用情况。
top_n:查询的行数。
SELECT * FROM pg_catalog.gs_total_memory_detail LIMIT {top_n};
\view avg_cpu {top_n}
avg_cpu:查询SQL语句的平均CPU时间,按降序输出。
top_n:查询的行数。
SELECT unique_sql_id, cpu_time / (n_calls) "average_cpu_time" FROM dbe_perf.statement WHERE n_calls != 0 ORDER BY 2 DESC LIMIT {top_n};
\om create_abort_sql_patch {patch_name} {unique_sql_id}
create_abort_sql_patch:创建避险SQL PATCH。
patch_name:SQL PATCH名。
unique_sql_id:指定SQL的unique_sql_id。
SELECT * FROM dbe_sql_util.create_abort_sql_patch('{patch_name}', {unique_sql_id});
\om kill_sess_thr {username} {database} {context_name}
kill_sess_thr:根据线程内存占用,查杀内存占用高的SQL语句。
username:用户名。
database:数据库名。
context_name:内存上下文的名称。
SELECT pg_catalog.pg_terminate_session(pid, sessionid), pid, sessionid FROM pg_catalog.pg_stat_activity WHERE usename = '{username}' AND datname != 'postgres' AND datname = '{database}' AND pid IN ( SELECT tid FROM pg_catalog.gs_thread_memory_context WHERE contextname = '{context_name}' ) AND state = 'idle';
\om kill_sess_sess {username} {database} {context_name}
kill_sess_sess:根据session内存占用,查杀内存占用高的SQL语句。
username:用户名。
database:数据库名。
context_name:内存上下文的名称。
SELECT pg_catalog.pg_terminate_session(pid, sessionid), pid, sessionid FROM pg_catalog.pg_stat_activity WHERE usename = '{username}' AND datname != 'postgres' AND datname = '{database}' AND pid IN ( SELECT threadid FROM pg_catalog.gs_session_memory_context WHERE contextname = '{context_name}' );