Fix pg_get_multixact_stats() members_size Calculation
Core Problem
The pg_get_multixact_stats() function, introduced to help DBAs monitor multixact storage consumption, contains an integer arithmetic bug that causes it to undercount the members_size value when the number of members is not evenly divisible by MULTIXACT_MEMBERS_PER_MEMBERGROUP (which is 4).
Technical Background
PostgreSQL's MultiXact mechanism stores member transaction IDs in a segmented on-disk format. Members are organized into "member groups" of 4 members each, and each group occupies MULTIXACT_MEMBERGROUP_SIZE bytes (20 bytes = 4 members × 5 bytes per member, where each member consists of a 4-byte TransactionId plus 1 byte of status flags).
The MultiXactOffsetStorageSize() inline function is responsible for converting a count of member slots (expressed as an offset difference) into an estimated byte size on disk:
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;
}
The bug is classic integer truncation: dividing by 4 before multiplying by 20 means any remainder (1–3 members) is silently discarded. For the example case of 2 members:
(2 - 0) / 4 = 0(integer division)0 * 20 = 0
This yields members_size = 0 despite num_members = 2.
Why This Matters
The pg_get_multixact_stats() function was specifically designed (commits 0e3ad4b96aed and 97b101776ce2) to give operators visibility into multixact storage growth—a known operational pain point for systems with heavy SELECT ... FOR SHARE or FOR KEY SHARE workloads. If the size estimate is incorrect for small member counts, it undermines the utility of the function for early detection of multixact wraparound risk. While the truncation error becomes proportionally negligible at large scales, it is confusing and technically incorrect at any scale.
Proposed Fix
The fix is mathematically elegant and preserves the intent of the original code:
Instead of computing (offset_diff / members_per_group) * group_size, compute offset_diff * (group_size / members_per_group).
Since MULTIXACT_MEMBERGROUP_SIZE / MULTIXACT_MEMBERS_PER_MEMBERGROUP = 20 / 4 = 5 (divides evenly, no precision loss), this simplifies to multiplying the member count by 5 bytes per member. This matches the documentation's example where 2785241176 * 5 = 13926205880.
The corrected calculation:
return (uint64) (new_offset - old_offset) *
(MULTIXACT_MEMBERGROUP_SIZE / MULTIXACT_MEMBERS_PER_MEMBERGROUP);
Design Considerations
-
No alignment/padding accounting: The fix deliberately does not account for the 12 bytes wasted per page due to member group alignment. This is consistent with the documentation examples and the function's intent as an estimate of logical storage consumption rather than exact physical file sizes.
-
Overflow safety: The cast to
uint64before multiplication ensures the product does not overflow for large offset differences (MultiXactOffset is a 32-bit type, so the maximum value of ~4 billion × 5 fits comfortably in 64 bits). -
Test coverage: The patch updates the existing isolation test to verify
members_sizecorrectness, ensuring this regression is caught if the calculation is inadvertently changed again.
Timing and Release Implications
This bug exists in the code paths introduced for PostgreSQL 19 (the commits referenced are recent). The author explicitly notes the desire to fix this before PostgreSQL 19 is released, suggesting this is a release-candidate or late-beta timeframe fix. Given the simplicity of the fix and the clear incorrectness of the current behavior, this should be a non-controversial backfix.
Assessment
This is a straightforward arithmetic bug with a clean fix. The original code appears to have been written thinking in terms of "how many complete groups fit, multiplied by group size" without considering the remainder. The documentation examples already imply the per-member calculation (5 bytes each), so the fix aligns implementation with documented behavior.