如何破坏数据库:模拟 XID 回卷

本页目录

参见第一部分:Part 1: How to Corrupt

直接模拟方法

这个方法需要一些时间,但效果很好。已在多个地方讨论过:

首先,开启一个"长时间运行的事务" (分配一个正常的 XID 给它,因为它将是一个写事务) — 并保持该会话处于开启状态 (我们将使用一个命名管道,也称为 FIFO,让 psql 会话夯住):

Shell
mkfifo dummy

psql -Xc "
  set idle_in_transaction_session_timeout = 0;
  begin;
  select pg_current_xact_id()
" \
-f dummy &

确保会话已开启并处于事务中:

Shell
psql -Xc "select state
  from pg_stat_activity
  where
    pid <> pg_backend_pid()
    and query ~ 'idle_in_tran'
"
        state
---------------------
 idle in transaction
(1 row)

现在,长时间运行的事务已经分配了 XID,我们只需要高速推进事务 ID 即可 — 可以使用同一个函数,以及具有多个连接的 pgbench:

Shell
pgbench -c8 -j8 -P60 -T36000 -rn \
  -f - <<< 'select pg_current_xact_id()'

这应该会以非常高的速度 (100-200k TPS) 推进当前 XID。而且由于我们有一个处于开启状态的长事务,因此 autovacuum 无法运行。

在 pgbench 运行时,可以使用包含 XID/MultiXID 回卷检查的监控工具观察数据库状态 (每个工具都应该有,但并非所有工具),或者使用一些代码片段 — 例如,Managing Transaction ID Exhaustion (Wraparound) in PostgreSQL

几小时后:

Shell
WARNING:  database "nik" must be vacuumed within 39960308 transactions
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.
You might also need to commit or roll back old prepared transactions, or drop stale replication slots.

如果继续:

PSQL
nik=# create table t3();
ERROR:  database is not accepting commands to avoid wraparound data loss in database "nik"
HINT:  Stop the postmaster and vacuum that database in single-user mode.
You might also need to commit or roll back old prepared transactions, or drop stale replication slots.

太好了,损害已经完成!现在我们可以释放 FIFO (这将关闭长时间的"闲置中的事务"会话):

Shell
exec 3>dummy && exec 3>&-

如何解决 — 基础

如何摆脱这种情况?我们可能会在另一个教程中详细讨论,但让我们先谈谈一些基础知识。

查看实际用例,从其他人的错误中吸取教训:

解决此问题通常需要很多时间。传统上,如上面的 HINT 所建议的那样,在单用户模式下完成。但我非常喜欢来自 @PostSQL 的想法 — 来自 GCP 团队 — 参照 Do you vacuum every day? 以及 pgsql-hackers 邮件列表 We should stop telling users to "vacuum that database in single-user mode".

另一种模拟回卷的方法

还有另一种模拟方法,它工作得更快 — 无需等待很多小时,可以使用 pg_resetwal 的 -x (文档) 选项来快速推进事务ID。

这个方法在 Transaction ID wraparound: a walk on the wild side 中有所描述。它非常有趣,看起来是这样的:

Shell
pg_ctl stop -D $PGDATA

pg_resetwal -x 2137483648 -D testdb ### 2^31 - 10000000

dd if=/dev/zero of=$PGDATA/pg_xact/07F6 bs=8192 count=15

MultiXact ID 回卷

MultiXact ID 回卷也有可能发生:Multixacts and Wraparound。这种风险经常被忽略,但它确实存在,尤其是如果你使用外键和/或结合子事务使用 SELECT ... FOR SHARE (以及 SELECT ... FOR UPDATE)。

一些相关的文章可以帮助你了解:

模拟 MultiXact ID 回卷也是可能的 — 为此,我们需要创建多个重叠事务来锁定相同的行 (例如,使用显式的 SELECT ... FOR SHARE)。

🎯 TODO: 演示示例 — 留待将来编辑。

每个人不仅应该监控 (并设置告警) 传统 XID 回卷的风险,还应监控 MultiXID 回卷风险。这应该包括在显示"容量" "capacity" 使用情况的代码片段中。

🎯 TODO: 展示 XID 和 MultiXID 使用情况的代码片段,包括数据库级别和表级别。显然,现实中遇到 MultiXID 回卷的情况较少 — 我几乎没见到有人使用 datminmxid 和 relminmxid。基本查询:

SQL
select
  datname,
  age(datfrozenxid),
  mxid_age(datminmxid)
from pg_database;

讨论与补充

在 GitHub 查看讨论
其他历史讨论(1)

支持作者

感谢支持 PostgreSQL Howto 的长期维护。

微信赞赏码

读者交流群

二维码若已过期,可通过公众号 PostgreSQL 学徒或邮件联系。

PostgreSQL 学徒读者交流群二维码