分布式死锁检测
分布式死锁检测
项目目的
本文介绍如何扩展 PostgreSQL 的锁系统,以多种方式(例如,FDW、libpq 和 dblink)表示包含在远程数据库服务器上运行的事务的等待图。
这可以检测由远程事务引起的死锁,这些死锁是由远程事务链形成的等待图循环造成的。
使用 FDW 产生全局死锁
我们可以使用现有的 FDW 很容易地产生全局死锁。
假设我们有两个服务器:server1 和 server2。两个服务器都拥有表 t1 (c int)
。
从 server1 看,server2 的表 t
被视为 svr2_t
,反之亦然,从 server2 看,server1 的表 t
被视为 svr1_t
。
然后我们可以按如下方式产生全局死锁
server1
BEGIN;
LOCK TABLE t IN ACCESS EXCLUSIVE MODE;
server2
BEGIN;
LOCK TABLE t in ACCESS EXCLUSIVE MODE;
server1
SELECT * FROM svr2_t;
server2
SELECT * FROM svr1_t;
如何表示包含远程事务的等待图
PostgreSQL 本身使用各种内部函数(主要在 lock.c
中)来表示对象级锁。这用于在事务无法在 deadlock_timeout
GUC 参数定义的时间范围内获取锁时追踪等待图(在 deadlock.c
中实现)。
我们可以扩展它来表示等待远程事务完成的事务的状态。
外部锁
在这里,我们在 lock.h
中定义了一种新的锁标签类型 LOCKTAG_EXTERNAL
,用来表示获取此锁的事务正在等待远程事务。因为这只有等待由其调用的远程事务(下游事务)完成的事务(上游事务)持有,所以数据库中运行上游事务的其他事务并不关心此锁。此锁仅以独占模式获取,并且仅由上游事务持有(并且,在某种程度上,上游事务也在等待此锁完成)。
锁标签信息类似于其他锁类型,包含对附加属性的引用信息,如下所述。为此,调用远程事务的应用程序应通过调用 ExternalLockAcquire()
来获取外部锁。这是一个对 LockAcquireExtended()
的包装,并为外部锁设置锁标签。
外部锁属性
因为我们使用此锁来跟踪等待图,所以我们需要另一个属性信息来连接到下游事务运行的远程数据库并跟踪等待图。
这是数据库的连接字符串和下游事务的后端 ID。
在 lock.c
中有新函数 ExternalLockSetProperties()
。为了帮助精确跟踪等待图并处理远程事务状态的更改,此函数需要连接字符串、远程事务 pgprocno、pid 和 xid。
死锁检测
死锁检测机制使用 deadlock.c
中现有的死锁检测代码,并进行了扩展。
当调用死锁检测(DeadLockCheck()
)时,它开始跟踪等待图。在检查过程中,当 DeadLockcheck()
在 PGPROC
的等待锁中找到外部锁时,它开始跟踪此外部锁表示的远程事务,以构建全局等待图。重复此过程,直到它找到全局等待图终止或返回到形成循环的原始上游事务。
添加了专用函数来执行此检查。
外部锁跟踪期间的 LWLocks
在本地等待图跟踪中,所有 LWLocks 都由死锁检查函数获取,以简化跟踪代码。在全局等待图跟踪中,我们在本地等待图跟踪期间获取所有 LWLocks。当它转到检查远程数据库中更远的等待图时,这些 LWLocks 都被释放,以便其他事务可以在远程等待图跟踪期间运行,而这个过程可能很耗时。当找到循环时,参与全局等待图循环的所有数据库将检查其等待图的本地部分是否稳定。如果不是,则意味着参与此等待图的至少一项事务正在运行,这不是死锁。如果稳定,则我们确定这是一个死锁。在检查本地等待图稳定性的过程中,我们再次在本地获取所有 LWLocks。
应用应该做什么
应用程序(或扩展,无论是什么),应该在调用远程事务之前调用两个函数。
ExternalLockAcquire()
ExternalLockSetProperties()
应用程序不需要释放外部锁来遵循两阶段锁定协议。外部锁将在事务结束时作为清理过程的一部分被释放。因为全局锁检查将在后台进行,所以应用程序根本不需要关心这一点。应用程序只需获取锁并提供信息来跟踪等待图。
外部锁属性
因为当前的锁结构太小而无法获取上面描述的锁属性,所以我们需要额外的空间来保存它们。在当前的实现中,我们将此保存在 $PGDATA/pg_external_locks
中的文件中。文件名基于锁标签中的值。为了简化实现,外部锁属性以纯文本形式写入,这可能需要更多改进以提高安全性。外部锁属性可以存储在其他地方,例如动态共享内存。
当前状态
该代码现在正在 PG 14 中运行。您可以从 https://github.com/koichi-szk/postgres.git
自由克隆仓库。请检出分支 koichi/global_deadlock_detection_14_0
。
因为我们没有实际的工作负载来测试此功能,所以我创建了一个单独的 git 仓库,其中包含测试环境和一些有用的测试功能。您可以访问仓库 https://github.com/koichi-szk/gdd_test.git
。请检出分支 PG14_GDD
。请注意,此仓库依赖于我的本地环境配置,您需要为自己的环境进行配置。
未来工作
因为没有实际的工作负载会导致 PG 的全局死锁,所以我会继续将此代码移植到更高级别的版本,以便在真正需要此功能时将其添加到 PG 本身中。