[Bug] ParsePrepareRecord() parses XLOG_XACT_PREPARE with a stale xl_xact_prepare layout: gid decodes as empty in pg_waldump and two-phase logical decoding
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 74/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- c, postgresql
調査の方向性
src/backend/access/rmgrdesc/xactdesc.c の ParsePrepareRecord() から始め、src/include/access/xact.h と src/include/access/twophase_xlog.h の TwoPhaseFileHeader を比較します。twophase.c の StartPrepare()/EndPrepare() を追跡します。提供されている pg_waldump および logical-decoding コマンドで問題を再現します。PREPARE が実際の gid を表示して出力し、two-phase decoding が正しく行われ、関連するレコードレイアウトにリグレッションがなければ完了です。
索引モデルが issue の本文から書いたものです。
説明
Apache Cloudberry version
main (89baa48a4f4, 2026-09-07) and local 3.0.0-devel+dev.8551.gc781604c5ba (PostgreSQL 16.9 base). The mismatch has existed since the GPDB-era commits b0208894eb5 / 68c0ff2d4ec / b74f174b6cd (2019) that extended TwoPhaseFileHeader.
What happened
ParsePrepareRecord() in src/backend/access/rmgrdesc/xactdesc.c walks a XLOG_XACT_PREPARE record using the layout of xl_xact_prepare (src/include/access/xact.h). But the record is actually written by StartPrepare()/EndPrepare() (twophase.c) as a TwoPhaseFileHeader (src/include/access/twophase_xlog.h), and Cloudberry extended that struct with fields that xl_xact_prepare never got:
TwoPhaseFileHeader (what is written, 96 bytes) xl_xact_prepare (what is parsed, 72 bytes)
... ...
int32 nabortrels; int32 nabortrels;
int32 ncommitdbs; <- extra int32 ncommitstats;
int32 nabortdbs; <- extra int32 nabortstats;
int32 ncommitstats; int32 ninvalmsgs;
int32 nabortstats; bool initfileinval;
int32 ninvalmsgs; uint16 gidlen;
bool initfileinval; XLogRecPtr origin_lsn;
Oid tablespace_oid_to_delete_on_abort; <- extra TimestampTz origin_timestamp;
Oid tablespace_oid_to_delete_on_commit; <- extra
uint16 gidlen;
XLogRecPtr origin_lsn;
TimestampTz origin_timestamp;
Consequences of reading through the wrong struct:
xlrec->gidlenis read from offset 54, which is the upper half of the realnabortstats(normally 0), soparsed->twophase_gidis an empty string.xlrec->ncommitstats/nabortstats/ninvalmsgsactually readncommitdbs/nabortdbs/ncommitstats;initfileinvalandorigin_lsnread from unrelated bytes.bufptrstarts at offset 72 instead of 96, soparsed->subxacts,xlocators,abortlocators,stats,abortstats,msgsall point at the wrong bytes. The parser also never skips thecommitdbs/abortdbs(DbDirNode) arrays thatEndPrepare()writes betweenabortrelsand the stats arrays.
Two user-visible symptoms:
-
pg_waldumpprints an empty gid for every PREPARE record (every distributed transaction on a segment):rmgr: Transaction ... desc: PREPARE gid : 2026-09-09 05:06:49.215203 CST rmgr: Transaction ... desc: COMMIT_PREPARED 5256: 2026-09-09 05:06:49.216803 CST gxid = 34413(
COMMIT_PREPAREDis parsed byParseCommitRecord()and is fine, which shows the gid really is34413in the WAL.) -
Logical decoding with
two_phaseemitsPREPARE TRANSACTION ''with an empty gid, while the matchingCOMMIT PREPAREDcarries the real gid. A pgoutput subscriber would try toPREPARE TRANSACTION ''. Worse,DecodePrepare()passes the shiftedparsed->subxacts/nsubxactsintoSnapBuildCommitTxn()andReorderBufferPrepare(), so decoding can consume garbage subxact ids.
Crash recovery is not affected because xact_redo() for PREPARE goes through twophase.c's own TwoPhaseFileHeader-based parsing, not ParsePrepareRecord().
What you think should happen instead
xl_xact_prepare must have the same layout as TwoPhaseFileHeader (the upstream PostgreSQL comment in twophase.c says the two must stay in sync), and ParsePrepareRecord() must skip the commitdbs / abortdbs arrays. Then pg_waldump shows PREPARE gid 34413: ... and two-phase logical decoding emits PREPARE TRANSACTION '34413'.
A static assert comparing sizeof(xl_xact_prepare) and sizeof(TwoPhaseFileHeader) (or simply typedef-ing one to the other) would keep this from regressing.
How to reproduce
On a demo cluster with wal_level = logical (needed only for step 2; step 1 works with replica):
-- via coordinator
create table t2pc(id int primary key, v text) distributed by (id);
insert into t2pc select g, 'x' from generate_series(1,6) g; -- touches all segments -> 2PC
-
pg_waldumpon any primary segment:pg_waldump -r Transaction <segdatadir>/pg_wal/<current wal file> | grep -E 'PREPARE|COMMIT_PREPARED' | tail -2shows
PREPARE gid : <timestamp>(empty gid) followed byCOMMIT_PREPARED ... gxid = <n>. -
two-phase logical decoding on a segment (utility mode):
-- PGOPTIONS='-c gp_role=utility' psql -p <segport> select * from pg_create_logical_replication_slot('s2pc', 'test_decoding', false, true); -- run the INSERT above on the coordinator select data from pg_logical_slot_peek_changes('s2pc', NULL, NULL, 'include-xids', '1');output:
BEGIN 5256 table public.t2pc: INSERT: id[integer]:2 v[text]:'x' ... PREPARE TRANSACTION '', txid 5256 COMMIT PREPARED '34413', txid 5256
Operating System
CentOS 7 (kernel 3.10.0-1160), gcc 12.2.1
Anything else
Found while prototyping an MPP logical-replication receiver, where the segment-side gid (which is the distributed xid) is the key for aligning per-segment streams. The fix is small and self-contained; happy to send a PR.
Are you willing to submit PR?
- Yes I am willing to submit a PR!
Code of Conduct
- I agree to follow this project's Code of Conduct
- 主要言語
- C
- スター
- 1.4k
- フォーク
- 248
- 平均マージ
- 4日 10時間
- マージ済み PR(30日)
- 40
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/cloudberry のほかの issue
-
type: Bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
apache/cloudberry#1885 · リアクション 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
apache/cloudberry#1825 ·
-
type: Bug
難易度 3/5 1〜2日 初心者へのやさしさ 65/100
apache/cloudberry#2048 · リアクション 1 件 ·
-
type: Bug
難易度 4/5 3〜5日 初心者へのやさしさ 40/100
apache/cloudberry#2047 ·
-
type: Bug
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
apache/cloudberry#2046 · コメント 1 件 ·
apache/cloudberry の issue をすべて見る
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
bradcypert/plum#53 ·
-
Component: GLib
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
Status: Opened
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
nextbsd/nextbsd-userland#285 ·