分布式DB锁问题排查方法 - 阿里云HybridDB for PostgreSQL最佳实践

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介:

标签

PostgreSQL , Greenplum , 锁 , SEGMENT不一致 , gp_session_role=utility , gp_dist_random


背景

Greenplum(GPDB)是一个分布式数据库,分布式数据库的锁管理比单机更加复杂。例如在加锁时,需要对所有节点加锁(包括MASTER和所有的SEGMENT节点),在释放锁时,则需要释放所有节点的锁。

如果在释放过程中,MASTER的锁释放了,而SEGMENT锁没有释放,会造成什么问题呢?

不用说,会有很诡异的问题出现。例如某个会话锁了某一张表,但是会话退出时,主节点的锁释放了,SEGNEMT节点的锁没有释放。

那么用户在发起新的会话后,如果加载与之冲突的锁,当然要等待了。但是在MASTER节点你观察不到到底它在等待谁,你只能观察到它在等待。是不是很诡异呢?

锁查看方法

当你遇到堵塞时,可以另外开启一个会话查看是谁堵塞了谁?

《PostgreSQL 锁等待监控 珍藏级SQL - 谁堵塞了谁》

with      
t_wait as      
(      
  select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,a.granted,     
  a.objid,a.objsubid,a.pid,a.transactionid,a.mppsessionid,a.mppiswriter,a.gp_segment_id,       
  b.procpid,b.sess_id,b.waiting_reason,b.current_query,b.xact_start,b.query_start,b.usename,b.datname,b.client_addr,b.client_port,b.application_name     
    from pg_locks a,pg_stat_activity b where a.mppsessionid=b.sess_id and not a.granted     
),     
t_run as     
(     
  select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,a.granted,     
  a.objid,a.objsubid,a.pid,a.transactionid,a.mppsessionid,a.mppiswriter,a.gp_segment_id,       
  b.procpid,b.sess_id,b.waiting_reason,b.current_query,b.xact_start,b.query_start,b.usename,b.datname,b.client_addr,b.client_port,b.application_name     
    from pg_locks a,pg_stat_activity b where a.mppsessionid=b.sess_id and a.granted     
),     
t_overlap as     
(     
  select r.* from t_wait w join t_run r on     
  (     
    r.locktype is not distinct from w.locktype and     
    r.database is not distinct from w.database and     
    r.relation is not distinct from w.relation and     
    r.page is not distinct from w.page and     
    r.tuple is not distinct from w.tuple and     
    r.transactionid is not distinct from w.transactionid and     
    r.classid is not distinct from w.classid and     
    r.objid is not distinct from w.objid and     
    r.objsubid is not distinct from w.objsubid and     
    r.mppsessionid <> w.mppsessionid     
  )      
),      
t_unionall as      
(      
  select r.* from t_overlap r      
  union all      
  select w.* from t_wait w      
)      
select locktype,datname,relation::regclass,page,tuple,textin(xidout(transactionid)),classid::regclass,objid,objsubid,     
string_agg(     
'Gp_Segment_Id: '||case when gp_segment_id is null then 'NULL' else gp_segment_id::text end||chr(10)||   
'MppIsWriter: '||case when mppiswriter is null then 'NULL' when mppiswriter is true then 'TRUE' else 'FALSE' end||chr(10)||   
'MppSessionId: '||case when mppsessionid is null then 'NULL' else mppsessionid::text end||chr(10)||   
'ProcPid: '||case when procpid is null then 'NULL' else procpid::text end||chr(10)||   
'Pid: '||case when pid is null then 'NULL' else pid::text end||chr(10)||     
'Lock_Granted: '||case when granted is null then 'NULL' when granted is true then 'TRUE' else 'FALSE' end||' , Mode: '||case when mode is null then 'NULL' else mode::text end||' , Waiting_Reason: '||case when waiting_reason is null then 'NULL' else waiting_reason::text end||chr(10)||     
'Username: '||case when usename is null then 'NULL' else usename::text end||' , Database: '||case when datname is null then 'NULL' else datname::text end||' , Client_Addr: '||case when client_addr is null then 'NULL' else client_addr::text end||' , Client_Port: '||case when client_port is null then 'NULL' else client_port::text end||' , Application_Name: '||case when application_name is null then 'NULL' else application_name::text end||chr(10)||      
'Xact_Start: '||case when xact_start is null then 'NULL' else xact_start::text end||' , Query_Start: '||case when query_start is null then 'NULL' else query_start::text end||' , Xact_Elapse: '||case when (now()-xact_start) is null then 'NULL' else (now()-xact_start)::text end||' , Query_Elapse: '||case when (now()-query_start) is null then 'NULL' else (now()-query_start)::text end||chr(10)||      
'SQL (Current SQL in Transaction): '||chr(10)||    
case when current_query is null then 'NULL' else current_query::text end,      
chr(10)||'--------'||chr(10)      
order by      
  (  case mode      
    when 'INVALID' then 0     
    when 'AccessShareLock' then 1     
    when 'RowShareLock' then 2     
    when 'RowExclusiveLock' then 3     
    when 'ShareUpdateExclusiveLock' then 4     
    when 'ShareLock' then 5     
    when 'ShareRowExclusiveLock' then 6     
    when 'ExclusiveLock' then 7     
    when 'AccessExclusiveLock' then 8     
    else 0     
  end  ) desc,     
  (case when granted then 0 else 1 end)    
) as lock_conflict    
from t_unionall     
group by     
locktype,datname,relation::regclass,page,tuple,textin(xidout(transactionid)),classid::regclass,objid,objsubid ;    

