Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

[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

オープン
#1,973 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
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 の本文から書いたものです。

説明

type: Bug
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->gidlen is read from offset 54, which is the upper half of the real nabortstats (normally 0), so parsed->twophase_gid is an empty string.
  • xlrec->ncommitstats / nabortstats / ninvalmsgs actually read ncommitdbs / nabortdbs / ncommitstats; initfileinval and origin_lsn read from unrelated bytes.
  • bufptr starts at offset 72 instead of 96, so parsed->subxacts, xlocators, abortlocators, stats, abortstats, msgs all point at the wrong bytes. The parser also never skips the commitdbs / abortdbs (DbDirNode) arrays that EndPrepare() writes between abortrels and the stats arrays.

Two user-visible symptoms:

  1. pg_waldump prints 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_PREPARED is parsed by ParseCommitRecord() and is fine, which shows the gid really is 34413 in the WAL.)

  2. Logical decoding with two_phase emits PREPARE TRANSACTION '' with an empty gid, while the matching COMMIT PREPARED carries the real gid. A pgoutput subscriber would try to PREPARE TRANSACTION ''. Worse, DecodePrepare() passes the shifted parsed->subxacts / nsubxacts into SnapBuildCommitTxn() and ReorderBufferPrepare(), 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
  1. pg_waldump on any primary segment:

    pg_waldump -r Transaction <segdatadir>/pg_wal/<current wal file> | grep -E 'PREPARE|COMMIT_PREPARED' | tail -2
    

    shows PREPARE gid : <timestamp> (empty gid) followed by COMMIT_PREPARED ... gxid = <n>.

  2. 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
主要言語
C
スター
1.4k
フォーク
248
平均マージ
4日 10時間
マージ済み PR(30日)
40

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

apache/cloudberry のほかの issue

apache/cloudberry の issue をすべて見る

似ている issue

C の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。