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. 进程架构总览

postmaster PID=1 (会话主进程) Auxiliary Processes(辅助进程)— 由 postmaster fork,无客户端连接 checkpointer 检查点 bgwriter 脏页后台写 walwriter WAL日志写入 autovacuum 自动清理launcher stats 统计收集 Backend Processes(后端进程)— 每个客户端连接对应一个,由 postmaster fork backend (PID 2001) 连接1: SELECT * FROM t; backend (PID 2002) 连接2: INSERT INTO t ... backend (PID 2003) 连接3: UPDATE t SET ... backend (PID …) 更多连接… 客户端1 (psql) 客户端2 (psql) 客户端3 (JDBC) 图例 postmaster(主进程) auxiliary(辅助进程) backend(后端进程)

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. 判定标准

完成以下验证即视为通过:

  1. 通过ps -ef能观察到postmaster、至少4类辅助进程(checkpointer、bgwriter、walwriter、stats collector)以及backend进程同时存在;
  2. 所有非postmaster进程的PPID均等于postmaster的PID;
  3. 建立新连接后,进程列表中新增backend进程,且该进程的PPID仍为postmaster的PID;
  4. 关闭该连接(psql退出)后,对应backend进程消失,而postmaster和辅助进程继续存活。

以上现象与源码中main.c的分支逻辑及postmaster.c的StartChildProcess()调用完全对应,证明进程架构分析正确。