PostgreSQL 内核可观测性体系

news2024/11/24 14:36:15

文章目录

    • 背景
    • Metrics
      • pgstat
        • 指标展示
        • 指标统计相关的guc 参数
        • pgstat 实现
      • pg_stat_statements
        • 基本用法及指标内容
        • pg_stat_statements 实现
    • Trace
    • 总结

背景

PG 作为一个演讲30多年历史的TP 数据库,其复杂度极高,如果帮助DBA 在线上分析复杂查询的问题或者构建PG集群提前发现问题的能力,这一些需求都是对PG的易用性、可靠性、性能有至关重要的作用,所以PG也提供了一套自己的可观测性体系。

当然,可靠性以及性能 核心还是由那一些 commiter 保障的。
本篇涉及的PG代码版本是 REL_12_STABLE

可观测性主要体现在 M (metrics), T(Trace), L(Log) 三个方面,LOG这块大家都比较熟知了,也算是Trace 能力的一种体现,在LOG 方面不会展开太多,主要关注前两个 M 和 T。

Metrics

我们执行sql的时候想要查看它的执行计划 以及 计划内每一步的耗时,可以通过 explain analyze 达成这个功能,它们本身属于 Metrics的一部分。
查看 启动的 postgres 进程时可以看到 PG 维护了一个 stats collector 子进程,用于做stat 统计在这里插入图片描述

pgstat

pgstat 是PG 统计Metrics 的核心,主要是统计PG 各个组件累计的一些指标,其功能也被用于analyze 统计的实现;当然其提供的指标集选项中包括了静态指标 ,也有一些动态指标。

指标展示

pgstat 的指标集合 是由 前面 stats collector 进程收集统计 并且存放在共享内存中,对用户提供类似表形态的查询能力。

前面提到的动态指标,类似 pg_stat_activitypg_stat_progress_vacuum等能够动态得展示当前各个进程的行为 以及 vacuum当前运行到哪一步了;
比如查看 当前集群的每一个进程状态(除了 stats collector之外):
在这里插入图片描述
部分动态指标 以及其展示的内容 查看可以参考如下表:

view name功能描述
pg_stat_activity监控当前集群的每一个pg 子进程状态
pg_stat_replication有主从的场景,监控每一个wal sender进程,展示客户端信息以及发送状态相关的指标
pg_stat_wal_receiver展示主从场景 wal receiver 进程的状态信息
pg_stat_subscription展示处理订阅信息相关的进程指标

更多的动态指标可以参考 monitoring-stats。

除了动态指标,还有一些静态指标,比如从创建表到现在该表内部的各种指标信息(insert tuple的个数、update tupe的个数,活跃tuple的个数最后一次 vacuum的时间等等);
通过 select * from pg_stat_all_tables; 就都能看到了:

在这里插入图片描述
还有很多其他静态指标,table, sys table, database,vacuum 等都能看到:
在这里插入图片描述
这一些指标能够极大得帮助我们展示当前整个 PG 集群各个组件的状态,从而能够进一步分析当前集群的健康状态(比如 tables 中 dead tuple数量过多,是不是就可以主动调度一次 vacuum做清理,否则影响读性能等)。

指标统计相关的guc 参数

对于上文提到的这一些指标的统计,PG 提供了一些控制参数:

  • track_activities = on;开启对各个子进程运行状态的监控。
  • track_counts = on; 开启一些累计指标的计数功能。
  • track_functions = on; 开启对 UDF(user defined functions)的一些调用统计,主要用于 pg_stat_user_functions 的查询。
  • track_io_timing = on; 对阻塞式读写状态的监控;

这几个参数的权限都是 PGC_SUSET,即在进程运行过程中只有 超级用户才有权限通过 set命令设置(普通用户操作可能会影响整个集群的统计行为),其他的设置都只能写入 postgresql.conf中重启 server才能生效。

pgstat 实现