但是本案例通过这个方法,你会发现,只有未赋予的等待,没有已赋予的灵异事件。原因是这个查询没有反馈SEGMENT上的锁等待。查询的是GPDB主节点的pg_locks。

正常情况下通过这种方法很容易排查问题,灵异事件需要特殊对待。

灵异锁等待事件排查手段一 - 通过(gp_dist_random)在主节点发起请求,在所有segment节点单独执行

Greenplum提供了一个函数接口gp_dist_random,当调用这个函数时,会下发到所有segment执行。

gp_dist_random函数的参数是对象名,换句话说说,会在所有segment查询这个对象。在select子句中可以输入一些函数调用,也会下发到SEGMENT节点执行。

我们在GPDB的源码中,可以看到大量gp_dist_random的使用。

进入排查阶段。

假设digoal.test这张表的truncate被堵塞了,通过前面的锁SQL,没有找到堵塞对象。所以我们需要通过gp_dist_random接口,去SEGMENT里面找找锁堵塞的原因。

1、到所有segment执行,找到堵塞digoal.test的QUERY。

SQL如下,发现有大量的copy to stdou的查询,看样子是用户断开了master节点的COPY操作,但是SEGMENT节点的COPY还在继续。并且这个事务是2天前发起的,期间还不知道锁了多少其他对象呢。它就是堵塞digoal.test的罪魁祸首。

digoal=# select gp_execution_dbid(),   -- 返回segment的dbid,对应gp_segment_configuration.dbid里可以得到SEGMENT。  
        inet_server_addr(),  -- 这个并不是segment IP,这个函数没有下推  
        inet_server_port(),  -- 这个并不是segment PORT,这个函数没有下推  
        *    
      from gp_dist_random('pg_stat_activity')   -- 查询pg_stat_activity视图  
      where procpid in   
        ( select pid from gp_dist_random('pg_locks')   -- 查询pg_locks视图,并找到锁digoal.test的PID  
	     where relation='digoal.test'::regclass  
	);   
  
结果如下:  
  
-[ RECORD 1 ]----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
datid            | 17159  
datname          | digoal  
procpid          | 39312  
sess_id          | 80714  
usesysid         | 10  
usename          | digoal_user  
current_query    | COPY digoal.test_1_prt_p20170819 (xxx,xxx,xxx....) TO stdout IGNORE EXTERNAL PARTITIONS;  
waiting          | f  
query_start      | 2017-08-22 12:32:14.674691+08  
backend_start    | 2017-08-20 22:06:03.1238+08  
client_addr      |   
client_port      | -1  
application_name |   
xact_start       | 2017-08-20 22:06:03.129544+08  
waiting_reason   |   
.........  
-[ RECORD 8 ]----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
datid            | 17159  
datname          | digoal  
procpid          | 52074  
sess_id          | 80714  
usesysid         | 10  
usename          | digoal_user  
current_query    | COPY dw.t_zhuanpan_1_prt_p20170214 (xxx,xxx,xxx....) TO stdout IGNORE EXTERNAL PARTITIONS;  
waiting          | f  
query_start      | 2017-08-22 12:30:03.907998+08  
backend_start    | 2017-08-20 22:06:03.134764+08  
client_addr      |   
client_port      | -1  
application_name |   
xact_start       | 2017-08-20 22:06:03.157087+08  
waiting_reason   |   

2、查看SEGMENT配置,通过dbid字段和gp_execution_dbid可以匹配到对应的SEGMENT。

digoal=# select * from gp_segment_configuration where role='p';  
 dbid | content | role | preferred_role | mode | status | port |       hostname       |       address        | replication_port | san_mounts   
