1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
/*
* multixact_rewrite.c
*
* Functions to convert multixact SLRUs from the pre-v19 format to the current
* format with 64-bit MultiXactOffsets.
*
* Copyright (c) 2025, PostgreSQL Global Development Group
* src/bin/pg_upgrade/multixact_rewrite.c
*/
#include "postgres_fe.h"
#include "access/multixact_internal.h"
#include "multixact_read_v18.h"
#include "pg_upgrade.h"
static void RecordMultiXactOffset(SlruSegState *offsets_writer, MultiXactId multi,
MultiXactOffset offset);
static void RecordMultiXactMembers(SlruSegState *members_writer,
MultiXactOffset offset,
int nmembers, MultiXactMember *members);
/*
* Convert pg_multixact/offset and /members from the old pre-v19 format with
* 32-bit offsets to the current format.
*
* Multixids in the range [from_multi, to_multi) are read from the old
* cluster, and written in the new format. An important edge case is that if
* from_multi == to_multi, this initializes the new pg_multixact files in the
* new format without trying to open any old files. (We rely on that when
* upgrading from PostgreSQL version 9.2 or below.)
*
* Returns the new nextOffset value; the caller should set it in the new
* control file. The new members always start from offset 1, regardless of
* the offset range used in the old cluster.
*/
MultiXactOffset
rewrite_multixacts(MultiXactId from_multi, MultiXactId to_multi)
{
MultiXactOffset next_offset;
SlruSegState *offsets_writer;
SlruSegState *members_writer;
char dir[MAXPGPATH] = {0};
bool prev_multixid_valid = false;
/*
* The range of valid multi XIDs is unchanged by the conversion (they are
* referenced from the heap tables), but the members SLRU is rewritten to
* start from offset 1.
*/
next_offset = 1;
/* Prepare to write the new SLRU files */
pg_sprintf(dir, "%s/pg_multixact/offsets", new_cluster.pgdata);
offsets_writer = AllocSlruWrite(dir, false);
SlruWriteSwitchPage(offsets_writer, MultiXactIdToOffsetPage(from_multi));
pg_sprintf(dir, "%s/pg_multixact/members", new_cluster.pgdata);
members_writer = AllocSlruWrite(dir, true /* use long segment names */ );
SlruWriteSwitchPage(members_writer, MXOffsetToMemberPage(next_offset));
/*
* Convert old multixids, if needed, by reading them one-by-one from the
* old cluster.
*/
if (to_multi != from_multi)
{
OldMultiXactReader *old_reader;
old_reader = AllocOldMultiXactRead(old_cluster.pgdata,
old_cluster.controldata.chkpnt_nxtmulti,
old_cluster.controldata.chkpnt_nxtmxoff);
for (MultiXactId multi = from_multi; multi != to_multi;)
{
MultiXactMember member;
bool multixid_valid;
/*
* Read this multixid's members.
*
* Locking-only XIDs that may be part of multi-xids don't matter
* after upgrade, as there can be no transactions running across
* upgrade. So as a small optimization, we only read one member
* from each multixid: the one updating one, or if there was no
* update, arbitrarily the first locking xid.
*/
multixid_valid = GetOldMultiXactIdSingleMember(old_reader, multi, &member);
/*
* Write the new offset to pg_multixact/offsets.
*
* Even if this multixid is invalid, we still need to write its
* offset if the *previous* multixid was valid. That's because
* when reading a multixid, the number of members is calculated
* from the difference between the two offsets.
*/
RecordMultiXactOffset(offsets_writer, multi,
(multixid_valid || prev_multixid_valid) ? next_offset : 0);
/* Write the members */
if (multixid_valid)
{
RecordMultiXactMembers(members_writer, next_offset, 1, &member);
next_offset += 1;
}
/* Advance to next multixid, handling wraparound */
multi++;
if (multi < FirstMultiXactId)
multi = FirstMultiXactId;
prev_multixid_valid = multixid_valid;
}
FreeOldMultiXactReader(old_reader);
}
/* Write the final 'next' offset to the last SLRU page */
RecordMultiXactOffset(offsets_writer, to_multi,
prev_multixid_valid ? next_offset : 0);
/* Flush the last SLRU pages */
FreeSlruWrite(offsets_writer);
FreeSlruWrite(members_writer);
return next_offset;
}
/*
* Write one offset to the offset SLRU
*/
static void
RecordMultiXactOffset(SlruSegState *offsets_writer, MultiXactId multi,
MultiXactOffset offset)
{
int64 pageno;
int entryno;
char *buf;
MultiXactOffset *offptr;
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
buf = SlruWriteSwitchPage(offsets_writer, pageno);
offptr = (MultiXactOffset *) buf;
offptr[entryno] = offset;
}
/*
* Write the members for one multixid in the members SLRU
*
* (Currently, this is only ever called with nmembers == 1)
*/
static void
RecordMultiXactMembers(SlruSegState *members_writer,
MultiXactOffset offset,
int nmembers, MultiXactMember *members)
{
for (int i = 0; i < nmembers; i++, offset++)
{
int64 pageno;
char *buf;
TransactionId *memberptr;
uint32 *flagsptr;
uint32 flagsval;
int bshift;
int flagsoff;
int memberoff;
Assert(members[i].status <= MultiXactStatusUpdate);
pageno = MXOffsetToMemberPage(offset);
memberoff = MXOffsetToMemberOffset(offset);
flagsoff = MXOffsetToFlagsOffset(offset);
bshift = MXOffsetToFlagsBitShift(offset);
buf = SlruWriteSwitchPage(members_writer, pageno);
memberptr = (TransactionId *) (buf + memberoff);
*memberptr = members[i].xid;
flagsptr = (uint32 *) (buf + flagsoff);
flagsval = *flagsptr;
flagsval &= ~(((1 << MXACT_MEMBER_BITS_PER_XACT) - 1) << bshift);
flagsval |= (members[i].status << bshift);
*flagsptr = flagsval;
}
}
|