Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

27.5. 动态跟踪 #

27.5.1. 为动态跟踪编译
27.5.2. 内置探针
27.5.3. 使用探针
27.5.4. 定义新探针

PostgreSQL 提供了支持数据库服务器动态跟踪的功能。这允许在代码中的特定点调用外部实用程序,从而跟踪执行。

PostgreSQL 的源代码中已经插入了一些探针或跟踪点。这些探针旨在供数据库开发人员和管理员使用。默认情况下,探针不会被编译到PostgreSQL 中;用户需要明确告知配置脚本以启用这些探针。

目前,DTrace 实用程序得到支持,在本文撰写之时,它适用于 Solaris、macOS、FreeBSD、NetBSD 和 Oracle Linux。适用于 Linux 的 SystemTap 项目提供了一个等效于 DTrace 的工具,也可以使用它。通过更改 src/include/utils/probes.h 中宏的定义,从理论上讲,可以支持其他动态跟踪实用程序。

27.5.1. 针对动态跟踪编译 #

默认情况下,探针不可用,因此您需要明确告知配置脚本以启用PostgreSQL 中的探针。若要包含 DTrace 支持,请在配置中指定 --enable-dtrace。有关更多信息,请参见 第 17.3.3.6 节

27.5.2. 内置探针 #

源代码中提供了许多标准探针,如 表 27.49 所示;表 27.50 显示了探针中使用的类型。当然,可以添加更多探针来增强 PostgreSQL 的可观察性。

表 27.49. 内置 DTrace 探针

