| From: | Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com> |
|---|---|
| To: | PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org> |
| Cc: | Michael Paquier <michael(dot)paquier(at)gmail(dot)com>, nagnrik(at)gmail(dot)com |
| Subject: | Fix pg_get_multixact_stats() members_size calculation |
| Date: | 2026-05-22 07:02:48 |
| Message-ID: | 819AC1B2-1A71-4244-B081-3ADD85D1725D@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
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/
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0001-Fix-pg_get_multixact_stats-members_size-calculati.patch | application/octet-stream | 9.6 KB |
| From | Date | Subject | |
|---|---|---|---|
| Previous Message | Samil C | 2026-05-22 06:53:49 | Inquiry Regarding Parallel DML (Write-Side) Support in Future PostgreSQL Releases |