PostgreSQL中的检查点进程详解
bgwriter与checkpointer进程对比
bgwriter进程与checkpointer进程,虽然都会写出脏页,但是刷脏的目的、频率、控制参数等,都有不同,以下是两则的区别:
区别 | bgwriter进程 | checkpointer进程 |
---|---|---|
当前,该进程刷脏的目的 | 1、保证有足够多的干净页面可以使用,提供数据查询的性能。 2、通过提前将一部分脏数据落盘,可以减少checkpoint操作时的IO操作,使系统IO趋于平稳。 |
1、保证数据的一致性,将所有脏块刷出到磁盘,并创建一个数据一致性的位点,以便在数据崩溃后进行恢复。 |
执行频率 | 默认200ms | 默认5min |
每次刷出脏块 | 默认100 | 全部脏块 |
脏页刷出策略 | Postgres 8.1版本之前是用的LRU算法,从8.1版开始,Postgres使用了Clock Sweep时钟扫描算法 | 无,刷出全部脏块。 |
是否更新pg_control文件? | 否 | 是 |
该进程在哪个版本引入? | Postgres 8.0 | Postgres 9.2 (在8.0-9.1版本中,由bgwriter进程处理checkpoint) |
在Postgres 9.2版本中,checkpointer进程正式从bgwriter进程中分离出来,称为一个独立的进程。
checkpointer进程分离出来前
每隔几分钟就要运行一次检查点,将全部脏页写入操作系统的缓存区,然后再将操作系统缓冲区的所有脏页刷新到磁盘,会发出一个全局的sync调用,这将导致磁盘IO使用量的周期性激增,通常会影响性能。
bgwriter进程既需要负责执行checkpoint操作,还需要保证缓冲区有足够的干净页面可以使用,越来越臃肿,不便于性能调优。
以下代码片段来自Postgres 9.1版本:
/* checkpoint刷脏 */
void CheckPointBuffers(int flags)
{
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(flags);
CheckpointStats.ckpt_write_t = GetCurrentTimestamp();
BufferSync(flags);
CheckpointStats.ckpt_sync_t = GetCurrentTimestamp();
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START();
smgrsync();
CheckpointStats.ckpt_sync_end_t = GetCurrentTimestamp();
TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE();
}
/* 全局sync,极大的增加了IO */
void smgrsync(void)
{
int i;
for (i = 0; i < NSmgr; i++)
{
if (smgrsw[i].smgr_sync)
(*(smgrsw[i].smgr_sync)) ();
}
}
checkpointer进程分离出来后
bgwriter以稳定的速度进行脏块的写入操作。当检查点触发时,需要写的脏页会少很多,fsync也只需考虑只上一次检查点以来产生的脏块,提高了性能,并且最大限度的减少检查点期间的性能降低。
由于checkpointer进程和bgwriter进程之间的职责分离,系统管理员可以更方便地进行优化和调整,以满足特定的性能要求。
关于bgwriter进程
在Postgres 8.0版本中引入该进程。
刷脏的目的
- 保证有足够多的干净页面可以使用,提供数据查询的性能。
- 通过提前将一部分脏数据落盘,可以减少checkpoint操作时的IO操作,使系统IO趋于平稳。
仅将部分脏块刷出到磁盘。
guc控制参数
# - Background Writer -
bgwriter_delay = 200ms # 10-10000ms between rounds
bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables
bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
bgwriter_flush_after = 512kB # measured in pages, 0 disables
关于checkpointer进程
在Postgres 9.2中,将checkpoint处理的部分从bgwriter中剥离出来,引入checkpointer进程,专门用于处理checkpoint,而bgwriter进程不再处理检查点。
每次会将全部脏块集中刷出盘磁盘,对数据库性能影响较大。
刷脏的目的
保证数据的一致性,将所有脏块刷出到磁盘,并创建一个数据一致性的位点,以便在数据崩溃后进行恢复。
guc控制参数
# - Checkpoints -
#checkpoint_timeout = 5min # range 30s-1d
max_wal_size = 1GB
min_wal_size = 80MB
#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0
#checkpoint_flush_after = 256kB # measured in pages, 0 disables
#checkpoint_warning = 30s # 0 disables
为什么将checkpointer从bgwriter进程中分离出来?
- 早前版本,bgwriter进程同时执行后台写入、检查点和一些其他任务。因为只有一个进程,在做检查点的fsync的时候,不能进行bgwriter的写操作,两个操作不能同时进行,带来负面的性能影响。
- 此外,再9.2版本中,开始使用锁存器(latch)替代轮询的循环来降低功耗,而bgwriter循环的复杂性很高,不太可能使用锁存器替代循环。
关于分离checkpointer进程与bgwriter进程的讨论:
https://www.postgresql.org/message-id/CA%2BU5nMLv2ah-HNHaQ%3D2rxhp_hDJ9jcf-LL2kW3sE4msfnUw9gA%40mail.gmail.com
后续
由于bgwriter和checkpointer进程演进的历史原因,导致即便是在当前的Postgres 12版本中,bgwrter进程的代码中仍有大量checkpointer进程的影子,checkpoint的状态统计信息,仍在bgwriter的状态统计视图pg_stat_bgwriter中。但两个进程在Postgres版本的不断演进中,代码在不断的分离,演化成两个相对独立的进程。
在Postgres 12版本中,bgwriter的状态统计数据结构如下,从中能看到checkpoint的状态信息,两者并未完全分离。
typedef struct PgStat_MsgBgWriter
{
PgStat_MsgHdr m_hdr;
PgStat_Counter m_timed_checkpoints;
PgStat_Counter m_requested_checkpoints;
PgStat_Counter m_buf_written_checkpoints;
PgStat_Counter m_buf_written_clean;
PgStat_Counter m_maxwritten_clean;
PgStat_Counter m_buf_written_backend;
PgStat_Counter m_buf_fsync_backend;
PgStat_Counter m_buf_alloc;
PgStat_Counter m_checkpoint_write_time; /* times in milliseconds */
PgStat_Counter m_checkpoint_sync_time;
} PgStat_MsgBgWriter;
在Postgres 15版本中,可以发现bgwriter和checkpointer的统计信息已经完全剥离。
typedef struct PgStat_BgWriterStats
{
PgStat_Counter buf_written_clean;
PgStat_Counter maxwritten_clean;
PgStat_Counter buf_alloc;
TimestampTz stat_reset_timestamp;
} PgStat_BgWriterStats;
typedef struct PgStat_CheckpointerStats
{
PgStat_Counter timed_checkpoints;
PgStat_Counter requested_checkpoints;
PgStat_Counter checkpoint_write_time; /* times in milliseconds */
PgStat_Counter checkpoint_sync_time;
PgStat_Counter buf_written_checkpoints;
PgStat_Counter buf_written_backend;
PgStat_Counter buf_fsync_backend;
} PgStat_CheckpointerStats;
推荐阅读
-
本地化浪潮中的 PostgreSQL - Zhenping Zhao
-
IDC与蚂蚁金服集团联合发布《风控技术十大趋势白皮书》--这是风控行业技术创新的风向标,也意味着在与黑灰产品的交锋中,技术升级迫在眉睫。 今天的商业模式不同于以往,随着数字化进程的进一步加快,金融机构必须时刻准备应对可能出现的经营风险。面对无边界、强对抗的新型重大风险,金融机构如何与之博弈,并始终领先一步?这正是《IDC 风险控制技术十大趋势指南》将深入探讨的话题。我们摘录了白皮书的部分内容与您分享。要获取全文,请在后台回复 "风险控制趋势"。 1 数字支付激增
-
在 LINUX 中查看进程的 4 种方法(摘要)
-
epoll简介及触发模式(accept、read、send)-epoll的简单介绍 epoll在LT和ET模式下的读写方式 一、epoll的接口非常简单,一共就三个函数:1. int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close关闭,否则可能导致fd被耗尽。2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epoll的事件注册函数,它不同与select是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create的返回值,第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */};events可以是以下几个宏的集合:EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLIN事件:EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。现在明白为什么说epoll必须要求异步socket了吧?如果同步socket,而且要求读完所有数据,那么最终就会在堵死在阻塞里。 EPOLLOUT:表示对应的文件描述符可以写; EPOLLOUT事件:EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那要先准备好下面条件:1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!其实,如果真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。1. 缓冲区由满变空.2.同时注册EPOLLIN | EPOLLOUT事件,也会触发一次EPOLLOUT事件这个两个也会触发EPOLLOUT事件 EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);EPOLLERR:表示对应的文件描述符发生错误;EPOLLHUP:表示对应的文件描述符被挂断;EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的产生,类似于select调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。-------------------------------------------------------------------------------------------- 从man手册中,得到ET和LT的具体描述如下EPOLL事件有两种模型:Edge Triggered (ET)Level Triggered (LT)假如有这样一个例子:1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符2. 这个时候从管道的另一端被写入了2KB的数据3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作4. 然后我们读取了1KB的数据5. 调用epoll_wait(2)......Edge Triggered 工作模式:如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。 i 基于非阻塞文件句柄 ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered 工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。然后详细解释ET, LT:LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。(未测试)另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取: 这里只是说明思路(参考《UNIX网络编程》) while(rs) {buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);if(buflen < 0){// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读// 在这里就当作是该次事件已处理处.if(errno == EAGAIN)break; else return; }else if(buflen == 0) { // 这里表示对端的socket已正常关闭. } if(buflen == sizeof(buf) rs = 1; // 需要再次读取 else rs = 0; } 还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send内部,当写缓冲已满(send返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send内部,但暂没有更好的办法. ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) { ssize_t tmp; size_t total = buflen; const char *p = buffer; while(1) { tmp = send(sockfd, p, total, 0); if(tmp < 0) { // 当send收到信号时,可以继续写,但这里返回-1. if(errno == EINTR) return -1; // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, // 在这里做延时后再重试. if(errno == EAGAIN) { usleep(1000); continue; } return -1; } if((size_t)tmp == total) return buflen; total -= tmp; p += tmp; } return tmp; } 二、epoll在LT和ET模式下的读写方式 在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK) 从字面上看, 意思是: * EAGAIN: 再试一次 * EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block * perror输出: Resource temporarily unavailable 总结: 这个错误表示资源暂时不够, 可能read时, 读缓冲区没有数据, 或者, write时,写缓冲区满了 。 遇到这种情况, 如果是阻塞socket, read/write就要阻塞掉。 而如果是非阻塞socket, read/write立即返回-1, 同 时errno设置为EAGAIN. 所以, 对于阻塞socket, read/write返回-1代表网络出错了. 但对于非阻塞socket, read/write返回-1不一定网络真的出错了. 可能是Resource temporarily unavailable. 这时你应该再试, 直到Resource available. 综上, 对于non-blocking的socket, 正确的读写操作为: 读: 忽略掉errno = EAGAIN的错误, 下次继续读 写: 忽略掉errno = EAGAIN的错误, 下次继续写 对于select和epoll的LT模式, 这种读写方式是没有问题的. 但对于epoll的ET模式, 这种方式还有漏洞. epoll的两种模式 LT 和 ET
-
C# 中的可数类型详解
-
Java 中枚举的使用详解
-
[Hadoop2.x] Hadoop 运行一段时间后,stop-dfs 等操作失败 原因及解决方案 - 永久解决方案:修改 $HADOOP_HOME/etc/hadoop/hadoop-env.sh 文件,将 ${HADOOP_PID_DIR} 的 export HADOOP_PID_DIR=${HADOOP_PID_ DIR} 改为你指定的目录,这样 Hadoop 就会将相关的 pid 进程文件保存在指定目录中,避免被 Linux 自动删除。例如 export HADOOP_PID_DIR=/usr/local/hadoop/pids/ 发现问题后的解决方案:
-
Asp.Net Core 中的进程内与进程外托管模式
-
TensorFlow 中的 LSTM 神经网络详解 - I. LSTM 神经元
-
有关 PostgreSQL 中 Datum 的解释