名称 参数 描述
transaction-start (LocalTransactionId) 在新事务开始时触发的探针。参数 0 是事务 ID。
transaction-commit (LocalTransactionId) 在事务成功完成时触发的探针。参数 0 是事务 ID。
transaction-abort (LocalTransactionId) 在事务未成功完成时触发的探针。参数 0 是事务 ID。
query-start (const char *) 在查询处理开始时触发的探针。参数 0 是查询字符串。
query-done (const char *) 在查询处理完成时触发的探针。参数 0 是查询字符串。
query-parse-start (const char *) 在查询解析开始时触发的探针。参数 0 是查询字符串。
query-parse-done (const char *) 在查询解析完成时触发的探针。参数 0 是查询字符串。
query-rewrite-start (const char *) 查询改写开始时触发的探测。arg0 是查询字符串。
query-rewrite-done (const char *) 查询改写完成时触发的探测。arg0 是查询字符串。
query-plan-start () 查询计划开始时触发的探测。
query-plan-done () 查询计划完成时触发的探测。
query-execute-start () 查询执行开始时触发的探测。
query-execute-done () 查询执行完成时触发的探测。
statement-status (const char *) 服务器进程更新其 pg_stat_activity.status 时的探测。arg0 是新的状态字符串。
checkpoint-start (int) 检查点开始时触发的探测。arg0 保存用于区分不同检查点类型的位标志,如关机、立即或强制。
checkpoint-done (int, int, int, int, int) 检查点完成时触发的探测。(在检查点处理期间,列出的探测按顺序触发。)arg0 是所写缓冲区的数量。arg1 是缓冲区的总数。arg2、arg3 和 arg4 分别包含已添加、已移除和已回收的 WAL 文件的数量。
clog-checkpoint-start (bool) 检查点的 CLOG 部分开始时触发的探测。对于正常检查点,arg0 为真;对于关机检查点,arg0 为假。
clog-checkpoint-done (bool) 检查点的 CLOG 部分完成时触发的探测。arg0 的含义与 clog-checkpoint-start 相同。
subtrans-checkpoint-start (bool) 检查点的 SUBTRANS 部分开始时触发的探测。对于正常检查点,arg0 为真;对于关机检查点,arg0 为假。
subtrans-checkpoint-done (bool) 检查点的 SUBTRANS 部分完成时触发的探测。arg0 的含义与 subtrans-checkpoint-start 相同。
multixact-checkpoint-start (bool) 检查点的 MultiXact 部分开始时触发的探测。对于正常检查点,arg0 为真;对于关机检查点,arg0 为假。
multixact-checkpoint-done (bool) 检查点的 MultiXact 部分完成时触发的探测。arg0 的含义与 multixact-checkpoint-start 相同。
buffer-checkpoint-start (int) 检查点的缓冲区写部分开始时触发的探测。arg0 保存用于区分不同检查点类型的位标志,如关机、立即或强制。
buffer-sync-start (int, int) 我们开始在检查点期间写脏缓冲区时触发的探测(在确定必须写哪个缓冲区之后)。arg0 是缓冲区的总数。arg1 是当前脏的需要写的缓冲区数量。
buffer-sync-written (int) 在检查点时期写入每个缓冲区后触发 Probe。arg0 是缓冲区的 ID 编号。
buffer-sync-done (int, int, int) 在所有脏缓冲区都写入后触发的 Probe。arg0 是缓冲区的总数量。arg1 是检查点进程实际写入的缓冲区数量。arg2 是预期写入的数量(arg1 of buffer-sync-start);任何差异都反映了在检查点期间其它进程刷新缓冲区。
buffer-checkpoint-sync-start () 在脏缓冲区写入到内核且在启动 fsync 请求之前触发的 Probe。
buffer-checkpoint-done () 在缓冲区同步到磁盘完成时触发的 Probe。
twophase-checkpoint-start () 在检查点的两阶段部分启动时触发的 Probe。
twophase-checkpoint-done () 在检查点的两阶段部分完成后触发的 Probe。
buffer-extend-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int) 在关系扩展启动时触发的 Probe。arg0 包含要扩展的 fork。arg1、arg2 和 arg3 包含表空间、数据库和关系 OID,用于标识关系。arg4 是创建本地缓冲区的临时关系的后端 ID,或者对于共享缓冲区而言是 INVALID_PROC_NUMBER (-1)。arg5 是调用者想要扩展的块数量。
buffer-extend-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int, BlockNumber) 在关系扩展完成后触发的 Probe。arg0 包含要扩展的 fork。arg1、arg2 和 arg3 包含表空间、数据库和关系 OID,用于标识关系。arg4 是创建本地缓冲区的临时关系的后端 ID,或者对于共享缓冲区而言是 INVALID_PROC_NUMBER (-1)。arg5 是关系扩展的块数量,这可能小于 buffer-extend-start 中的数量,原因是资源约束。arg6 包含第一个新块的 BlockNumber。
buffer-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 在缓冲区读取启动时触发的 Probe。arg0 和 arg1 包含页面的 fork 和块编号。arg2、arg3 和 arg4 包含表空间、数据库和关系 OID,用于标识关系。arg5 是创建本地缓冲区的临时关系的后端 ID,或者对于共享缓冲区而言是 INVALID_PROC_NUMBER (-1)。
buffer-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) 在读取缓冲区完成时触发的探针。arg0 和 arg1 包含页面分叉和块编号。arg2、arg3 和 arg4 包含表空间、数据库和关系 OID,用于标识该关系。arg5 是为本地缓冲区创建临时关系的后端 ID,或共享缓冲区的 INVALID_PROC_NUMBER (-1)。如果在池中找到该缓冲区,arg6 为 true,否则为 false。
buffer-flush-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 针对共享缓冲区发出任何写入请求之前触发的探针。arg0 和 arg1 包含页面分叉和块编号。arg2、arg3 和 arg4 包含表空间、数据库和关系 OID,用于标识该关系。
buffer-flush-done (ForkNumber, BlockNumber, Oid, Oid, Oid) 在写入请求完成后触发的探针。(请注意,这只是将数据传递到内核所需的时间;它通常尚未实际写入到磁盘。)参数与 buffer-flush-start 相同。
wal-buffer-write-dirty-start () 由于没有更多 WAL 缓冲区空间,当服务器进程开始写入脏 WAL 缓冲区时触发的探针。(如果这种情况经常发生,则表示 wal_buffers 太小。)
wal-buffer-write-dirty-done () 在脏 WAL 缓冲区写入完成时触发的探针。
wal-insert (unsigned char, unsigned char) 在插入 WAL 记录时触发的探针。arg0 是该记录的资源管理器 (rmid)。arg1 包含信息标志。
wal-switch () 在请求 WAL 段切换时触发的探针。
smgr-md-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 在开始从关系中读取块时触发的探针。arg0 和 arg1 包含页面分叉和块编号。arg2、arg3 和 arg4 包含表空间、数据库和关系 OID,用于标识该关系。arg5 是为本地缓冲区创建临时关系的后端 ID,或共享缓冲区的 INVALID_PROC_NUMBER (-1)。
smgr-md-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) 在读取块完成后触发的探针。arg0 和 arg1 包含页面分叉和块编号。arg2、arg3 和 arg4 包含表空间、数据库和关系 OID,用于标识该关系。arg5 是为本地缓冲区创建临时关系的后端 ID,或共享缓冲区的 INVALID_PROC_NUMBER (-1)。arg6 为实际读取的字节数,而 arg7 为请求的字节数(如果它们不同,则表示读取过短)。
smgr-md-write-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 当开始向一个关系中写入块时激活的探针。arg0 和 arg1 包含页面的 fork 和块编号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端 ID,或共享缓冲区的 INVALID_PROC_NUMBER (-1)。
smgr-md-write-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) 块写入完成后激活的探针。arg0 和 arg1 包含页面的 fork 和块编号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端 ID,或共享缓冲区的 INVALID_PROC_NUMBER (-1)。arg6 是实际写入的字节数,arg7 是请求的字节数(如果不同,表示写入失败)。
sort-start (int, bool, int, int, bool, int) 启动排序操作时激活的探针。arg0 表示堆、索引或基元排序。arg1 对于唯一值强制实施为 true。arg2 是键列数。arg3 是允许的工作内存的千字节数。arg4 是在需要对排序结果进行随机访问时为 true。arg5 在 0 时表示串行,在 1 时表示并行工作进程,在 2 时表示并行领导。
sort-done (bool, long) 排序完成后激活的探针。arg0 对于外部排序为 true,对于内部排序为 false。arg1 是用于外部排序的磁盘块数,或用于内部排序的内存千字节数。
lwlock-acquire (char *, LWLockMode) 获取 LWLock 时激活的探针。arg0 是 LWLock 的片。arg1 是请求的锁模式,独占或共享。
lwlock-release (char *) 释放 LWLock 时激活的探针(但请注意,任何已释放的等待者尚未唤醒)。arg0 是 LWLock 的片。
lwlock-wait-start (char *, LWLockMode) 当 LWLock 不可立即使用且服务器进程已开始等待锁可用时激活的探针。arg0 是 LWLock 的片。arg1 是请求的锁模式,独占或共享。
lwlock-wait-done (char *, LWLockMode) 当服务器进程从等待 LWLock 中释放时激活的探针(它实际上还没有该锁)。arg0 是 LWLock 的片。arg1 是请求的锁模式,独占或共享。
lwlock-condacquire (char *, LWLockMode) 当呼叫方指定不等待时,该探测器会在成功获取 LWLock 后触发。arg0 是 LWLock 的分区。arg1 是请求的锁模式,可以是独占模式或共享模式。
lwlock-condacquire-fail (char *, LWLockMode) 当呼叫方指定不等待时,该探测器会在未成功获取 LWLock 后触发。arg0 是 LWLock 的分区。arg1 是请求的锁模式,可以是独占模式或共享模式。
lock-wait-start (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) 当对重量级锁(lmgr 锁)的请求开始等待(因为锁不可用)时,该探测器会触发。arg0 至 arg3 是标识被锁定的对象的标记字段。arg4 指示被锁定的对象类型。arg5 指示请求的锁类型。
lock-wait-done (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) 在对重量级锁(lmgr 锁)的请求完成等待(即获取锁)时,该探测器会触发。参数与 lock-wait-start 相同。
deadlock-found () 当死锁检测器发现死锁时,该探测器会触发。

