2. 理解PostgreSQL进程架构
任务说明
本案例通过阅读PostgreSQL源码并实际运行进程,分析三类核心进程的启动流程与职责:postmaster(主守护进程)、backend(后端服务进程)和auxiliary(辅助进程,包括checkpointer、bgwriter、walwriter等)。完成目标后,你应能准确描述:谁先启动、谁负责fork谁、各进程在数据库运行中承担什么工作。
1. 源码阅读:进程启动入口
PostgreSQL源码中,src/backend/main/main.c是所有进程的入口。关键逻辑如下(以PostgreSQL 15为例):
// src/backend/main/main.c(关键片段)
int main(int argc, char *argv[]) {
// 判断当前进程类型
if (argc > 1 && strcmp(argv[1], "--forkbackend") == 0)
exit(PostgresMain(argc, argv, NULL)); // backend进程
else if (argc > 1 && strcmp(argv[1], "--forkcheckpointer") == 0)
exit(CheckpointerMain()); // checkpointer辅助进程
else if (argc > 1 && strcmp(argv[1], "--forkwalwriter") == 0)
exit(WalWriterMain()); // walwriter辅助进程
else if (argc > 1 && strcmp(argv[1], "--forkbgwriter") == 0)
exit(BgWriterMain()); // bgwriter辅助进程
// ... 其他辅助进程分支
else
exit(PostmasterMain(argc, argv)); // postmaster主进程
}
从上述代码可见:所有进程共用同一个main入口,通过启动参数区分角色。postmaster是唯一由用户直接启动的进程,其余进程均由postmaster通过fork()创建,并在子进程中传入对应--fork*参数。
2. 进程启动时序与职责
postmaster启动后按固定顺序创建辅助进程,再监听客户端连接请求。核心流程位于src/backend/postmaster/postmaster.c的ServerLoop()和PostmasterStateMachine()中。启动顺序如下:
// src/backend/postmaster/postmaster.c(关键调用顺序)
static void PostmasterStateMachine(void) {
// 1. 启动统计收集进程
if (CheckPostmasterSignal(PMSIGNAL_START_STATS) && StartChildProcess(STATS_COLLECTOR) == 0)
// 2. 启动walwriter(预写日志写入)
if (WalWriterNeeded() && StartChildProcess(WALWRITER) == 0)
// 3. 启动checkpointer(检查点)
if (CheckpointerNeeded() && StartChildProcess(CHECKPOINTER) == 0)
// 4. 启动bgwriter(后台写)
if (BgWriterNeeded() && StartChildProcess(BGWRITER) == 0)
// 5. 启动autovacuum launcher(自动清理)
if (AutoVacuumingActive() && StartChildProcess(AUTOVAC_LAUNCHER) == 0)
}
当客户端连接请求到达时,postmaster在ServerLoop()中调用BackendStartup()为每个连接fork一个backend进程。backend进程负责执行该连接上的所有SQL命令,彼此独立,互不干扰。
3. 进程架构总览
4. 实际运行与验证
以下操作在Linux环境、PostgreSQL 15+中验证。初始化并启动数据库后,执行进程查看命令:
# 1. 初始化并启动(若已运行可跳过)
initdb -D /tmp/pgdata
pg_ctl -D /tmp/pgdata -l /tmp/pglog start
# 2. 查看所有PostgreSQL相关进程
ps -ef | grep postgres | grep -v grep
预期输出(示例,PID和路径因环境而异):
postgres 12345 1 0 10:00 ? 00:00:00 /usr/lib/postgresql/15/bin/postgres -D /tmp/pgdata
postgres 12346 12345 0 10:00 ? 00:00:00 postgres: checkpointer
postgres 12347 12345 0 10:00 ? 00:00:00 postgres: background writer
postgres 12348 12345 0 10:00 ? 00:00:00 postgres: walwriter
postgres 12349 12345 0 10:00 ? 00:00:00 postgres: autovacuum launcher
postgres 12350 12345 0 10:00 ? 00:00:00 postgres: stats collector
观察要点:
- PPID列:所有辅助进程的PPID都等于postmaster的PID(12345),证明它们由postmaster直接fork。
- 进程名:辅助进程在命令行中显示为
postgres: checkpointer等,与源码中--fork*分支一一对应。
接着建立客户端连接,再次查看进程:
# 3. 打开一个psql连接(保持不退出)
psql -d postgres -c "SELECT pg_sleep(30);" &
# 4. 再次查看进程
ps -ef | grep postgres | grep -v grep
预期输出中新增一行:
postgres 12360 12345 0 10:05 ? 00:00:00 postgres: postgres postgres [local] SELECT
该行中:12360为backend进程PID,PPID=12345(postmaster),postgres postgres表示“用户-数据库名”,[local]表示本地连接,末尾的SELECT为当前正在执行的SQL状态。
5. 判定标准
完成以下验证即视为通过:
- 通过
ps -ef能观察到postmaster、至少4类辅助进程(checkpointer、bgwriter、walwriter、stats collector)以及backend进程同时存在; - 所有非postmaster进程的PPID均等于postmaster的PID;
- 建立新连接后,进程列表中新增backend进程,且该进程的PPID仍为postmaster的PID;
- 关闭该连接(psql退出)后,对应backend进程消失,而postmaster和辅助进程继续存活。
以上现象与源码中main.c的分支逻辑及postmaster.c的StartChildProcess()调用完全对应,证明进程架构分析正确。