对于以上统计过程的实现整体比较简单的:

  1. 集群启动 postmaster 初始化 整个PG 所有的共享内存变量和信号量的时候会 对pgstat 存储统计信息的 BackendStatusArray 变量进行初始化,其是一个共享内存变量。
  2. 集群正常运行调度各个dml 的过程中 内部已经在做统计了。每一个backend的统计信息会放在从 BackendStatusArray中取出来的对应当前backend的变量中。比如我们调用 vacuum 命令的时候会调用 pgstat_progress_start_command以及pgstat_progress_start_command来做动态指标的统计;静态指标的统计则直接在 对应的执行链路插入统计count指标的函数方法,比如 heap_insert 完成tuple的插入之后就会执行 pgstat_count_heap_insert(relation, 1);
  3. 集群启动的时候也会启动 stats collector进程,该进程主要用于自动处理集群内部的的一些组件指标统计需求。比如 因脏页过多或者周期 启动了 checkpointer 进程来工作,则其main函数内部会调用pgstat_send_bgwriter发送指定的消息类型,请求 PgstatCollectorMain 中对应的指标统计函数进行指标收集存储。
  4. 用户在psql中想要读取这一些统计的指标,则会通过 pgstatfuncs.c 实现的众多函数来去从 共享内存变量中提取对应需要的数据。

对于 analyze 这样的功能,完成对sql的追踪过程之后,也是将完成了的统计信息发送给 stats collector做存储,供用户通过 pgstatfuncts 中的函数做访问分析。

pg_stat_statements

以上通过 pgstat 统计的信息是整个数据库的状态视图,包括其中的各个组件。这一些metrics能帮助我们提前发现整体的一些问题,但是如果我们想要优化 sql 性能,那也就意味着需要query级别的统计信息,显然pgstat 是没法提供query级别的统计信息的。
这个能力会由 PG 的插件 pg_stat_statements 来提供。

基本用法及指标内容

因为 pg_stat_statements 是作为插件来使用的,开启的话则需要做如下配置:

创建这个插件需要 pg_stat_statements.control文件的存在,因为我是本地编译源码使用的,所以需要做一些额外的配置。

  1. 完成PG 本身编译的情况下,进入 contrib/pg_stat_statements/ 直接 make && make install 生成pg_stat_statements.so 动态库。
  2. 编辑 postgresql.conf 文件,增加一行配置 shared_preload_libraries='pg_stat_statements' 标识启动时需要加载 前面编译好的动态库。
  3. PG 集群重启 ./bin/pg_ctl -D /Users/zhanghuigui/Desktop/work/source/postgres/build/data -l logfile restart
  4. psql 进入测试数据库,创建插件:create extension pg_stat_statements;
  5. 可以进行查询:select * from pg_stat_statements;,能够看到一些执行过的sql 指标,指标类型包括如下信息:
     userid 
     dbid
     queryid #唯一标识一个 query
     query # query的详细内容
     calls # 调用的次数
     total_time # 调用这么多次,执行的总时间
     min_time # 其中最小时间
     max_time
     mean_time
     stddev_time
     rows 
     shared_blks_hit # buffer cache 命中的次数
     shared_blks_read # buffer cache调度的读
     shared_blks_dirtied # 写buffer cache 产生了多少脏页
     shared_blks_written # 写了多个 page
     local_blks_hit # local buffer cache命中次数...
     local_blks_read
     local_blks_dirtied
     local_blks_written
     temp_blks_read
     temp_blks_written
     blk_read_time # 读io上消耗的时间
     blk_write_time
    

通过这一些信息能够看到一些query粒度的基本统计信息,其中比较关键的是query 在内存/io 上的一些指标,这样我们能够分析出这个query的性能是否合理,如果cache命中都很少而大多数都在io上面,则能够进一步做一些os/pg 本身的配置调整或者问题分析。

pg_stat_statements 实现

因为它本身统计的是query级别的 metrics,很明显的是需要需要嵌入到执行器的逻辑中才能拿到这一些信息,PG 提供了 hook functions,这一些钩子函数就像UDF (user defined function) 一样能够嵌入到执行器的逻辑中进行执行统计;统计的指标也是和 pgstat 一样存储到共享内存中。

在加载插件的时候会初始化对应的 hook function:
在这里插入图片描述
像是 ExecutorRun_hook 函数指针会被放在 执行器逻辑中调度,也就是pgss_ExecutorRun函数,其内还是会执行实际的standard_ExecutorRun函数,只是多了一些其他统计相关的逻辑:
在这里插入图片描述
指标的收集过程是在 执行器的 End部分,也就是 pgss_ExecutorEnd函数中,因为参数是 QueryDesc,其能够拿到整个链路执行时保存的结果,而实际执行的过程中保存的query级别的指标会放在 queryDesc->totaltime->bufusage 数据结构中。
BufferUsage的信息如下:

