数据集历史线从快照分支到克隆;另一快照上的独立锁形表示用户 hold。

ZFS 快照给文件系统一个回退键

ZFS 快照通过写时复制引用保留某一时点的数据集视图,不需要把全部数据另复制一遍。只要还有引用需要旧块,它们就继续保留。但创建仍涉及元数据与同步工作,不能承诺零分配,也不能把在提示符重画之前返回当作实测保证。本次没有运行 ZFS,保留文件中也未找到原文所称实验的原始输出,所以数值与运行效果必须单独限定来源。 这样的历史适合本地恢复,但 tank/data 的快照仍位于同一个池。让池不可用的故障,也可能让全部快照不可用。这描述的是共享故障边界,不是断言每次控制器故障都会原子地毁掉所有块。独立备份或复制还需要自己的保留策略及恢复验证,不能仅因另一处存在相同名字,就宣布已经获得完整灾难恢复能力。 为一个一致的树命名 快照原子地捕获数据集的文件系统状态;递归 -r 会让后代数据集在同一时刻建立快照。不递归时,子数据集被遗漏,并不是自动晚一点另拍一份。文件系统一致不等于应用检查点:数据库缓冲、日志以及选定数据集之外的文件,仍需遵循应用支持的备份或崩溃恢复流程。必须协调所有必要部分,才能把整组状态作为可恢复对象。 tank/data@daily-2026-09-16 这样的名字可以辅助保留策略,还应结合创建时间、属性与备份记录;名字本身不是删除资格的证明。zfs list -t snapshot -o name,used,refer,creation -s creation 用于观察快照及空间统计,真正清理之前还要检查依赖和 hold。自动命名也可以被可靠管理,关键是保留规则有明确、可追溯的依据。 空间在哪里:zfs list -o space zfs list -o space tank/data 选择 NAME、AVAIL、USED、USEDSNAP、USEDDS、USEDREFRESERV 与 USEDCHILD,不包含 REFER。USED 分解为四项 used-by 类别,不能写成 REFER 加 USEDSNAP。REFER 表示数据集可访问的数据,其中也可能有共享块。单份快照的 USED 统计其独有引用,删除其他快照还会改变这个值,所以逐份相加不等于整体可回收空间。 ...

三月 21, 2028 · 2 分钟 · XB Field Notes
同一份摘要正确的归档,先因新集群缺少角色而恢复失败;补齐角色后恢复两笔订单,合计 6075 分。

没验证的备份只是空想