------+---------+------+----------------+------+--------+------+----------------------+----------------------+------------------+------------  
    1 |      -1 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |                  |   
    2 |       0 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    3 |       1 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    4 |       2 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    5 |       3 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    6 |       4 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    7 |       5 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    8 |       6 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
    9 |       7 | p    | p              | s    | u      | xxxx | xxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxx |             xxxx |   
(9 rows)  

3、观察一下这些PID都锁了哪些对象。

select gp_execution_dbid(), pid, relation::regclass, locktype, mode, granted  
  from gp_dist_random('pg_locks')   
  where pid in   
    (select pid from gp_dist_random('pg_locks') where relation='digoal.test'::regclass);  

4、杀死这些SEGMENT上的PROCPID。

注意,数据会重分布到所有节点后再执行pg_terminate_backend,所以pid很可能被重分布到另一个SEGMENT,然后调用pg_terminate_backend,因为PID不是当前SEGMENT的PID,所以根本杀不掉。

所以手段一仅仅适合排查问题,不能解决问题。

select gp_execution_dbid() dbid,   
       pg_terminate_backend(procpid)  -- 杀杀杀,但是数据会重分布到所有节点执行,因此不是当前节点的procpid也会被pg_terminate_backend  
from gp_dist_random('pg_stat_activity')   
where procpid in   
  (select pid from gp_dist_random('pg_locks') where relation='digoal.test'::regclass and granted);  
  
WARNING:  PID 77961 is not a PostgreSQL server process  (seg21 slice3 xxxxxxxxx:25453 pid=128069)  
 dbid | pg_terminate_backend   
------+----------------------  
   23 | f  
(1 row)  

灵异锁等待事件排查手段二 - 使用(gp_session_role=utility)直接连接SEGMENT

segment节点是不能直接连接的,需要设置一个参数,就可以连接了。

手段一帮助我们找到了持锁的segment,手段二则登陆对应的主机,直连segment去terminate对应的process。

PGOPTIONS="-c gp_session_role=utility" psql -d dbname -h hostname -p port -U user  

直连到SEGMENT后,可以在这里看到持锁的SQL,这个SQL是COY TO STDOU,显然是MASTER中断这个SQL后,而segment没有中断造成的。属于GPDB本身的BUG,需要修复。