typedef struct BufferUsage
{
	long		shared_blks_hit;	/* # of shared buffer hits */
	long		shared_blks_read;	/* # of shared disk blocks read */
	long		shared_blks_dirtied;	/* # of shared blocks dirtied */
	long		shared_blks_written;	/* # of shared disk blocks written */
	long		local_blks_hit; /* # of local buffer hits */
	long		local_blks_read;	/* # of local disk blocks read */
	long		local_blks_dirtied; /* # of shared blocks dirtied */
	long		local_blks_written; /* # of local disk blocks written */
	long		temp_blks_read; /* # of temp blocks read */
	long		temp_blks_written;	/* # of temp blocks written */
	instr_time	blk_read_time;	/* time spent reading */
	instr_time	blk_write_time; /* time spent writing */
} BufferUsage;

其中每一个指标都是在实际访问对应的组件时统计的
回到 pgss_ExecutorEnd函数,主要是将 queryDesc->totaltime->bufusage 统计好的数据通过 pgss_store 保存到 pgss_hash 共享内存的 HTAB hash表中,会将当前query的 userid , dbid, queryid 作为hash key。

pgss_hash 中的数据会在集群停止的时候通过 pgss_shmem_shutdown 写入到 data/pg_stat_tmp 中的 .stat文件中,方便重新启动的时候继续将之前记录的统计信息加载到内存中。

当然,也提供了 统计条数的guc 配置:

  • pg_stat_statements.max 设置最大保存的统计条数,默认是5000
  • pg_stat_statements.track 设置 追踪那一些sql,默认是顶层查询,会忽略字查询的统计。如果想要追踪所有的查询,则需要配置为 all
  • pg_stat_statements.track_utility 开启对utility 模式执行的sql 状态的追踪
  • pg_stat_statements.save 开启 集群退出时保存 追踪记录的 stats

其实 PG 这里做的可扩展性很强,如果用户觉得当前query级别的统计信息还不够,那用户完全可以自定义自己的统计函数,不过就是需要在内核链路中增加类似 BufferUsage 这样的自定义指标类型, 还是需要对内核代码 尤其是 执行器部分要熟悉一些。

目前query 级别的统计信息缺失的一大部分是事务相关的统计;包括 pgstats 给到的一些指标与事务部分相关的基本没有;这里是 PG 12版本的指标,看了一下15版本,增加了更多的指标,包括:生成plan的时间,wal相关操作的 数据量 以及 count,还有 jit的各个阶段的时间。可能事务部分的统计对于PG 来说有办法拿到锁相关的统计信息就够了(pg_locks 表),不需要更多其他的状态了,所以现有的指标对于PG的用户以及内核开发者来说是足够的。

Trace

Trace 能力本身和 LOG 能力是类似的,保存sql 数据库内部发生的各个事件,提供一种能够追踪query 详细步骤的能力。只是 Trace本身功能会更为专注,使用上更为便捷,能够快速详细得展示一个sql 的关键步骤的耗时,方便我们快速排查问题。
Trace 能力本身其实主要应用在微服务场景,在众多的调度模块中增加 trace能力,从而能够专注于一个context的生命周期,通过trace 快速定位线上问题的根因;数据库组件其实相对较少,trace能力在数据库领域可能更专注于性能分析。

PG 作为传统关系型数据库,其实Trace 能力比较弱的,仅提供了通过 DTrace 进行分析的能力。

主要应该是没有需求,其利用前面的metrics + commiter本身的能力 😐 ,作为发现以及解决问题的手段就已经足够了,其实并不需要再引入 trace 能力了。