表 27.50. 探测器参数中使用的已定义类型

类型 定义
LocalTransactionId unsigned int
LWLockMode int
LOCKMODE int
BlockNumber unsigned int
Oid unsigned int
ForkNumber int
bool unsigned char

27.5.3. 使用探测器 #

以下示例展示了一个 DTrace 脚本,用于分析系统中的事务计数,作为在性能测试前和后对 pg_stat_database 进行快照的替代方案

#!/usr/sbin/dtrace -qs

postgresql$1:::transaction-start
{
      @start["Start"] = count();
      self->ts  = timestamp;
}

postgresql$1:::transaction-abort
{
      @abort["Abort"] = count();
}

postgresql$1:::transaction-commit
/self->ts/
{
      @commit["Commit"] = count();
      @time["Total time (ns)"] = sum(timestamp - self->ts);
      self->ts=0;
}

在执行时,示例 D 脚本给出的输出类似于

# ./txn_count.d `pgrep -n postgres` or ./txn_count.d <PID>
^C

Start                                          71
Commit                                         70
Total time (ns)                        2312105013

注意

SystemTap 使用与 DTrace 不同的符号为跟踪脚本命名,尽管底层的跟踪点是兼容的。值得注意的是,在撰写本文时,SystemTap 脚本必须使用双下划线代替连字符来引用探测器名称。预计此问题会在未来的 SystemTap 版本中得到解决。

