Thread
-
Fix pg_get_multixact_stats() members_size calculation
Chao Li <li.evan.chao@gmail.com> — 2026-05-22T07:02:48Z
Hi, While testing pg_get_multixact_stats(), I found that it can undercount members_size. This is a simple repro, starting from a fresh cluster. Session 1: ``` evantest=# create table t (i int primary key); CREATE TABLE evantest=# insert into t values (1); INSERT 0 1 evantest=# evantest=# begin; BEGIN evantest=*# select * from t where i = 1 for share; i --- 1 (1 row) ``` Session 2: ``` evantest=# begin; BEGIN evantest=*# select * from t where i = 1 for share; i --- 1 (1 row) evantest=*# select * from pg_get_multixact_stats(); num_mxids | num_members | members_size | oldest_multixact -----------+-------------+--------------+------------------ 1 | 2 | 0 | 1 (1 row) ``` num_members is reported as 2, but members_size is reported as 0, which looks surprising. The current implementation does the division before multiplying by the member-group size: ``` static inline uint64 MultiXactOffsetStorageSize(MultiXactOffset new_offset, MultiXactOffset old_offset) { Assert(new_offset >= old_offset); return (uint64) ((new_offset - old_offset) / MULTIXACT_MEMBERS_PER_MEMBERGROUP) * MULTIXACT_MEMBERGROUP_SIZE; } ``` Since MULTIXACT_MEMBERS_PER_MEMBERGROUP is 4, any remainder of 1 to 3 members is truncated. This is less visible with large values, but it becomes obvious with a small number of members, as in the example above. I checked the related commits, 0e3ad4b96aedee57fc2694e28486fe0ceca8110a and 97b101776ce23dd6c4abbdae213806bc24ed6133, and I didn't see anything suggesting that this truncation was intentional. So even though this is a small issue, I think it is better to fix it before PostgreSQL 19 is released. The fix is straightforward, just compute the per-member size first, which is MULTIXACT_MEMBERGROUP_SIZE / MULTIXACT_MEMBERS_PER_MEMBERGROUP, and then multiply that by (new_offset - old_offset). The doc example also seems to confirm that members_size is meant to be num_members * 5, without rounding for group alignment or accounting for the 12 bytes wasted per page: ``` <screen> =# SELECT *, pg_size_pretty(members_size) members_size_pretty FROM pg_catalog.pg_get_multixact_stats(); num_mxids | num_members | members_size | oldest_multixact | members_size_pretty -----------+-------------+--------------+------------------+--------------------- 311740299 | 2785241176 | 13926205880 | 2 | 13 GB (1 row) </screen> ``` Where 2785241176 * 5 = 13926205880. With the fix, the same test reports members_size as 10: ``` evantest=*# select * from pg_get_multixact_stats(); num_mxids | num_members | members_size | oldest_multixact -----------+-------------+--------------+------------------ 1 | 2 | 10 | 1 (1 row) ``` The attached patch also updates the existing isolation test to cover members_size. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/