关于PG 的 DTrace 的使用简单介绍一下:

  1. 编译时开启 dtrace 选项, configure 的时候增加 --enable-dtrace 配置,编译完成之后就可以看到重新生成了一个 probe.h,里面的trace 桩都用 dtrace的接口重新写了一遍。

    #define	TRACE_POSTGRESQL_TRANSACTION_START(arg0) \
    do { \
    	__asm__ volatile(".reference " TRACE_POSTGRESQL_TYPEDEFS); \
    	__dtrace_probe$postgresql$transaction__start$v1$756e7369676e656420696e74(arg0); \
    	__asm__ volatile(".reference " TRACE_POSTGRESQL_STABILITY); \
    } while (0)
    
  2. probe.d 中能够找到支持 dtrace 的探针函数,如果不在probe.d中的话就无法探测了。

  3. 运行 PG 集群,并编写dtrace 探测脚本进行探测。

    #!/usr/sbin/dtrace -qs
    
    BEGIN
    {
      wal_insert = 0;
      printf ("%27s | %20s | %s\n", "STAGE", "TIME-ns", "WAL-INS-COUNT");
    }
    
    postgresql$1:::transaction-start
    {
      start_time = timestamp;
    }
    
    postgresql$1:::transaction-commit
    {
      printf("%10s | %20d | %d\n", "transaction-commit-duration",
          timestamp - start_time, wal_insert);
      wal_insert = 0;
    }
    
    postgresql$1:::transaction-abort
    {
      printf("%10s | %20d | %d\n", "transaction-abort-duration",
          timestamp - start_time, wal_insert);
      wal_insert = 0;
    }
    
    postgresql$1:::wal-insert
    {
      wal_insert ++;
    }
    

    这个脚本我探测的内容是 每一个事务从 start 到 commit/abort 的延时,并且统计在此期间写了多少次wal。
    运行的话:./test-drace.d $pid,指定psql 启动的backend 进程id即可,然后在进程id中做一些操作就能看到实时打印的结果。

    我是在mac上跑的脚本,dtrace运行时需要root权限

    最后的输出结果如下:

                          STAGE |              TIME-ns | WAL-INS-COUNT
    transaction-commit-duration |               698250 | 3
    transaction-abort-duration |           5139649500 | 3
    transaction-commit-duration |              1212917 | 2
    

当然大家如果想要添加自己的探针,也比较简单,在 probe.d 中增加自己的探针类型,增加一个对应的宏定义类型到 probe.h中,将这个类型放在自己想要统计的 内核代码中,最后重新编译这个探针就能用了。

关于postgresql 的 dtrace 使用有一些注意的地方:
使用者想要在线上用这个功能,需要对内核的行为 以及 dtrace的行为有一定的理解,不能在频繁调用的地方增加探针,否则会极大得拖慢应用的性能。

dtrace能做的,systemtap也能做,目前来看其实比dtrace更方便,至少不用增加probe重新编译,而是任意编译到binary的函数符号都能被systemtap追踪;只不过dtrace 在跨平台上支持的更好一些,能够在unix系统跑(mac),而systemtap仅能在linux上跑。

所以PG 社区并不建议 DBA 或者 用户去线上使用这个功能,这个能力本身就是为内核开发者来做调试用的,大家也能看出来 PG 本身的 trace能力对于普通用户来说还是比较弱的,对于社区的开发者来说当前的dtrace非常灵活且可配置,已经使用习惯了,完全不需要外部用户考虑这方面。

总结

总的来说 PG 社区的可观测性能力是不错的,不过在Trace 这方面还是比较欠缺(菜鸟角度),对于想要基于PG做开发的数据库来说这方面的能力需要独立建设。
当然业界已经有比较成熟的trace 体系,像是 opentracing 以及 dapper 这种支持分布式trace的架构,能够极大得提升复杂系统排查问题的效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/58338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nexus私服(二)

(一) SpringBoot配置Maven发布至Nexus私服 Nexus默认会带有四常用个仓库,maven-central、maven-public、maven-releases、maven-snapshotsmaven-central类型是proxy代理类型,地址是指向maven中央仓库地址https://repo1.maven.org/maven2/maven-releases、…

空间复杂度(Space Complexity)

预计阅读时间:10分钟 一、简介 随着计算机空间的发展,空间复杂度逐渐变得不那么重要了,但它在比赛中仍然存在。 推导方法: 用常数1取代运行时间中的所有加法常数。 在修改后的运行次数函数中,只保留最高阶项。…

[附源码]Python计算机毕业设计Django旅游网的设计与实现

项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等等。 环境需要 1.运行环境:最好是python3.7.7,…

2013-2020年全国31省数字经济信息化基础数据

2013-2020年全国31省数字经济信息化基础 1、时间:2013-2020年 2、来源:国家统计J和统计NJ 3、范围:31省 4、指标包括: "光缆线路长度(公里)、移动电话基站(万个)、信息传输、软件和信息技术服务业…

基于模糊BP神经网络轨迹跟踪(Matlab代码实现)

👨‍🎓个人主页:研学社的博客 💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜…

【VUE Cli脚手架软件安装篇】

