<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>预写日志 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E9%A2%84%E5%86%99%E6%97%A5%E5%BF%97/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E9%A2%84%E5%86%99%E6%97%A5%E5%BF%97/index.xml" rel="self" type="application/rss+xml"/><item><title>Postgres WAL 归档实现时间点恢复</title><link>https://www.xbcatm.com/zh/posts/postgres-wal-archiving-restore/</link><pubDate>Fri, 25 Jun 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/postgres-wal-archiving-restore/</guid><description>从 PostgreSQL 基础备份恢复到命名日志目标，在回放实际暂停后核对业务行，并用缺失必要日志段的对照证明该目标无法到达。</description><content:encoded>&lt;p&gt;时间点恢复从一份有效的物理基础备份开始，重放到指定恢复目标所需的 WAL。目标必须位于这份备份及其可用时间线能够覆盖的历史中。开启归档并不会自动获得恢复到任意更早日期的能力，也不会修复基础备份与期望终点之间已经丢失的一段日志。恢复范围由真正保存并可读取的材料决定。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;复制出来的数据目录不会自动成为有效的在线备份。应采用受支持的备份方式，保存必要日志与时间线历史，并检查备份清单。清单校验程序能发现许多完整性问题，但它运行成功，不能证明更晚的归档日志存在，也不能证明业务期望的恢复目标对应正确状态。后两者需要实际启动恢复，再检查恢复出来的数据。&lt;/p&gt;
&lt;p&gt;选择目标也需要明确边界。时间、事务标识、日志位置以及命名恢复点表达的含义不同；对适用的目标类型，是否包含目标边界由对应参数控制。本实验使用命名恢复点，避免依赖人工判断某个墙上时钟时间，但这个标记必须由管理操作事先写入日志。复用同一个名字可能使恢复停在首个同名位置，而不是操作人员心中较新的那个位置。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;在 Linux 环境中使用非超级操作系统账户运行脚本，准备 PostgreSQL 18 服务端、客户端以及支持保留属性复制、临时文件、硬链接和按文件同步的工具。本次在 PostgreSQL 18.6 上实际执行。集群位于私有临时目录，使用独立 Unix 套接字并关闭网络监听。本地归档用于演练恢复机制，不代表能够抵抗整台宿主机丢失。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;077&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;lab&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d /tmp/xbcatm-pitr.XXXXXX&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/socket&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;primary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;/primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;restored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;/restored
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;/missing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cleanup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; cluster in &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restored&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$primary&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$cluster&lt;/span&gt;&lt;span class="s2"&gt;/postmaster.pid&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$cluster&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -m fast -w stop &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; cleanup EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;initdb -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$primary&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --no-locale --encoding&lt;span class="o"&gt;=&lt;/span&gt;UTF8 --auth-local&lt;span class="o"&gt;=&lt;/span&gt;trust --auth-host&lt;span class="o"&gt;=&lt;/span&gt;reject &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$primary&lt;/span&gt;&lt;span class="s2"&gt;/postgresql.conf&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;CONF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;listen_addresses = &amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;unix_socket_directories = &amp;#39;$lab/socket&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;port = 56432
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;wal_level = replica
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_mode = on
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_command = &amp;#39;$lab/archive-copy.sh &amp;#34;%p&amp;#34; &amp;#34;%f&amp;#34; &amp;#34;$lab/archive&amp;#34;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CONF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive-copy.sh&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;ARCHIVE_HELPER&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;set -eu
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;wal_source=$1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;wal_name=$2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_directory=$3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_target=$archive_directory/$wal_name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;if test -e &amp;#34;$archive_target&amp;#34;; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; cmp -s &amp;#34;$wal_source&amp;#34; &amp;#34;$archive_target&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; sync &amp;#34;$archive_target&amp;#34; &amp;#34;$archive_directory&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; exit 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_temporary=$(mktemp &amp;#34;$archive_directory/.incoming.XXXXXX&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;trap &amp;#39;rm -f &amp;#34;$archive_temporary&amp;#34;&amp;#39; EXIT HUP INT TERM
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;cp &amp;#34;$wal_source&amp;#34; &amp;#34;$archive_temporary&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;sync &amp;#34;$archive_temporary&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ln &amp;#34;$archive_temporary&amp;#34; &amp;#34;$archive_target&amp;#34; || cmp -s &amp;#34;$wal_source&amp;#34; &amp;#34;$archive_target&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;sync &amp;#34;$archive_target&amp;#34; &amp;#34;$archive_directory&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ARCHIVE_HELPER&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;700&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive-copy.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$primary&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -l &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/primary.log&amp;#34;&lt;/span&gt; -w -t &lt;span class="m"&gt;15&lt;/span&gt; start &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PGHOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;/socket &lt;span class="nv"&gt;PGPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;56432&lt;/span&gt; &lt;span class="nv"&gt;PGDATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;id -un&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PGCONNECT_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;SQL&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CREATE TABLE marker (id integer PRIMARY KEY,value text);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;INSERT INTO marker VALUES (1,&amp;#39;base&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_basebackup -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/base&amp;#34;&lt;/span&gt; -X stream -c fast -w
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_verifybackup -q &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/base&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt -c &lt;span class="s1"&gt;&amp;#39;SELECT pg_switch_wal()&amp;#39;&lt;/span&gt; &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;SQL&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;INSERT INTO marker VALUES (2,&amp;#39;keep&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SELECT pg_create_restore_point(&amp;#39;before_bad_change&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;UPDATE marker SET value=&amp;#39;bad&amp;#39; WHERE id=1;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;DELETE FROM marker WHERE id=2;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt -c &lt;span class="s1"&gt;&amp;#39;SELECT pg_walfile_name(pg_current_wal_lsn())&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt -c &lt;span class="s1"&gt;&amp;#39;SELECT pg_switch_wal()&amp;#39;&lt;/span&gt; &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; ! &lt;span class="nb"&gt;test&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive/&lt;/span&gt;&lt;span class="nv"&gt;$segment&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;attempt+1&lt;span class="k"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$attempt&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -lt &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;archive segment did not arrive&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep 0.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;primary_now &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt -c &lt;span class="s2"&gt;&amp;#34;SELECT string_agg(id||&amp;#39;:&amp;#39;||value,&amp;#39;,&amp;#39; ORDER BY id) FROM marker&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restored&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp -a &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/base/.&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restored&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restored&lt;/span&gt;&lt;span class="s2"&gt;/postgresql.conf&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;CONF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;port = 56433
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_mode = off
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;restore_command = &amp;#39;cp &amp;#34;$lab/archive/%f&amp;#34; &amp;#34;%p&amp;#34;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;recovery_target_name = &amp;#39;before_bad_change&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;recovery_target_action = &amp;#39;pause&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CONF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;touch &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restored&lt;/span&gt;&lt;span class="s2"&gt;/recovery.signal&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restored&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -l &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/restored.log&amp;#34;&lt;/span&gt; -w -t &lt;span class="m"&gt;15&lt;/span&gt; start &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;PGPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;56433&lt;/span&gt; psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt -c &lt;span class="s2"&gt;&amp;#34;SELECT pg_get_wal_replay_pause_state()=&amp;#39;paused&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; t&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;attempt+1&lt;span class="k"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$attempt&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -lt &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;restore target was not reached&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep 0.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGPORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;56433&lt;/span&gt; psql -X -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -qAt &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;SQL&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SELECT pg_is_in_recovery(),pg_get_wal_replay_pause_state();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SELECT id,value FROM marker ORDER BY id;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;DO $$ BEGIN
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; IF (SELECT string_agg(id||&amp;#39;:&amp;#39;||value,&amp;#39;,&amp;#39; ORDER BY id) FROM marker)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; IS DISTINCT FROM &amp;#39;1:base,2:keep&amp;#39; THEN
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; RAISE EXCEPTION &amp;#39;point-in-time result mismatch&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; END IF;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;END $$;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; ! -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/base/pg_wal/&lt;/span&gt;&lt;span class="nv"&gt;$segment&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive-missing&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; archived_file in &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive/&amp;#34;&lt;/span&gt;*&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;archived_file&lt;/span&gt;&lt;span class="p"&gt;##*/&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$segment&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cp &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$archived_file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive-missing/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp -a &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/base/.&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;/postgresql.conf&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;CONF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;port = 56434
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;archive_mode = off
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;restore_command = &amp;#39;cp &amp;#34;$lab/archive-missing/%f&amp;#34; &amp;#34;%p&amp;#34;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;recovery_target_name = &amp;#39;before_bad_change&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;recovery_target_action = &amp;#39;pause&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CONF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;touch &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;/recovery.signal&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -l &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/missing.log&amp;#34;&lt;/span&gt; -w -t &lt;span class="m"&gt;15&lt;/span&gt; start &amp;gt;/dev/null 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;missing-segment server was briefly available&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$missing&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; status &amp;gt;/dev/null 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;attempt+1&lt;span class="k"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$attempt&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -lt &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;missing-segment server did not stop&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep 0.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -F &lt;span class="s1"&gt;&amp;#39;recovery ended before configured recovery target was reached&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/missing.log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;/archive/&lt;/span&gt;&lt;span class="nv"&gt;$segment&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; missing_segment_target_unreachable
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lab_directory %s\nrequired_segment %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$lab&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$segment&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;主库最初只有第一行，值为 &lt;code&gt;base&lt;/code&gt;。取得并验证基础备份后，脚本切换日志段，再插入值为 &lt;code&gt;keep&lt;/code&gt; 的第二行，并创建错误修改之前的命名恢复点。只有在标记之后，才把第一行改成 &lt;code&gt;bad&lt;/code&gt;、删除第二行。输出中的主库当前状态应只剩 &lt;code&gt;1:bad&lt;/code&gt;，证明错误修改确实已经发生，不能把没有真正写入的错误当作成功排除的对象。&lt;/p&gt;
&lt;p&gt;恢复使用基础备份的独立副本。恢复信号文件请求归档恢复，恢复命令提供归档文件，命名目标和暂停动作让回放停下来供检查。脚本等待暂停状态函数明确返回已经暂停。仅用另一个布尔函数得到真，只能证明暂停请求已发出，不一定证明回放此刻已经停止；两者在并发恢复时不能互换。&lt;/p&gt;
&lt;p&gt;预期结果先表示实例仍在恢复且已暂停，再显示第一行为 &lt;code&gt;base&lt;/code&gt;、第二行为 &lt;code&gt;keep&lt;/code&gt;。SQL 断言核对两行组合后的精确内容。这同时证明较早的正确事务被保留，较晚的错误变更被排除，比“数据库已经接受连接”更有说服力：服务可能在恢复尚未到达目标时，就允许只读连接进入。&lt;/p&gt;
&lt;p&gt;负向对照另外复制归档，但排除备份完成后必需的日志段。原归档保持完整，并且先确认基础备份本身没有带上被排除的那一段。使用不完整副本恢复时，最终必须因配置的目标尚未到达而发生致命错误。脚本等待该实验服务确实停止，并核对日志中的原因，而不是只凭首次启动命令的退出码下结论。缺段目标不可到达的标记，表示这项预期失败已经被观察到。&lt;/p&gt;
&lt;p&gt;所有实验集群都会在脚本退出时停止，打印目录保留日志与数据便于复核。这次演练不会把恢复副本提升或交给业务应用使用，恢复到正确内容与接管生产流量是不同的验证事项。复制出来的失败实验也不会破坏原始归档，便于在同一套材料上再次复现成功路径。&lt;/p&gt;
&lt;h2 id="接受恢复结果之前先核对"&gt;接受恢复结果之前先核对&lt;/h2&gt;
&lt;p&gt;目标处的暂停给了核对业务不变量的机会。除了总行数，还应检查关联记录、事务边界和最后一项可以接受的操作。恢复目标处继续执行会结束恢复，不等于请求任意重放目标之后的所有变更。若要追求更晚的目标，应按文档规定停止实例、调整目标再继续，避免错误理解暂停与继续的含义。&lt;/p&gt;
&lt;p&gt;记录基础备份身份、可用时间线、目标选择、恢复事实和实际耗时。配置文件及必需的外部软件也应纳入恢复方案，因为 WAL 不会恢复所有外部依赖。实验期间保护原始备份和归档，使不同目标的尝试有共同可信起点。一次成功证明的是一个具体目标；随着备份轮换，持续跨保留区间演练，才能判断承诺的恢复窗口是否始终可用。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/notes/backups-are-only-useful-after-a-restore-rehearsal/"&gt;备份只有在演练过恢复之后才有用&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/continuous-archiving.html"&gt;PostgreSQL 18 连续归档&lt;/a&gt;，基础备份、归档恢复与时间线要求，已核验 2026-09-10。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/runtime-config-wal.html"&gt;PostgreSQL 18 恢复目标&lt;/a&gt;，目标边界、暂停行为以及未到达目标时的致命失败，已核验 2026-09-10。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/functions-admin.html"&gt;PostgreSQL 18 管理函数&lt;/a&gt;，命名恢复点及实际回放暂停状态，已核验 2026-09-10。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/app-pgverifybackup.html"&gt;PostgreSQL 18 pg_verifybackup&lt;/a&gt;，清单验证及仍需实际恢复测试的原因，已核验 2026-09-10。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>UNLOGGED 表减少数据日志，也放弃内容的崩溃恢复</title><link>https://www.xbcatm.com/zh/posts/unlogged-tables-trade-durability/</link><pubDate>Mon, 24 May 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/unlogged-tables-trade-durability/</guid><description>非日志表的数据及索引变化跳过 WAL；干净重启保留行，崩溃恢复和物理主备切换则需要重建内容。</description><content:encoded>&lt;p&gt;PostgreSQL 的 UNLOGGED 表保留正常 SQL 事务行为，但放弃其内容的崩溃恢复能力。它适合能够重复生成的暂存数据，不适合把唯一副本托付给一次提交。判断边界是服务器是否经历了非正常关闭后的恢复，不能简单地把“重启”两个字等同于数据丢失，也不能因为一次正常重启后行还在，就认为它具备持久性保证。&lt;/p&gt;
&lt;h2 id="哪些内容跳过-wal"&gt;哪些内容跳过 WAL&lt;/h2&gt;
&lt;p&gt;非日志表的数据变化及其索引变化不写入预写日志。系统目录的变化仍可能产生 WAL，同一个事务还可能修改普通表，所以不能把整个工作负载描述为“完全没有 WAL”。表的数据页仍然占据存储空间，仍要经过缓冲区管理和后续写回；UNLOGGED 并不是内存表，也不是不需要磁盘的缓存。写入收益来自减少特定日志工作，实际改善幅度需要测量，不能承诺固定倍数。&lt;/p&gt;
&lt;p&gt;服务器正常运行时，事务、快照、约束和回滚仍然生效。语句被拒绝或应用断开连接，并不意味着此前提交的全部行都会消失。但是后端进程异常退出可能导致服务器重启其他进程并进入恢复，因此也不能声称“应用事件绝不影响非日志表”。排查时应查看 PostgreSQL 日志和恢复过程，而不是仅凭事故最初发生在哪一层就推断数据状态。&lt;/p&gt;
&lt;h2 id="重启与复制的边界"&gt;重启与复制的边界&lt;/h2&gt;
&lt;p&gt;干净关闭后，非日志表内容会保留到下次启动。&lt;code&gt;pg_ctl&lt;/code&gt; 的 fast 模式会回滚正在执行的事务并正常关闭，immediate 模式则直接中止进程，不完成干净关闭。后者需要崩溃恢复，恢复期间非日志关系会被重置：表定义仍在，但此前提交的行不再存在。主机维护若完成了干净关闭，并不会仅因为“重启”就清空这类表；反过来，不能把正常关闭的测试当成抗崩溃证明。&lt;/p&gt;
&lt;p&gt;物理备库不会收到非日志表的数据内容。备库仍处于恢复模式时，读取这类关系会报错，而不是成功返回零行。提升为主库后可以读取它，但没有已经复制过来的行可以恢复。因此故障切换流程必须先重建或重新填充，再允许依赖这些数据的消费者进入服务。主备拥有同一个表定义，只说明目录信息存在，并不代表表中业务数据已经复制。&lt;/p&gt;
&lt;p&gt;普通日志表不能用外键引用非日志表。PostgreSQL 会拒绝这样的建表语句，因为持久化的子表引用不能安全依赖可能在恢复中消失的父表行。下面的实验直接检查这一限制，不会把它描述成“外键还在、父表行没了、数据库默默允许不一致”。设计暂存区时，要把关系约束放到符合持久性边界的位置，而不是事后修补孤立记录。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;一个常见事故是导入程序把文件装入非日志暂存表后就确认成功，并删除唯一的原始文件。随后服务器崩溃，暂存行消失，而输入已无处重取，这就变成不可恢复的数据丢失。原始输入应该至少保留到持久化目标提交成功，重建流程也必须知道需要哪一个输入版本。去重记录和导入检查点要属于持久化契约，不能和暂存内容一起丢掉。&lt;/p&gt;
&lt;p&gt;另一个问题出现在报表服务改读备库，或刚完成提升就立即对外服务。只检测行数为零会漏掉恢复模式下的读取错误；无限重试也无法生成不存在的数据。应明确标记“正在重建”，在重建完成且结果验证通过前控制请求准入。演练要测量重建耗时、上游是否可用、记录数及内容是否符合预期；只建回一张空表不算恢复，能运行的脚本也不等于已经证明恢复时间满足要求。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;把 Bash 代码保存为 &lt;code&gt;unlogged.sh&lt;/code&gt;。它要求以非 root 用户执行 PostgreSQL 18 的命令行工具。下面的容器命令提供工具，不发布网络端口，也不挂载宿主机数据；两个实验数据库都位于容器内的新临时目录。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run --rm --network none --user postgres --entrypoint bash -i &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; postgres:18-alpine &amp;lt; unlogged.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;pg_lab&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d /tmp/unlogged-lab.XXXXXX&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cleanup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; cluster in standby primary&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$cluster&lt;/span&gt;&lt;span class="s2"&gt;/postmaster.pid&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$cluster&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -m immediate -w stop &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rm -rf -- &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; cleanup EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;primary&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; psql -X -h &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -p &lt;span class="m"&gt;55432&lt;/span&gt; -d postgres -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;standby&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; psql -X -h &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -p &lt;span class="m"&gt;55433&lt;/span&gt; -d postgres -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start_primary&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/primary&amp;#34;&lt;/span&gt; -l &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/primary.log&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="s2"&gt;&amp;#34;-k &lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt; -p 55432 -c listen_addresses=&amp;#39;&amp;#39;&amp;#34;&lt;/span&gt; -w start &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;initdb -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/primary&amp;#34;&lt;/span&gt; --no-locale --encoding&lt;span class="o"&gt;=&lt;/span&gt;UTF8 --auth&lt;span class="o"&gt;=&lt;/span&gt;trust &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start_primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;primary &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;SQL&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CREATE TABLE durable (id integer PRIMARY KEY);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CREATE UNLOGGED TABLE scratch (id integer PRIMARY KEY);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;INSERT INTO durable VALUES (1);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;INSERT INTO scratch VALUES (1);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;BEGIN;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;INSERT INTO scratch VALUES (2);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ROLLBACK;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;DO $$
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;BEGIN
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; EXECUTE &amp;#39;CREATE TABLE durable_ref (id integer REFERENCES scratch(id))&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; RAISE EXCEPTION &amp;#39;a logged table unexpectedly referenced an unlogged table&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EXCEPTION WHEN invalid_table_definition THEN
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; RAISE NOTICE &amp;#39;logged-to-unlogged foreign key rejected&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;END $$;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SELECT (SELECT count(*) FROM durable), (SELECT count(*) FROM scratch)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;primary -Atc &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$counts&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1|1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/primary&amp;#34;&lt;/span&gt; -m fast -w stop &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start_primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;primary -Atc &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$counts&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1|1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;clean restart: 1|1\n&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_basebackup -h &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -p &lt;span class="m"&gt;55432&lt;/span&gt; -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/standby&amp;#34;&lt;/span&gt; -R -X stream -c fast
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/standby&amp;#34;&lt;/span&gt; -l &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/standby.log&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="s2"&gt;&amp;#34;-k &lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt; -p 55433 -c listen_addresses=&amp;#39;&amp;#39;&amp;#34;&lt;/span&gt; -w start &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;standby -Atc &lt;span class="s1"&gt;&amp;#39;SELECT pg_is_in_recovery()&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;standby -Atc &lt;span class="s1"&gt;&amp;#39;SELECT count(*) FROM durable&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; standby -Atc &lt;span class="s1"&gt;&amp;#39;SELECT count(*) FROM scratch&amp;#39;&lt;/span&gt; 2&amp;gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/read-error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;unlogged read unexpectedly succeeded on standby&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -F &lt;span class="s1"&gt;&amp;#39;cannot access temporary or unlogged relations during recovery&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/read-error&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/standby&amp;#34;&lt;/span&gt; -w promote &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;standby -Atc &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$counts&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1|0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;promoted standby: 1|0\n&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_ctl -D &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pg_lab&lt;/span&gt;&lt;span class="s2"&gt;/primary&amp;#34;&lt;/span&gt; -m immediate -w stop &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start_primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;primary -Atc &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$counts&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1|0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;crash recovery: 1|0\n&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;primary -Atc &lt;span class="s1"&gt;&amp;#39;SHOW server_version&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;PASS: rollback, foreign key restriction, clean restart, standby, promotion, crash&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;主库与备库使用同一个临时目录中的不同 Unix 套接字端口，退出时的 trap 只关闭这两个实验集群并移除其文件。实验故意对临时主库执行 immediate 关闭，不能把这一步改成已有数据库目录。PostgreSQL 18.6 上，回滚和正常重启后的计数都是 &lt;code&gt;1|1&lt;/code&gt;；仍处于恢复模式的备库拒绝读取 scratch。备库提升和主库崩溃恢复后分别得到 &lt;code&gt;1|0&lt;/code&gt;，表明持久化行还在而暂存内容不在。错误文本断言使用集群的 C locale，避免本地化消息造成误判。&lt;/p&gt;
&lt;p&gt;这些结果验证生命周期和约束语义，不代表已经测出吞吐量改善，也不证明所有文件系统在真实掉电后的表现。性能评估还应计入输入保留和重建成本。持久化目标必须保留正常 WAL 与持久性配置；如果同时关闭这些保护，就改变了对照条件，无法再用本实验说明普通表与非日志表的区别。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/sql-createtable.html"&gt;PostgreSQL CREATE TABLE&lt;/a&gt;，非日志表内容及其关联索引，已核验 2026-09-10。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/wal-intro.html"&gt;PostgreSQL WAL 介绍&lt;/a&gt;，恢复与持久性的作用，已核验 2026-09-10。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/app-pg-ctl.html"&gt;PostgreSQL pg_ctl&lt;/a&gt;，干净关闭与立即关闭模式，已核验 2026-09-10。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>