From 6353c2dc45d9cdc64c5206ec576c578a3d2edd08 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Fri, 22 May 2026 14:47:21 +0800 Subject: [PATCH v1] Fix pg_get_multixact_stats() members_size calculation pg_get_multixact_stats() reports members_size as the storage used by the currently retained multixact member entries. However, MultiXactOffsetStorageSize() divided the member count by the number of members per storage group before multiplying by the group size, so it rounded down and reported zero for 1-3 retained members. This patch changes to compute the size from the member count directly. Extend the multixact-stats isolation test to include members_size in its snapshots and verify that it matches the reported member count. Author: Chao Li Reviewed-by: Discussion: https://postgr.es/m/ --- src/include/access/multixact_internal.h | 9 +++-- .../isolation/expected/multixact-stats.out | 36 +++++++++++-------- src/test/isolation/specs/multixact-stats.spec | 31 ++++++++++------ 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/include/access/multixact_internal.h b/src/include/access/multixact_internal.h index 82349ea0d32..09755511376 100644 --- a/src/include/access/multixact_internal.h +++ b/src/include/access/multixact_internal.h @@ -126,9 +126,14 @@ static inline uint64 MultiXactOffsetStorageSize(MultiXactOffset new_offset, MultiXactOffset old_offset) { + uint64 size_per_member; + Assert(new_offset >= old_offset); - return (uint64) ((new_offset - old_offset) / MULTIXACT_MEMBERS_PER_MEMBERGROUP) * - MULTIXACT_MEMBERGROUP_SIZE; + Assert(MULTIXACT_MEMBERGROUP_SIZE % MULTIXACT_MEMBERS_PER_MEMBERGROUP == 0); + + size_per_member = MULTIXACT_MEMBERGROUP_SIZE / MULTIXACT_MEMBERS_PER_MEMBERGROUP; + + return (new_offset - old_offset) * size_per_member; } #endif /* MULTIXACT_INTERNAL_H */ diff --git a/src/test/isolation/expected/multixact-stats.out b/src/test/isolation/expected/multixact-stats.out index 27a6510c4ad..f170b04c3e4 100644 --- a/src/test/isolation/expected/multixact-stats.out +++ b/src/test/isolation/expected/multixact-stats.out @@ -3,7 +3,7 @@ Parsed test spec with 2 sessions starting permutation: snap0 s1_begin s1_lock snap1 s2_begin s2_lock snap2 check_while_pinned s1_commit s2_commit step snap0: CREATE TEMP TABLE snap0 AS - SELECT num_mxids, num_members, oldest_multixact + SELECT num_mxids, num_members, members_size, oldest_multixact FROM pg_get_multixact_stats(); step s1_begin: BEGIN; @@ -15,7 +15,7 @@ step s1_lock: SELECT 1 FROM mxq WHERE id=1 FOR KEY SHARE; step snap1: CREATE TEMP TABLE snap1 AS - SELECT num_mxids, num_members, oldest_multixact + SELECT num_mxids, num_members, members_size, oldest_multixact FROM pg_get_multixact_stats(); step s2_begin: BEGIN; @@ -27,7 +27,7 @@ step s2_lock: SELECT 1 FROM mxq WHERE id=1 FOR KEY SHARE; step snap2: CREATE TEMP TABLE snap2 AS - SELECT num_mxids, num_members, oldest_multixact + SELECT num_mxids, num_members, members_size, oldest_multixact FROM pg_get_multixact_stats(); step check_while_pinned: @@ -39,32 +39,37 @@ step check_while_pinned: ARRAY[ 'is_init_mxids', 'is_init_members', + 'is_init_members_size', 'is_init_oldest_mxid', - 'is_init_oldest_off', 'is_oldest_mxid_nondec_01', 'is_oldest_mxid_nondec_12', - 'is_oldest_off_nondec_01', - 'is_oldest_off_nondec_12', 'is_members_increased_ge1', + 'is_msize_matches_members', 'is_mxids_nondec_01', 'is_mxids_nondec_12', 'is_members_nondec_01', - 'is_members_nondec_12' + 'is_members_nondec_12', + 'is_msize_nondec_01', + 'is_msize_nondec_12' ], ARRAY[ (s2.num_mxids IS NOT NULL), (s2.num_members IS NOT NULL), + (s2.members_size IS NOT NULL), (s2.oldest_multixact IS NOT NULL), (s1.oldest_multixact::text::bigint >= COALESCE(s0.oldest_multixact::text::bigint, 0)), (s2.oldest_multixact::text::bigint >= COALESCE(s1.oldest_multixact::text::bigint, 0)), (s2.num_members >= COALESCE(s1.num_members, 0) + 1), + (s2.members_size = s2.num_members * 5), (s1.num_mxids >= COALESCE(s0.num_mxids, 0)), (s2.num_mxids >= COALESCE(s1.num_mxids, 0)), (s1.num_members >= COALESCE(s0.num_members, 0)), - (s2.num_members >= COALESCE(s1.num_members, 0)) + (s2.num_members >= COALESCE(s1.num_members, 0)), + (s1.members_size >= COALESCE(s0.members_size, 0)), + (s2.members_size >= COALESCE(s1.members_size, 0)) ] ) AS r(assertion, ok); @@ -72,18 +77,19 @@ assertion |ok ------------------------+-- is_init_mxids |t is_init_members |t +is_init_members_size |t is_init_oldest_mxid |t -is_init_oldest_off |t is_oldest_mxid_nondec_01|t is_oldest_mxid_nondec_12|t -is_oldest_off_nondec_01 |t -is_oldest_off_nondec_12 |t is_members_increased_ge1|t +is_msize_matches_members|t is_mxids_nondec_01 |t -is_mxids_nondec_12 | -is_members_nondec_01 | -is_members_nondec_12 | -(13 rows) +is_mxids_nondec_12 |t +is_members_nondec_01 |t +is_members_nondec_12 |t +is_msize_nondec_01 |t +is_msize_nondec_12 |t +(14 rows) step s1_commit: COMMIT; step s2_commit: COMMIT; diff --git a/src/test/isolation/specs/multixact-stats.spec b/src/test/isolation/specs/multixact-stats.spec index 07d4b11be6d..bc612e20818 100644 --- a/src/test/isolation/specs/multixact-stats.spec +++ b/src/test/isolation/specs/multixact-stats.spec @@ -4,8 +4,10 @@ # is pinned by two open transactions, we check some patterns that VACUUM and # FREEZE cannot violate: # 1) "members" increased by at least 1 when the second session locked the row. -# 2) (num_mxids / num_members) not decreased compared to earlier snapshots. -# 3) "oldest_*" fields never decreased. +# 2) "members_size" reflects the storage used by the member entries. +# 3) (num_mxids / num_members / members_size) not decreased compared to +# earlier snapshots. +# 4) "oldest_*" fields never decreased. # # This test does not run checks after releasing locks, as freezing and/or # truncation may shrink the multixact ranges calculated. @@ -39,14 +41,14 @@ step s2_commit { COMMIT; } # multixacts have not initialized yet. step snap0 { CREATE TEMP TABLE snap0 AS - SELECT num_mxids, num_members, oldest_multixact + SELECT num_mxids, num_members, members_size, oldest_multixact FROM pg_get_multixact_stats(); } # Save multixact state after s1 has locked the row. step snap1 { CREATE TEMP TABLE snap1 AS - SELECT num_mxids, num_members, oldest_multixact + SELECT num_mxids, num_members, members_size, oldest_multixact FROM pg_get_multixact_stats(); } @@ -54,21 +56,25 @@ step snap1 { # a multixact with at least 2 members. step snap2 { CREATE TEMP TABLE snap2 AS - SELECT num_mxids, num_members, oldest_multixact + SELECT num_mxids, num_members, members_size, oldest_multixact FROM pg_get_multixact_stats(); } # Pretty, deterministic key/value outputs based of boolean checks: # is_init_mxids : num_mxids not NULL # is_init_members : num_members not NULL +# is_init_members_size : members_size not NULL # is_init_oldest_mxid : oldest_multixact not NULL # is_oldest_mxid_nondec_01 : oldest_multixact not decreased (snap0->snap1) # is_oldest_mxid_nondec_12 : oldest_multixact did not decreased (snap1->snap2) # is_members_increased_ge1 : members increased by at least 1 when s2 joined +# is_msize_matches_members : members_size matches the member count # is_mxids_nondec_01 : num_mxids not decreased (snap0->snap1) # is_mxids_nondec_12 : num_mxids not decreased (snap1->snap2) # is_members_nondec_01 : num_members not decreased (snap0->snap1) # is_members_nondec_12 : num_members not decreased (snap1->snap2) +# is_msize_nondec_01 : members_size not decreased (snap0->snap1) +# is_msize_nondec_12 : members_size not decreased (snap1->snap2) step check_while_pinned { SELECT r.assertion, r.ok FROM snap0 s0 @@ -78,32 +84,37 @@ step check_while_pinned { ARRAY[ 'is_init_mxids', 'is_init_members', + 'is_init_members_size', 'is_init_oldest_mxid', - 'is_init_oldest_off', 'is_oldest_mxid_nondec_01', 'is_oldest_mxid_nondec_12', - 'is_oldest_off_nondec_01', - 'is_oldest_off_nondec_12', 'is_members_increased_ge1', + 'is_msize_matches_members', 'is_mxids_nondec_01', 'is_mxids_nondec_12', 'is_members_nondec_01', - 'is_members_nondec_12' + 'is_members_nondec_12', + 'is_msize_nondec_01', + 'is_msize_nondec_12' ], ARRAY[ (s2.num_mxids IS NOT NULL), (s2.num_members IS NOT NULL), + (s2.members_size IS NOT NULL), (s2.oldest_multixact IS NOT NULL), (s1.oldest_multixact::text::bigint >= COALESCE(s0.oldest_multixact::text::bigint, 0)), (s2.oldest_multixact::text::bigint >= COALESCE(s1.oldest_multixact::text::bigint, 0)), (s2.num_members >= COALESCE(s1.num_members, 0) + 1), + (s2.members_size = s2.num_members * 5), (s1.num_mxids >= COALESCE(s0.num_mxids, 0)), (s2.num_mxids >= COALESCE(s1.num_mxids, 0)), (s1.num_members >= COALESCE(s0.num_members, 0)), - (s2.num_members >= COALESCE(s1.num_members, 0)) + (s2.num_members >= COALESCE(s1.num_members, 0)), + (s1.members_size >= COALESCE(s0.members_size, 0)), + (s2.members_size >= COALESCE(s1.members_size, 0)) ] ) AS r(assertion, ok); } -- 2.50.1 (Apple Git-155)