当前位置: 首页 > postgresql, WAL日志 > 正文

第一拍:源头封口(产生物理载荷)

  • 物理现场:业务在疯狂写入,pg_wal 目录下的一个文件(比如叫 000000010000000A000000FF)刚好写到了第 16777216 个字节。
  • 物理动作:负责写日志的 WAL Writer 进程或者前台业务进程,调用底层的 close() 函数,把这个 16MB 的文件彻底封口。从此以后,这个文件变成了**“只读的物理标本”**。

🎬 第二拍:生成“发车信号”(极其关键的防呆设计)

  • 前置逻辑:文件封口了,怎么通知归档进程去干活?如果用内存发消息,万一瞬间断电,消息就丢了,这个文件就永远不会被归档了。
  • 物理动作:PG 内核极其老辣地采用了**“落盘即真理”**的法则。内核直接在 pg_wal/archive_status/ 这个子目录下,创建一个大小为 0 字节的空文件,名字叫做: 000000010000000A000000FF.ready
  • 定性:这个 .ready 文件就是一张“押运传票”。只要它存在于硬盘上,就代表对应的 16MB 日志需要被运走。断电重启也不怕,因为传票在硬盘上。

🎬 第三拍:归档进程苏醒(Archiver 介入)

  • 物理现场:后台一直处于休眠(Sleep)状态的 Archiver(归档进程),被内核的内部信号唤醒,或者它自己按周期醒来了。
  • 物理动作:它第一件事根本不是去看日志文件,而是直接去扫描 archive_status/ 目录。
  • 数据得出:它一眼就看到了那个闪闪发光的 .ready 文件。它立刻知道:“来活了,目标是 000000010000000A000000FF。”

🎬 第四拍:拼装押运指令(执行 archive_command)

  • 前置逻辑:归档进程自己是不会搬运文件的,它必须依靠你(DBA)写在 postgresql.conf 里的 archive_command
  • 物理动作:假设你写的命令是 cp %p /mnt/nfs_backup/%f。 归档进程开始做底层的字符串替换(Macro Substitution)
    • 它把 %p 替换成这个日志的绝对物理路径(如 /var/lib/pgsql/data/pg_wal/00000001...)。
    • 它把 %f 替换成这个日志的纯文件名(如 00000001...)。
  • 执行:拼装完成后,归档进程向 Linux 操作系统发起 system() 调用,把这串拼好的命令扔给操作系统去执行。

🎬 第五拍:生与死的审判(死等操作系统的 Return Code)

  • 前置逻辑:命令扔出去了,归档进程怎么知道运成功没有?它绝对不看目标端到底有没有这个文件,它只认死理——看 Linux 返回的退出状态码(Exit Code/Return Code)
  • 物理动作(分岔路口):归档进程死死盯着刚才执行的那个 cp 命令的返回值。
    • 分支 A(物理大圆满 – 返回值是 0): 如果 Linux 告诉它命令执行成功(返回 0)。归档进程立刻掏出 rename() 函数,把那个 0 字节的 .ready 文件,重命名为 .done000000010000000A000000FF.done)。 此时,归档的物理闭环彻底宣告完成!
    • 分支 B(死亡螺旋 – 返回值非 0): 如果目标 NAS 满了,或者网络断了,cp 命令报错了(返回非 0)。 归档进程会立刻在数据库的错误日志里大吼一声(报错),然后它会绝不妥协地进行无限次重试! 只要没成功,那个 .ready 文件就绝对不会变成 .done

🎬 第六拍:闭环与交接(Checkpointer 的入场)

  • 前置逻辑:归档进程运完了,但它没有权力去删除原来的 16MB 文件。它只负责把 .ready 变成 .done
  • 物理动作:就像我们上一局推演的,等到 Checkpointer(检查点进程)来清理磁盘时,它看到了这个 .done 文件,它才知道:“哦,归档兄弟已经运走了,这个 16MB 的实体文件我可以安全地拿去**回收(Recycle)或者删除(Unlink)**了。”

架构级物理闭环总结

你看到了吗?没有任何思维跳跃。 生成 16MB 载荷 -> 产生 .ready 信号 -> 唤醒归档进程 -> 替换 %p %f 执行外部命令 -> 强校验退出码为 0 -> 重命名为 .done -> 最终交给检查点进程去回收物理空间。




🧬 终极补充一:架构的物理跃迁 —— 抛弃 archive_command,拥抱 archive_library (PG 15+ 革命)

我们刚才推演第四拍时提到,归档进程会拼装一个命令,然后调用操作系统的 system() 去执行(比如 cp)。

  • 传统架构的致命瓶颈(物理开销): 在 Linux 底层,调用 system() 意味着必须执行 fork()exec()。也就是说,每归档一个 16MB 的文件,操作系统都要去克隆一个进程、分配独立的内存空间、加载 Shell 环境。如果高并发下每秒产生 10 个 WAL 文件,这种疯狂的 fork() 会极其严重地消耗 CPU 算力,甚至引发系统的 Context Switch(上下文切换)风暴!
  • 新世代的降维打击(archive_library: 从 PostgreSQL 15 开始,内核研发团队彻底忍不了这种粗暴的玩法了。他们引入了 archive_library物理逻辑:你不再需要写那些笨重的 Shell 脚本了。原厂架构师可以直接用 C 语言写一个归档模块,编译成动态链接库(.so 文件)。PG 启动时,直接把这个 .so 加载到归档进程的同一块内存地址空间里! 当 .ready 文件生成时,归档进程直接在内存里调用 C 函数指针,把 16MB 的二进制流通过内部 API 直接推送到灾备端(比如 S3 对象存储 API)。
  • 定性fork() 开销,没有跨进程通信损耗! 这是从“外部挂载武器”到“内置原生主炮”的绝对物理进化!

📊 终极补充二:内存级的法医探针 —— pg_stat_archiver 视图的底层映射

考官一定会问:“你刚才说归档失败了会无限重试,那作为 DBA,我怎么在它把磁盘撑爆之前,精准狙击到这个故障?”

  • 常规 DBA 的土办法:写个 Shell 脚本去 pg_wal/archive_status 目录下 ls | grep .ready | wc -l,看堆积了多少个文件。这极其业余且消耗 I/O。
  • 你的高阶玩法(内存直读): “我会直接查询系统内部的 pg_stat_archiver 内存视图! 在 C 语言底层,PG 在共享内存里维护了一个叫做 PgStat_MsgArchiver 的结构体。归档进程每成功一次,就会在内存里把 archived_count 加 1,并记录 last_archived_wal;只要失败一次,哪怕只失败了半秒,就会立刻把 failed_count 加 1,并刻下 last_failed_wal 及其时间戳。 定性:作为高阶架构师,排障绝对不依赖外部的、滞后的文件系统扫描,而是直接读取共享内存中被内核实时更新的状态机统计量!”

了解 www.876873.xyz 的更多信息

订阅后即可通过电子邮件收到最新文章。

wal日志的归档进程 工作原理,逻辑条理 的推演:等您坐沙发呢!

发表评论