目录 1. 搭建开发环境 1.1. 安装Node.js 1.2. 配置npm源 2. 安装VUE Cli 3. 创建第1个VUE CLI工程 3.1. 创建工程 3.2. 启动服务 3.3. 停止服务 3.4. 重启服务 1. 搭建开发环境 1.1. 安装Node.js 首先需要下载Node.js安装包,下载地址可参考:ht…

【数据结构】顺序表的概念及实现

顺序表1、顺序表概念2、初始化顺序表3、销毁顺序表4、判断顺序表是否为空5、打印顺序表6、检查顺序表的容量(同时充当扩容任务)7、顺序表的尾插8、顺序表的头插9、顺序表的尾删10、顺序表的头删11、查找顺序表中某个数的位置12、在顺序表pos位置插入数字…

光环:元宇宙概念及生态发展现状与研判——张子良

摘要:文章内容主要来源于光环国际2022年第三届中国科创者大会张子良老师的分享,原分享名称为"元宇宙行业应用实践探索"。讲述了元宇宙的理论知识、元宇宙生态圈及当前发展的情况。提出来看一个概念是否相同可以从引入时间、内容两个方面去界定…

【用python的标准库画出显示实时时间的数码管】

🤵‍♂️ 个人主页老虎也淘气 个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏…

Java项目:SSM电影售票管理系统

作者主页:源码空间站2022 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 管理员角色包含以下功能: 管理员登陆,管理员用户管理,新闻公告增删改查,电影类型增删改查,影院信息增删改查,电影信息增删改查,订单…

计算机网络学习笔记(II)——应用层

文章目录第二章—应用层2.1、应用层原理网络应用的体系结构客服—服务器(C/S)体系结构对等体(P2P)体系结构C/S和P2P体系的混合结构进程通信分布式进程通信需要解决的问题应用层协议Internet传输层提供的服务UDP存在的必要性2.2、W…

【Android插件化框架】插件APK中的动态代理

在 Android 中实现插件化框架,需要解决的问题主要如下: 资源和代码的加载Android 生命周期的管理和组件的注册宿主 APK 和插件 APK 资源引用的冲突解决 下面分析几个目前主流的开源框架 DL 动态加载框架 ( 2014 年底) 是基于代理的方式实现插件框架&…

渗透测试 | APP信息收集

0x00 免责声明 本文仅限于学习讨论与技术知识的分享,不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本文作者不为此承担任何责任,一旦造成后果请自行承担…

【学习笔记76】认识ajax和ajax的请求

一、认识前后端交互 1、前后端交互 前端向后端发送请求, 索要数据因为前端没有办法存储大量数据, 所以数据都存储在后端当前端需要数据时, 需要向后端发送请求, 得到想要的数据 2、什么是ajax ajax全名async javascript and XML(异步JavaScript和XML)是前后台交互的能⼒&#…

手摸手带你撸一个拖拽效果

目录 前言 准备 创建所需要结构 编写样式 js编写拖拽效果 解释方法 所有代码 结尾 前言 最近看见一个拖拽效果的视频,看好多人评论说跟着敲也没效果,还有就是作者也不回复大家提出的一些疑问,本着知其然必要知其所以然的心理&#xf…

【疯狂世界杯】css 动画实现跳动的足球

📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言📝…

python T检验

T检验通常分为三种:单样本T检验、双样本T检验、配对样本T检验原理可以参考:一文详解t检验本文主要介绍使用python实现T检验的过程,内容主要是参考这篇博文:利用python库stats进行t检验 文章目录一、单样本T检验二、独立样本t检验&…

二、Git本地仓库基本操作——创建Git仓库、提交更新或删除文件

1. 创建本地工作仓库 创建本地工作仓库有两种方法: git init 在本地初始化一个git仓库git clone 直接克隆一个远程的git仓库 方法一: 我们在其中一个目录下,点击鼠标右键,然后启动git bash。输入下面命令: git in…

【实战案例】Python 信用卡欺诈检测其实特简单

当我们在网上购买产品时,很多人喜欢使用信用卡。但信用卡欺诈常常会在身边发生,网络安全正成为我们生活中至关重要的一部分。 为了解决这个问题,我们需要利用机器学习算法构建一个异常行为的识别系统,如果发现可疑,中…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校饭堂管理系统8gmjo

这个选题的话其实有很多的,就看你自己能接受怎么样的,比如可以做网站类、系统类、小程序类、安卓app、大数据类等等,这个也要看你个人能力和技术问题,如果技术小白或者有一点点基础的话建议选择网站类和系统类的,如果有…