备份任务完成,证明备份过程生成了一份材料;恢复演练则要证明这份材料、现有工具以及必要环境,能共同恢复出可用系统。这是不同的结论。恢复证据应明确使用了哪份备份、恢复到什么状态、补齐了哪些依赖,以及花了多久才让结果对应用真正有用。不能只保留一个任务成功时间,就省略后面的验证。 坏掉的样子 合法的数据库归档仍然可能依赖范围之外的对象。PostgreSQL 的单库导出工具只导出一个数据库,角色却属于整个集群。在独立集群里恢复带授权信息的备份时,可能因为被引用的角色不存在而失败。如果演练始终在原集群进行,这种缺少恢复材料的问题就会被环境中已有的角色掩盖。 恢复错误也需要明确处理。恢复工具默认可以在 SQL 出错后继续,并在末尾报告错误数量。部分表能够查询,不代表半恢复状态就可以接受。这个小型夹具选择把恢复放进一个事务,使成功具有原子性;发生预期失败时,也不会留下已经恢复了一半的表,便于确认依赖修正之后重新执行的结果。 恢复位置正确是另一项要求。完全恢复成功的备份,也可能按设计不包含它之后发生的变更。校验和证明材料与记录一致,不证明其中包含最新业务事件。同样,总行数相同,并不能排除金额、关联关系或权限错误。验证项需要表达应用语义,不能因为容易查询就只检查几个不能区分正确与错误状态的数字。 摘要不能补齐缺失的集群角色。将同一归档恢复到新目标后,还要核对业务状态、约束与实际权限;本轮未重跑原文 PostgreSQL 演练。 快速验证 在 Linux 环境中使用非超级操作系统账户运行脚本,准备 PostgreSQL 18 服务端、客户端和摘要校验工具。本次实际执行版本是 PostgreSQL 18.6。脚本建立互相独立的临时源集群与目标集群,使用私有 Unix 套接字,不打开网络监听。实验角色没有登录能力,通过受控角色切换验证授权,不涉及真实账户凭据。 #!/bin/sh set -eu umask 077 lab=$(mktemp -d /tmp/xbcatm-restore.XXXXXX) mkdir "$lab/socket" source=$lab/source target=$lab/target cleanup() { for cluster in "$target" "$source"; do if test -f "$cluster/postmaster.pid"; then pg_ctl -D "$cluster" -m fast -w stop >/dev/null fi done } trap cleanup EXIT for cluster in "$source" "$target"; do initdb -D "$cluster" --no-locale --encoding=UTF8 --auth-local=trust --auth-host=reject >/dev/null cat >> "$cluster/postgresql.conf" <<CONF listen_addresses = '' unix_socket_directories = '$lab/socket' CONF done printf 'port = 56432\n' >> "$source/postgresql.conf" printf 'port = 56433\n' >> "$target/postgresql.conf" pg_ctl -D "$source" -l "$lab/source.log" -w -t 15 start >/dev/null pg_ctl -D "$target" -l "$lab/target.log" -w -t 15 start >/dev/null export PGHOST=$lab/socket PGPORT=56432 PGDATABASE=postgres PGUSER=$(id -un) export PGCONNECT_TIMEOUT=2 psql -X -v ON_ERROR_STOP=1 -qAt <<'SQL' CREATE ROLE backup_reader NOLOGIN; CREATE TABLE customers (id integer PRIMARY KEY,name text NOT NULL); CREATE TABLE orders (id integer PRIMARY KEY,customer_id integer REFERENCES customers,amount_cents integer CHECK (amount_cents>0)); INSERT INTO customers VALUES (1,'Ada'); INSERT INTO orders VALUES (10,1,4210),(11,1,1865); CREATE VIEW order_totals AS SELECT c.name,sum(o.amount_cents) AS total_cents FROM customers c JOIN orders o ON o.customer_id=c.id GROUP BY c.id,c.name; GRANT SELECT ON customers,orders,order_totals TO backup_reader; SQL pg_dump -Fc -w -f "$lab/snapshot.dump" postgres sha256sum "$lab/snapshot.dump" > "$lab/snapshot.sha256" psql -X -v ON_ERROR_STOP=1 -qAt -c 'INSERT INTO orders VALUES (12,1,1000)' printf 'source_after_backup_orders ' psql -X -v ON_ERROR_STOP=1 -qAt -c 'SELECT count(*) FROM orders' sha256sum -c "$lab/snapshot.sha256" if PGPORT=56433 pg_restore --single-transaction --exit-on-error -w -d postgres "$lab/snapshot.dump" >"$lab/missing-role.log" 2>&1; then echo 'restore unexpectedly ignored a missing role' >&2 exit 1 fi grep -F 'role "backup_reader" does not exist' "$lab/missing-role.log" test "$(PGPORT=56433 psql -X -v ON_ERROR_STOP=1 -qAt -c "SELECT to_regclass('public.orders') IS NULL")" = t echo failed_restore_rolled_back PGPORT=56433 psql -X -v ON_ERROR_STOP=1 -qAt -c 'CREATE ROLE backup_reader NOLOGIN' PGPORT=56433 pg_restore --single-transaction --exit-on-error -w -d postgres "$lab/snapshot.dump" PGPORT=56433 psql -X -v ON_ERROR_STOP=1 -qAt <<'SQL' SELECT count(*) FROM orders; SELECT name,total_cents FROM order_totals; SELECT has_table_privilege('backup_reader','orders','SELECT'),has_table_privilege('backup_reader','orders','INSERT'); DO $$ BEGIN IF (SELECT count(*) FROM orders)<>2 OR (SELECT string_agg(id||':'||amount_cents,',' ORDER BY id) FROM orders) IS DISTINCT FROM '10:4210,11:1865' OR (SELECT total_cents FROM order_totals WHERE name='Ada') IS DISTINCT FROM 6075 OR (SELECT count(*) FROM orders o LEFT JOIN customers c ON c.id=o.customer_id WHERE c.id IS NULL)<>0 THEN RAISE EXCEPTION 'restored business data mismatch'; END IF; END $$; DO $$ BEGIN BEGIN INSERT INTO orders VALUES (98,999,100); RAISE EXCEPTION 'restored foreign key unexpectedly accepted an orphan'; EXCEPTION WHEN foreign_key_violation THEN RAISE NOTICE 'restored foreign key rejected orphan'; END; END $$; SET ROLE backup_reader; SELECT id,amount_cents FROM orders ORDER BY id; DO $$ BEGIN BEGIN INSERT INTO orders VALUES (99,1,100); RAISE EXCEPTION 'read-only role unexpectedly wrote data'; EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'read-only role rejected write'; END; END $$; SQL printf 'lab_directory %s\n' "$lab" 源库包含一个客户、两笔订单、汇总视图、外键以及只读角色授权。自定义格式归档捕获这个状态,之后源库又增加第三笔订单。输出中源库订单数为三,证明选定备份点之外确实存在后续活动,避免测试把当前源库和已经保存的快照混为一谈。恢复目标是否应该包含第三笔,需要由选择的备份点决定。 ...

六月 2, 2027 · 3 分钟 · XB Field Notes