您应该记住,DTrace 脚本需要谨慎编写并调试,否则收集到的跟踪信息可能毫无意义。在发现问题的大多数情况下,都是检测机制有故障,而不是基础系统。在讨论使用动态跟踪发现的信息时,请务必附上用于允许检查和讨论该信息所使用的脚本。

27.5.4. 定义新探测器 #

开发人员可以在需要时在代码中定义新探针,但是这将需要重新编译。以下是如何插入新探针的步骤

  1. 确定探针名称和要通过探针提供的数据

  2. 将探针定义添加到 src/backend/utils/probes.d

  3. 如果它还没有出现在包含探测点模块之中,则将 pg_trace.h 包括进来,并在源代码中的所需位置插入 TRACE_POSTGRESQL 探测宏

  4. 重新编译并验证新探针是否可用

示例:以下是如何添加一个探针以通过事务 ID 跟踪所有新事务的示例。

  1. 决定该探针将被命名为 transaction-start ,并需要 LocalTransactionId 类型的一个参数

  2. 将探针定义添加到 src/backend/utils/probes.d

    probe transaction__start(LocalTransactionId);
    

    注意在探针名称中使用双下划线。在使用探针的 DTrace 脚本中,双下划线需要替换为连字符,因此 transaction-start 是要为用户编制文档的名称。

  3. 在编译时,transaction__start 转换为一个称为 TRACE_POSTGRESQL_TRANSACTION_START 的宏(注意这里下划线是单下划线),通过包含 pg_trace.h 可以获得该宏。将宏调用添加到源代码中的适当位置。在这种情况下,它看起来如下

    TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
    
  4. 在重新编译并运行新二进制文件后,通过执行以下 DTrace 命令来检查是否添加的新探针可用。您会看到类似的输出

    # dtrace -ln transaction-start
       ID    PROVIDER          MODULE           FUNCTION NAME
    18705 postgresql49878     postgres     StartTransactionCommand transaction-start
    18755 postgresql49877     postgres     StartTransactionCommand transaction-start
    18805 postgresql49876     postgres     StartTransactionCommand transaction-start
    18855 postgresql49875     postgres     StartTransactionCommand transaction-start
    18986 postgresql49873     postgres     StartTransactionCommand transaction-start
    

向 C 代码中添加跟踪宏时需要注意一些事项

  • 您应该注意为探针的参数指定的数据类型与宏中使用的变量的数据类型匹配。否则,您将会得到编译错误。

  • 在大多数平台上,如果 PostgreSQL 是使用 --enable-dtrace 构建的,则在每次控件通过宏时都将计算跟踪宏的参数,即使没有进行任何跟踪。如果您只是报告几个本地变量的值,则通常不必担心这一点。但请注意不要在参数中加入昂贵函数调用。如果您需要这样做,请考虑使用一项检查来保护宏,以查看该跟踪是否真的已启用

    if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
        TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));
    

    每个跟踪宏都有一个对应的 ENABLED 宏。