开发者学堂课程【PolarDB for PostgreSQL 内核解读系列课程:【视频】PostgreSQL 体系结构】学习笔记,与课程紧密连接,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/1042/detail/15316
PostgreSQL 体系结构
内容介绍
一. 系统表
二. 启动流程
三. 查询流程
四. 辅助进程
继续讲解 postgre 数据库内核解读的系列教程,这是第二节课 postgre 的体系结构,这节主要介绍系统表,初始化,启动还有查询的流程,之后会介绍一些辅助进程。
本课程计划分15个章节,目前是第二章节体系结构。这部分介绍完后,后面的章节会介绍存储引擎,SQL 引擎,执行引擎,另外就是并化控制,复制和一些分布式的技术,现在是基本流程整体架构的介绍。
一.系统表
第一部分讲解系统表,系统表是什么?可以看到这是Wiki上对于系统表的定义,系统表又叫database catalog,或者叫metadata原数据。
在SQL的标准里把元素句叫做information schema,所以无论是oracle还是SQL server MySQL都是有information的schema。这个schema是要依据于SQL标准来定义的,information schema可以看到它是ANSI的这个标准,是美国国家标准协会出的相关标准,SQL92也是ANSI出的标准。它怎么定义information schema?它就听tables, view ,columns,procedures,database这些信息全都是在information里进行一个定义来便于它使用,这些都是以view的形式存在,并且是一个read only的view。
对于PG来说也有这个系统表,首先PG有一些system的catalogs,最常用的比方PG class表,PG pro表,在这些表基础上会创建一些视图,这些视图分两类,一类就是这种system_views.sql创建出来的视图,这些视图主要就是用来便于提取和访问system catalog。它主要是基于system catalog创建的一些视图,信息都是在system catalog里面,information schema是依据SQL标准,可以认为information schema.SQL定义的视图,跟system catalog里是有重复和冲突的地方,但是information schema.SQL,它的名字还有一些用法都是更标准一些,而system_views.sql有PG独有的一些能力。
为什么要提右上PolarDB的这个图,主要是因为PolarDB本身mySQL的版本可以看到是有InnoDB和X-Engine两个引擎,这两个引擎之间最重要的就是会通过系统表来进行一个关联,所以system catalog是非常重要的。
接着介绍 postgre 系统表间的关系,这里可以看到最常用的pg_database,这些都属于系统表不是视图。pg class 是保存一些表和view的一些信息。Index还有language,proc,其他一些过程语言的支持。包括trigger,包括这个过程,定义,类型,可以看到都在引用类型,因为每个里面都会包含一些类型信息。
包括constraint相关的约束的信息,所以这里可以看到它这的系统表是比较复杂的,而系统表就是在设计一个系统时,最先需要去设计的一个数据结构。需要把整个的系统除了数据之外,要将原数据设计好保存好,这是设计一个数据库系统最先要做的事情。
之前也设计过图数据库,图数据库跟关型数据库不一样,它有顶点。它的顶点之间有边,顶点和边都有对应的属性信息,这些信息在关系里面,怎么将图数据库的信息保存起来,并快速的保存和定位信息,这就是要设计图数据库和关型数据库做一个多模数据,需要考虑的问题。这个当时也是设计了很久,关于如何把图数据库转到关系里面做多模。
再介绍一下这个系统表,比方通过\d的方法,可以看到加这个表的名字就可以显示出系统表的信息,那通过这种方式可以去看这个系统表,还可以怎么去学习这个系统表?通过pg的手册,手册里对于每个系统表都有这样的一个定义。这里面每个字段分别是怎么定义的,这里面都有相关链接。
之后也可以通过select语句,如果知道这个系统表的表的名字就可以去查它。从namespace来看,最重要的几个,information schema public catalog,这几个是最重要的。另外就是一般的表里都会有一个ncl,ncl就是访问控制相关的信息,就是哪些用户可以访问读取和读写这些表。
之后就是像database,也是一样的,默认建的几个Database都有,包括上节课建的test。
接着看一下这个系统表对于平时用户DBA,那要怎么去用它?比方查一下Pg class,就能把所有的库里面的表,包括定义的还有系统的表全查出来,包括rel型机。
另外就是可以过滤,比方输入自己的创建的表,来查看自己的表的情况。之后可以进行一个调优,比方这个命中率,有多少的I O?是读八分,这个命中还是没命中,这是做T P C C,这是测试时很关注的一个点,如果八分命中率上不去,那肯定就上不去。
那么表级的命中率,比方某一个表的命中情况,包括这个表的大小情况。也可以用性能的插件,这个插件就是类似于pg_stat_statements ,首先需增加shared_preload _libraries=pg_ stat_statements这个参数。上节课将这个参数加进去之后不成功,有一个同学反应有这个参数之后没有装插件,首先就是要在有的这个参数之外,还要安装这个插件。而P G的这些插件都在contrive目录下,这个目录下就需要在里面进行make store或者就直接make word,它可能就把所有的信息都装起来了。那么装完以后有了这个参数之后安装还需要Create extension,这就是P G插件的使用。这个相当于额外的东西。这里面可以通过它来查SQL占I O最多的SQL,比方最耗时的SQL。这个有点像oracle的V dollar相关的一些视图,查到了一些信息。这些就相当于调优时会用到一些系统表。
另外就是查看代码,代码其实是在backend的catalog目录下。生成的时候在这里面可以看到,它是会生成一些比方这个系统表的create,create时把它类型显示出来,之后它会再插入里面,将默认的信息插入到表里面。这样就默认的生成了模板库,模板库里就包含了元数据。这些信息有一个定制O I的过程,可以看到这里面很多文件都是可以在它标注时由generate BTL和PL来生成。这个文件是在编译的时期调用的,主要就是调用porn,进行前期的处理,这个处理可以看注释,是在92年时P G的OID,因为对应每一个系统表里面的一条记录都需要有一个OID的值,这个OID值有上千个,有几千个之后它们之间又有一些关联,如果人为去做这个事情就很复杂,所以它需要在编译时自动去生成O I D,这些OID会自动生成。
如果有一个系统表,系统表定义好以后在进程启动时,会建立syscache和relcache来缓存系统表的信息,因为系统表的信息不停的在用它,如果不放到内存里它的性能就会很差,所以就放到cache里。这里面的cache包含很多cache,包括在util的开目录下,包括rel cache catcache,还有常用的plan cache。这些都是为了做加速的,像plan cache 。
比方将plan cache放到全局里做全局的开始,这样所有人都能访问到它,这样的好处就是它占用的资源就更少了。像做TPCC就是一个很重要的问题,plan cache太大了,因为每个进程都有自己的plan cache。
TPCC里面又有很多存的过程并且是带参数的,结果就产生很多plan,所以很费内存,因为要启动很多个进程来做处理,所以这里面就让这些怎么加到全局里做一些管理,叫做global cache,从catcache里来看主要存的是系统表的信息。这个系统表的信息可以从主结构里定义现有的这些系统表,如果想自己加一个系统表就在后面追加一个,按照利用它的方法可以尝试创建自己的系统表,或者创建自己的系统视图。
这里面会有它对应的相关信息和更详细的信息描述,每个系统表里的类型是怎么样的,几个字段,这样的一些的描述。
这些信息最终是由catcacheheader来header,将它们穿起来,每个系统表都连起来之后用hash表做一个缓存处理,还是很复杂的。但是在每个postgre的backend启动时都需要加载这些信息,将这个数据结构建好,但是它不是把所有系统表信息都直接加载进来,是访问时发现没有,就会把它加进来。
relcache就相对简单一点,就用了一个hash表。它主要是felnodemap相关的一些信息,它会将它读进来,那么这个就相当于notmap记录的就是每个文件对应的OID信息,那OID信息就能确定了,这个OID信息和这个文件信息,在系统表里对应的是一个O I D信息就是每个表对应的唯一O I D。
这个O I D对应的在磁盘上文件的编号是什么?就是用它来做一个maping映射。可以看到它在做管理初始化时也是创建了一个hash表,hashcreat,感兴趣的同学也可以通过这个方法去学习一下P G的很重要的数据结构,hash表的结构是怎么创建的。包括它的启动的几个流程,怎么得到这个?它怎么查hash?怎么去找都可以通过用这个方法来学习系统表相关的一些东西。第一节就讲到这里,讲完以后根据原则演示一些命令,或者演示一些代码。
在这里执行一下,可以看到这个Pgclass系统表是在pgcatalog这个空间下,它里面的这个结构在这有一个定义。比方也可以通过这个知道表明了可以select信息查到它。也可以去查某一张表,用这个V条件做一个过滤可以看到这张表对应的文件编号是什么。
再去看postgre这个数据库的八块命中率,八块命中率是0.998,这个命中率很高,因为表都在里面,如果去查这张表,可能查几次这个命中率会更高。之后查一下这个表相关的命中的一些情况,包括这个表的大小,TY它的大小只有8K,因为创建好这个表以后只差两条数据,再看一下访问IO的情况,按照IO做一个降序的排列值,将IO访问最高的五个,这个I O访问包括I O的读时间和写时间,这几个sql设置config时就是最耗I O的,因为没有太多别的操作在里。
再看一下执行时间来排序这个情况,这里面可以看到这个CREAT DATABASE test,这个是整体执行时间最长的。
到这个目录下看一下代码的一些实现哈。再打开另一个这个窗口,到catalog代码目录下,首先这个代码目录下可以看到这里面system_view刚才介绍到它会包含很多view在里面,它的终端是有问题的,将终端退掉重新开一个终端。
先连到86这台服务器,再到Catalog的代码目录下,看一下system_view的情况,这里面大量的用到了pgclass和其他表做draw。结果得到一些sql标准里面定义好的view,它都是叫create view,它create了很多view,都是从Pgclass系统表里面提取出来的。
再看一下标准,其实创建方法是一样的,只是它的名字或者格式在这里面更标准。里面也是同样的东西,都是在不同的Pgclass这些表里面帮它查一些数据,查好了之后就命名为tables。
这些view跟oracle其他的数据库比较兼容性更好,因为它是标准的,可以看到这里面postgres的BKI,postgresBKI可以看它这里面会create proc这个系统表,创建这个系统表里面的各个字段的类型,创建好之后就insert,insert之后它的数据分别是怎样的,另外最重要的是OID在编译时期生成的文件,最重要的就是将这些信息分出来,这就是系统表的代码。
之后就是catcache和real cache,看一下它们的代码,首先catcache是在图文件下定义的,这里面就有backend hash 表这都跟hash有关,它相当于是某一个系统表对应的一个数据结构。之后会练到header下面,header下面有一个list,这个list的结构也是P G,最常用的一个结构,就会将list练出来。
relcache也是一样的情况,可以看到这个I D是在syscache.h下,接着去找这个文件看一下它的id,是这样定义的:syscacheidentifier,有这么多系统表,如果要加一个系统表就加一个自己的名字就可以了。
要注意的是它一般会有一个Size,这个size会通过最后一个来计算,所以要差的时候别差最后一个,差最后一个就要把名字改掉就可以了。这是第一部分系统表。
之后介绍这几个流程,初始化流程,启动流程和查询流程。初始化的流程上节已经演示了这个命令,叫 init DB,之后它就创建出来底下的目录结构,包括这个base drectoryg 根目录,之后底下有base目录,还有模板库,模板库底下又有跟很多这样的文件,这个里面又有global,还有其他的信息,有很多信息都在里面。
刚才介绍到在编译的过程中就会由genbki.pl来生产postgres.bki,这个bki是init db里面创建的很重要的一个文件,系统表就是通过它创建出来的。它创建完以后会创建这些目录,创建录目录以后就会创建tmp1模板库,之后再拷贝 tmp1生成 tmp0和postgres 这个数据库。
二.启动流程
那么再看一下这个代码的流程,首先要编译生成各自的OID,之后进入到的initDB里面,查看PG data这个目录生成路径,在这里面可以看到setup data 就将用到的这些文件名字设置好,设置好以后就便于到目录里读这些文件。
之后要在这个里面初始化目录,初始化目录首先要创建目录,创建Save目录,其实在这已经定义好这些是 save 目录,那这个目录结构就跟这里面的结构是一样的,它只要做一个这样的循环就可以把这些都创建出来再修改配置。
之后在 bootstrap 模式下来生成调用BKI的文件,来生成tmp1,这个相当于启动的参数是 boot 参数来读BKI生成的东西。再读这些Sql,比方information sql,system view sql,最后做一些template的拷贝,tmp0有个特点就是不接受这个连接,不可以修改,之后再拷贝tmp1 postgres这个数据库。
之后再介绍一下postgres的启动流程,启动的时候Postgres有三种模式,第一种叫postmaster启动模式。第二种就是initDB的时候用到的bootstrap模式,第三种就是single模式,它就不对外监听直接启动backend提供服务。
再就是要创建topMemorycontext来管理它的内存,创建信号的处理函数,包括加载G U C,再做一些监听,创建共享内存,再就是辅助进程的启动,从图里面看就是先进的postmaster,它会启动一个监听,这样就可以收到这边的消息。
它会创建共享内存还有一些基本的参数,再启动这些辅助进程,将这些辅助进程启动起来,启动完以后也可以对外提供服务了。
这时比方有一个请求,请求一个服务时首先创建一个postgres进程,对它提供服务。
从代码角度来看,稍后可以演示一下,最开始的时候是main函数,进来后它有三种模式,分别是对应这三个函数single,postgresMain,正常情况下是postmastermain,
在postmastermain里创建marcontext,之后设置信号,G C参数的一些加载。包括这个锁文件,控制文件,还有library加载。
之后就是监听,监听完后创建共享内存,共享内存这一块信息创建出来后就开始加载各种各样的进程啊,包括特殊的backendworker,比方做病情回放时其中很多个类似backend进程,就用这种方法。接着进入到这个循环里面,如果要是有新的连接,这个东西带零,这时就启动一个进程,将相关创建好的连接传进去,让它们之间进行通信,对他进行服务,这里面包括进程的相关的辅助进程的启动。
这是执行的流程,执行流程简单来说从client,通过interface library 通常就是在Pg里面。
再连到postmaster监听,监听到信息之后会创建一个postgres server,来对它进行一个服务。它就会把SQL发过来 ,它收到sql后会进行一个解析,生成parse tree,去判断它的类型,是什么样的执行路径,然后进行优化,形成优化的plan tree,之后就真正的在执行期里开始执行,执行的就是的table lookup,提取方法用不同的方法,比方tablestand或者不同的方法来提取数据,那么数据最终返回到客户,在界面上就能显示出来。
执行相关函数的过程在上节做了G D P的调试,调试的时候就演示了posgres的Q消息,其实就是在执行simple query的地方下一个断点,在这个断点进来以后发消息时就会停在这个断点上,所以它会一步一步的进行事务处理,包括查询的生成querytree,生成plantree,最后创建一个portal,在portal里面执行一些策略的设置,初始化,最终portal run来执行,最终调执行引擎的执行run,来真正的执行这个query,最后会把这个结果返回回,关闭这个事务,结束事务。
这部分几个主要的流程就介绍完了,这个流程也看一下代码。
三.查询流程
第一个是初始化的流程,初始化的流程在这个init DB里面,首先initDB是一个进程,那这个进程肯定有它的命函数,这个是它的入口函数,这里面除了参数之外还会解析很多参数,这些都不太重要。
这里面可以看到是在setup pgdata,用的就是PGData这个环境变量,上一节设置了环境变量,这样就能知道数据目录在哪个位置。用到的这些文件,先把它的路径设置好,之后就开始真正的init这个数据目录,init时首先要创建数据目录,创建数据目录来将所有的目录创建好。之后他会用sub目录,可以看模板库里面定义的,就是init之后的目录都在这里面定义好了,它是做一个循环。
其实这个里面很简单,就是循环make DIR,调这个命令就可以。Tmp0做什么事情?
在read file,B K I的这个file读完以后,它做一些环境的替换,替换后在这里设置boot模式来启动一个P G,用P G把它读到Postgres的B K I里,将信息进行执行,也就是插入的一些动作,生成新的一些数据,那么这些数据生成好后,就生成了这个tmp0这个模板。在setup_sysviews里面加依赖,加权限或者加sysviews,把这个文件读出来再执行,相当于也是在这个数据库里面执行sql的命令,来生成它最初的schema系统的view。包括这里面一些权限,最后这里面就是tmp0,其实是创建tmp0,允许连接等于force,所以它是比较特殊的是不允许连接的,它是用tmp1拷贝过来的。Makepostgres1也是拷贝过来的,这就是初始化的流程。
之后就是初始化init DB的流程,这个之后介绍启动的流程,启动的流程就从这个命开始介绍,这是整个系统里面刚起来的时候,调pgcontrol的start,最开始启动的时候,它就启动的是main,首先看一下main文件,它是在这个main.c里面,在这个main弹数可以看到,它主要有三种路径,前面就是解析它的参数,解析参数以后就是三种,第一种就就boot模式,第二种single模式,就是没有倾听的模式,那第三种就是postmastermain在这个里面它会启动最重要一个启动流程,重点关注这个。
在postmastermain看一下,首先它是tpomemory的创建这个很重要,创建一个top之后底下的才一层一层的去创建memory context来管理内存。之后每个进程也都一样,来设置它的信号,解析它的参数,之后看一下其余参数,check,看这些文件和目录是否正确,创建目录的这个锁文件,防止启动多个进程出现冲突的情况。
在process_shared_proload library这个地方,proload这个library,这些就是外面的一些插件的加载。如果要是加新的进程要改这个值,否则可能启动不起来。接着就会启动监听,会监听6688参数配置端口,主要在这里面做了监听的启动,监听完后Remove,将原来垃圾文件删掉,就开始启动了,先启动的是log日志的进程,之后做一些其他的初始化,访问控制HBA,就是client健全的相关信息的读取。
其他的进程就是pg worker的启动,最后进入到最重要的sol loop里面,在sol loop里面是一个for循环,就是loop,loop时它会去接受这个请求,如果这个请求大于零,说明有人要连接,于是就构造一个port,将它地址信息告诉你,先创建好一个连接,连接好以后传到backend里,backend启动后就利用这个连接跟另一边进行通信。再进行检查,哪个进程的PID等于零,就说明它没有启动,就需要把它给启动起来。基本上就是这样一个流程,在循环里面不停的做这个事情。这个流程因为上一节课做了一个debug,这节就过了,所以这节代码就不介绍了。
四.辅助进程
最后介绍辅助进程,辅助进程是第四个部分,看一下属性,首先这个postmaster和postgres其实是一个进程。
只是它们启动方式不同,所以它们走的流程不太一样。这个sys logger是一个日志进程,把日志打开就会搜集日志信息,定位错误的时候就会去看这个日志。之后就是bgwriter,它就在后台把脏页写出去。Walwriter,就是wal日志,来写盘的进程。Pgarch就是备份日志,将这个日志归档时用这个进程。autovacuum是做自动清理的一个进程。稍后会介绍autovacuum相关的信息,再来仔细看一下代码。pgstat就是统计信息相关的搜集一些进程。
可以看到在后台每一个进程都对应了一大堆的配置。比方log,开不开启,目录是怎样的,循不循环,几天循环一次,要log的信息是哪些,checkpoint log 连接的log,类似于这种前缀,日志的格式等等。这里面还有很多没有列出来。这些就是常用的一些配置,autovacuum也是同样情况,跟freeze有关的,跟它的vacuum最大的worker并行的进程处,还有一些其他的相关的一些设置。bgwriter也是一样的,包括它的L R U的list,它的一些设置和管理。包括checkpoint的time时间相关的一些信息,相关的一些设置。那么右边这张图是polarDB,PGE的它的进程情况,它的BG writer其实有多个,那就说明刷盘的时候觉得性能跟不上,可能就是启动做并行的bgwriter,并行其实是经常用的一个手段,比方做wal日志的回放也是用了并行这样的方式。
可以看到这里面有一些flashback的一些进程,还有很多跟wal相关的。这里面都是跟wal相关的,还有index,为什么这么多?因为做这种存储计算分离之后做logDB,logDB在这样的架构下日志是非常重要的一个信息,在各个节点之间只读节点之间,都是要传logindex,获取它的log信息,所以它的这个log改造是非常大的。
最后再看一下autovacuum的辅助进程,首先可以看一下它有几个进程,有mantra worker,最重要的是,真正工作的时候这个worker里面可能会起多个worker,看一下这个里面做了什么事情。首先前面也是处理这些信号,不同信号对应的信号处理的函数是怎样的,它设置这个东西,每个进程前面都是做这个事情。
之后做一些基本的设置,一般的进程启动的时候都会有这些基本init过程。它会去做这种错误处理,就是set一个jump,如果出错的话要怎么处理,再做相关参数的一些设置,设置一些参数。之后就要真正干事情,这里面最重要的就是做这个autovacuum,来看它是怎么读的,首先看注释,它要执行一个表的来做 vacuum。首先它创建自己对应的 context,启动事务,有的进程是不需要事务的,这个vacuum 肯定是需要事务保证的。
那这里面它会设置系统表,找到database相关的信息,那么包括同学们的相关变量,也会在这里面进行一些初始付出值。创建对应的一个hash表便于找到rel ID,相当于做rel cache这样的工作,接着就开始search,那么on first pass第一趟的时候它是找maintable,普通的表的情况。
那普通表情况肯定是在wel,在做循环,找这个表之后要relation need vacanalyz,这个时候它有一个fist vacuum,可以看一下今天同学的问题,就是在这个地方用这个参数来控制它的first vacuum就是要freeze了。
另外如果不是这种情况,要怎么去判断,比方用vacuum这个阈值进行比较,来看是不是需要做vacuum或者做其他的,这就是判断的一个情况,之后就会在这边启动第二趟,第二趟是toast表,过程是一模一样的。之后就temporary也是类似的,目的就是check它到底是不是需要,如果需要就加到它的list里面在后面统一处理。那最后它会得到一个list,这个list其实就是需要做这个Vacuum相关的这些表的一个list。那么在这个list下就需要真正的去做真正的vacuum list,真正的内存在这里面。
这个里面也是会做前期的准备,做vacuum的时候会在这里会bulidlist ,把要做的这个表串起来。最终就是在这里面loop process each select relation,所以每一个符合条件需要做vacuum的表,都要进来循环的给它做vacuum ,vacuum的时候就叫analyze rel,就是要做一个rel函数,看它对应就是某一个ID,就循环某一个table的analyze操作。做这个相关操作的时候当然要把这个表先尝试一下,将它锁起来,因为做这个操作需要先锁表,锁表以后要check这个权限,没有权限也不能做这个事情,之后要真正的就是要do analyze rel, 真正的做这个事情。
继续看真正工作的时候它是怎么做的?它有两个逻辑,在做这vacuum rel的时候有两种情况,一种就是负vacuum,可以看一下它的过程,打开这个表以后它会创建一个新的表缓存新的数据,相当于所有的都是新的。如果不是这种负vacuum情况,就会有lazy的方法,那lazy的方法会做一个便利,便利扫描所有页面,扫描页面以后它会把这个页面里面需要做vacuum的形成一个列表。形成列表以后会在后面进行相关的一些处理,或者类似这样的操作,最终完成了垃圾信息的清。
大致就是就是这样的一个流程,今天的课就介绍到这里。
再回顾一下这节课,这节课是整个系列课程的第二节。主要讲的是系统表,启动的流程,查询的流程和辅助进程。
系统表很重要,这一章回去可以考虑如何去增加自己的系统表和系统视图或者增加一个自己参数。可以自己用一用这种调优的方法。
第二个就是启动的流程,启动的流程里面可以根据启动过程,如果自己要去创建一个进程,要怎么去创建,就可以模拟它的创建进程的一些方法可以做一些测试和尝试。下一节会讲存储管理,存储引擎相关的知识。