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: v1-0001-Fix-pg_get_multixact_stats-members_size-calculati.patch
Description: Binary data

Reply via email to