digoal=# \x  
Expanded display is on.  
digoal=# select array_agg(' usename: '||usename||' datname: '||datname||' current_query: '||coalesce(current_query,'')||' xact_start: '||coalesce(xact_start,'1970-01-01')||' backend_start: '||coalesce(backend_start,'1970-01-01')) from pg_stat_activity where procpid in (select pid from pg_locks where relation='digoal.test'::regclass);  
-[ RECORD 1 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
array_agg | {" usename: digoal_user datname: digoal current_query: COPY digoal.test_1_prt_p20170213 (xxxx,xxxx......) TO stdout IGNORE EXTERNAL PARTITIONS; xact_start: 2017-08-20 22:06:03.157087+08 backend_start: 2017-08-20 22:06:03.134764+08"}  
  
  
postgres=# select usename,datname,xact_start,current_query from pg_stat_activity ;  
  usename  | datname  |          xact_start           |                                                                                             current_query                                                                              
                   
-----------+----------+-------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
-----------------  
 digoal_user | digoal      |                               | <IDLE>  
 digoal_user | digoal      | 2017-08-20 22:06:03.157087+08 | COPY digoal.test_1_prt_p20170203 (xxx,xxx,......) TO stdout IGNORE EXTE  
RNAL PARTITIONS;  
 digoal_user | digoal      |                               | <IDLE>  
 aurora    | postgres | 2017-08-22 11:59:43.652306+08 | select usename,datname,xact_start,current_query from pg_stat_activity ;  
 dw        | digoal      | 2017-08-22 11:59:37.1828+08   | INSERT INTO digoal.test\r                                                                                                                                                             
                   
                                                      : SELECT  * from xxx.xxx017082222115801;  
 dw        | digoal      |                               | <IDLE>  
(6 rows)  

杀掉所有segment的持锁会话后,恢复业务。

postgres=# select pg_terminate_backend(77961);  
 pg_terminate_backend   
----------------------  
 t  
(1 row)  

参考

《PostgreSQL 锁等待监控 珍藏级SQL - 谁堵塞了谁》

PGOPTIONS="-c gp_session_role=utility" psql -d dbname -h hostname -p port -U user  

https://www.postgresql.org/docs/8.2/static/runtime-config-developer.html

《Greenplum通过gp_dist_random('gp_id') 在所有节点调用某个函数》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
3天前
|
弹性计算 数据安全/隐私保护
【畅玩雾锁王国】阿里云一键部署雾锁王国/Enshrouded游戏服务端教程
【畅玩雾锁王国】阿里云一键部署雾锁王国/Enshrouded游戏服务端教程。本文将为您提供极简部署雾锁王国服务器的指引,「仅需轻点三次鼠标,即可完成开服」,和自己的朋友一起enjoy独立畅快的游戏体验。
77 0
|
3天前
|
SQL canal 算法
PolarDB-X最佳实践:如何设计一张订单表
本文主要内容是如何使用全局索引与CO_HASH分区算法(CO_HASH),实现高效的多维度查询。
|
3天前
|
消息中间件 NoSQL Kafka
云原生最佳实践系列 5:基于函数计算 FC 实现阿里云 Kafka 消息内容控制 MongoDB DML 操作
该方案描述了一个大数据ETL流程,其中阿里云Kafka消息根据内容触发函数计算(FC)函数,执行针对MongoDB的增、删、改操作。
|
3天前
|
存储 关系型数据库 分布式数据库
PolarDB-X最佳实践系列(五):使用通义千问和存储过程快速生成测试数据
我们在测试数据库性能的过程中,通常需要生成一批测试数据。 以前,一般要写一段程序或者脚本来完成这项工作,但现在是2024年啦!时代变了!
PolarDB-X最佳实践系列(五):使用通义千问和存储过程快速生成测试数据
|
3天前
|
自然语言处理 算法 关系型数据库
阿里云PAI大模型RAG对话系统最佳实践
本文为大模型RAG对话系统最佳实践,旨在指引AI开发人员如何有效地结合LLM大语言模型的推理能力和外部知识库检索增强技术,从而显著提升对话系统的性能,使其能更加灵活地返回用户查询的内容。适用于问答、摘要生成和其他依赖外部知识的自然语言处理任务。通过该实践,您可以掌握构建一个大模型RAG对话系统的完整开发链路。
|
3天前
|
弹性计算 数据安全/隐私保护
【零成本】【懒人版】阿里云上雾锁王国/Enshrouded服务搭建教程
【零成本】【懒人版】雾锁王国/Enshrouded服务搭建教程。随着游戏行业的不断发展,玩家们对于游戏体验的要求也越来越高。为了满足玩家们的需求,腾讯云提供了游戏联机服务器一键部署方案,本文将为大家分享基于阿里云服务器10秒钟完成雾锁王国游戏服务器搭建教程,让大家的游戏体验更加顺畅。
|
3天前
|
弹性计算 数据安全/隐私保护
2024年雾锁王国/Enshrouded阿里云云主机搭建攻略
随着游戏行业的不断发展,玩家们对于游戏体验的要求也越来越高。为了满足玩家们的需求,阿里云提供了游戏联机服务器一键部署方案,本文将为大家分享基于阿里云服务器10秒钟完成雾锁王国游戏服务器搭建教程,让大家的游戏体验更加顺畅。
2024年雾锁王国/Enshrouded阿里云云主机搭建攻略
|
3天前
|
弹性计算 数据安全/隐私保护
2024年雾锁王国(Enshrouded)服务器怎么部署 阿里云上雾锁王国服务器部署方法
2024年雾锁王国(Enshrouded)服务器怎么部署 阿里云上雾锁王国服务器部署方法。雾锁王国(Enshrouded)作为一款热门多人在线游戏,为了给玩家提供稳定、流畅的联机体验,阿里云提供了高效便捷的快速部署解决方案,本文将为大家分享阿里云一键部署雾锁王国联机服务器详细教程。
|
3天前
|
机器学习/深度学习 人工智能 自然语言处理
|
3天前
|
存储 机器学习/深度学习 人工智能
基于Megatron-Core的稀疏大模型训练工具:阿里云MoE大模型最佳实践
随着大模型技术的不断发展,模型结构和参数量级快速演化。大模型技术的应用层出不穷。大模型展现惊人效果,但训练和推理成本高,一直是巨大挑战。模型稀疏化能降低计算和存储消耗。近期以Mixtral为代表的MoE(多专家混合)大模型证明了稀疏MoE技术能大幅降低计算量、提升推理速度,模型效果甚至超过同规模稠密模型。阿里云PAI和NVIDIA团队深入合作,基于Megatron-Core MoE框架,解决了MoE大模型训练落地时会遇到的可拓展性、易用性、功能性以及收敛精度等核心问题,在下游任务上取得了很好的模型效果。

相关产品

  • 云原生数据库 PolarDB