[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
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 74/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- c, postgresql
- Lĩnh vực
- databases, distributed-systems
Hướng nghiên cứu
Bắt đầu với ParsePrepareRecord() trong src/backend/access/rmgrdesc/xactdesc.c và so sánh src/include/access/xact.h với TwoPhaseFileHeader trong src/include/access/twophase_xlog.h; theo dõi StartPrepare()/EndPrepare() trong twophase.c. Tái hiện vấn đề bằng các lệnh pg_waldump và logical-decoding được cung cấp. Hoàn thành có nghĩa là PREPARE hiển thị và phát ra gid thực, giải mã two-phase chính xác và không có hồi quy trong bố cục bản ghi liên quan.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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
- Ngôn ngữ chính
- C
- Star
- 1.4k
- Fork
- 248
- Merge trung bình
- 4 ngày 10 giờ
- Pull request đã merge (30 ngày)
- 40
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của apache/cloudberry
-
type: Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
apache/cloudberry#1885 · 2 reaction ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
apache/cloudberry#1825 ·
-
type: Bug
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 65/100
apache/cloudberry#2048 · 1 reaction ·
-
type: Bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 40/100
apache/cloudberry#2047 ·
-
type: Bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
apache/cloudberry#2046 · 1 bình luận ·
Tất cả issue của apache/cloudberry
Issue tương tự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
bradcypert/plum#53 ·
-
Component: GLib
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Status: Opened
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
nextbsd/nextbsd-userland#285 ·