Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * combocid.c
4 : : * Combo command ID support routines
5 : : *
6 : : * Before version 8.3, HeapTupleHeaderData had separate fields for cmin
7 : : * and cmax. To reduce the header size, cmin and cmax are now overlaid
8 : : * in the same field in the header. That usually works because you rarely
9 : : * insert and delete a tuple in the same transaction, and we don't need
10 : : * either field to remain valid after the originating transaction exits.
11 : : * To make it work when the inserting transaction does delete the tuple,
12 : : * we create a "combo" command ID and store that in the tuple header
13 : : * instead of cmin and cmax. The combo command ID can be mapped to the
14 : : * real cmin and cmax using a backend-private array, which is managed by
15 : : * this module.
16 : : *
17 : : * To allow reusing existing combo CIDs, we also keep a hash table that
18 : : * maps cmin,cmax pairs to combo CIDs. This keeps the data structure size
19 : : * reasonable in most cases, since the number of unique pairs used by any
20 : : * one transaction is likely to be small.
21 : : *
22 : : * With a 32-bit combo command id we can represent 2^32 distinct cmin,cmax
23 : : * combinations. In the most perverse case where each command deletes a tuple
24 : : * generated by every previous command, the number of combo command ids
25 : : * required for N commands is N*(N+1)/2. That means that in the worst case,
26 : : * that's enough for 92682 commands. In practice, you'll run out of memory
27 : : * and/or disk space way before you reach that limit.
28 : : *
29 : : * The array and hash table are kept in TopTransactionContext, and are
30 : : * destroyed at the end of each transaction.
31 : : *
32 : : *
33 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
34 : : * Portions Copyright (c) 1994, Regents of the University of California
35 : : *
36 : : * IDENTIFICATION
37 : : * src/backend/utils/time/combocid.c
38 : : *
39 : : *-------------------------------------------------------------------------
40 : : */
41 : :
42 : : #include "postgres.h"
43 : :
44 : : #include "access/htup_details.h"
45 : : #include "access/xact.h"
46 : : #include "miscadmin.h"
47 : : #include "storage/shmem.h"
48 : : #include "utils/combocid.h"
49 : : #include "utils/hsearch.h"
50 : : #include "utils/memutils.h"
51 : :
52 : : /* Hash table to lookup combo CIDs by cmin and cmax */
53 : : static HTAB *comboHash = NULL;
54 : :
55 : : /* Key and entry structures for the hash table */
56 : : typedef struct
57 : : {
58 : : CommandId cmin;
59 : : CommandId cmax;
60 : : } ComboCidKeyData;
61 : :
62 : : typedef ComboCidKeyData *ComboCidKey;
63 : :
64 : : typedef struct
65 : : {
66 : : ComboCidKeyData key;
67 : : CommandId combocid;
68 : : } ComboCidEntryData;
69 : :
70 : : typedef ComboCidEntryData *ComboCidEntry;
71 : :
72 : : /* Initial size of the hash table */
73 : : #define CCID_HASH_SIZE 100
74 : :
75 : :
76 : : /*
77 : : * An array of cmin,cmax pairs, indexed by combo command id.
78 : : * To convert a combo CID to cmin and cmax, you do a simple array lookup.
79 : : */
80 : : static ComboCidKey comboCids = NULL;
81 : : static int usedComboCids = 0; /* number of elements in comboCids */
82 : : static int sizeComboCids = 0; /* allocated size of array */
83 : :
84 : : /* Initial size of the array */
85 : : #define CCID_ARRAY_SIZE 100
86 : :
87 : :
88 : : /* prototypes for internal functions */
89 : : static CommandId GetComboCommandId(CommandId cmin, CommandId cmax);
90 : : static CommandId GetRealCmin(CommandId combocid);
91 : : static CommandId GetRealCmax(CommandId combocid);
92 : :
93 : :
94 : : /**** External API ****/
95 : :
96 : : /*
97 : : * GetCmin and GetCmax assert that they are only called in situations where
98 : : * they make sense, that is, can deliver a useful answer. If you have
99 : : * reason to examine a tuple's t_cid field from a transaction other than
100 : : * the originating one, use HeapTupleHeaderGetRawCommandId() directly.
101 : : */
102 : :
103 : : CommandId
6274 tgl@sss.pgh.pa.us 104 :CBC 11161294 : HeapTupleHeaderGetCmin(HeapTupleHeader tup)
105 : : {
5995 bruce@momjian.us 106 : 11161294 : CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
107 : :
6274 tgl@sss.pgh.pa.us 108 [ - + ]: 11161294 : Assert(!(tup->t_infomask & HEAP_MOVED));
109 [ + - - + ]: 11161294 : Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
110 : :
111 [ + + ]: 11161294 : if (tup->t_infomask & HEAP_COMBOCID)
112 : 2870387 : return GetRealCmin(cid);
113 : : else
114 : 8290907 : return cid;
115 : : }
116 : :
117 : : CommandId
118 : 2969768 : HeapTupleHeaderGetCmax(HeapTupleHeader tup)
119 : : {
5995 bruce@momjian.us 120 : 2969768 : CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
121 : :
4099 alvherre@alvh.no-ip. 122 [ - + ]: 2969768 : Assert(!(tup->t_infomask & HEAP_MOVED));
123 : :
124 : : /*
125 : : * Because GetUpdateXid() performs memory allocations if xmax is a
126 : : * multixact we can't Assert() if we're inside a critical section. This
127 : : * weakens the check, but not using GetCmax() inside one would complicate
128 : : * things too much.
129 : : */
3612 andres@anarazel.de 130 [ + + + - : 2969768 : Assert(CritSectionCount > 0 ||
+ + + - -
+ ]
131 : : TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
132 : :
6274 tgl@sss.pgh.pa.us 133 [ + + ]: 2969768 : if (tup->t_infomask & HEAP_COMBOCID)
134 : 2870315 : return GetRealCmax(cid);
135 : : else
136 : 99453 : return cid;
137 : : }
138 : :
139 : : /*
140 : : * Given a tuple we are about to delete, determine the correct value to store
141 : : * into its t_cid field.
142 : : *
143 : : * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to
144 : : * false. If we do need one, *cmax is replaced by a combo CID and *iscombo
145 : : * is set to true.
146 : : *
147 : : * The reason this is separate from the actual HeapTupleHeaderSetCmax()
148 : : * operation is that this could fail due to out-of-memory conditions. Hence
149 : : * we need to do this before entering the critical section that actually
150 : : * changes the tuple in shared buffers.
151 : : */
152 : : void
153 : 1751928 : HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
154 : : CommandId *cmax,
155 : : bool *iscombo)
156 : : {
157 : : /*
158 : : * If we're marking a tuple deleted that was inserted by (any
159 : : * subtransaction of) our transaction, we need to use a combo command id.
160 : : * Test for HeapTupleHeaderXminCommitted() first, because it's cheaper
161 : : * than a TransactionIdIsCurrentTransactionId call.
162 : : */
3766 rhaas@postgresql.org 163 [ + + + + ]: 1950037 : if (!HeapTupleHeaderXminCommitted(tup) &&
164 : 198109 : TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup)))
6274 tgl@sss.pgh.pa.us 165 : 197216 : {
5704 heikki.linnakangas@i 166 : 197216 : CommandId cmin = HeapTupleHeaderGetCmin(tup);
167 : :
6274 tgl@sss.pgh.pa.us 168 : 197216 : *cmax = GetComboCommandId(cmin, *cmax);
169 : 197216 : *iscombo = true;
170 : : }
171 : : else
172 : : {
173 : 1554712 : *iscombo = false;
174 : : }
175 : 1751928 : }
176 : :
177 : : /*
178 : : * Combo command ids are only interesting to the inserting and deleting
179 : : * transaction, so we can forget about them at the end of transaction.
180 : : */
181 : : void
182 : 433759 : AtEOXact_ComboCid(void)
183 : : {
184 : : /*
185 : : * Don't bother to pfree. These are allocated in TopTransactionContext, so
186 : : * they're going to go away at the end of transaction anyway.
187 : : */
188 : 433759 : comboHash = NULL;
189 : :
190 : 433759 : comboCids = NULL;
191 : 433759 : usedComboCids = 0;
192 : 433759 : sizeComboCids = 0;
193 : 433759 : }
194 : :
195 : :
196 : : /**** Internal routines ****/
197 : :
198 : : /*
199 : : * Get a combo command id that maps to cmin and cmax.
200 : : *
201 : : * We try to reuse old combo command ids when possible.
202 : : */
203 : : static CommandId
204 : 224787 : GetComboCommandId(CommandId cmin, CommandId cmax)
205 : : {
206 : : CommandId combocid;
207 : : ComboCidKeyData key;
208 : : ComboCidEntry entry;
209 : : bool found;
210 : :
211 : : /*
212 : : * Create the hash table and array the first time we need to use combo
213 : : * cids in the transaction.
214 : : */
215 [ + + ]: 224787 : if (comboHash == NULL)
216 : : {
217 : : HASHCTL hash_ctl;
218 : :
219 : : /* Make array first; existence of hash table asserts array exists */
3062 220 : 19370 : comboCids = (ComboCidKeyData *)
221 : 19370 : MemoryContextAlloc(TopTransactionContext,
222 : : sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE);
223 : 19370 : sizeComboCids = CCID_ARRAY_SIZE;
224 : 19370 : usedComboCids = 0;
225 : :
6274 226 : 19370 : hash_ctl.keysize = sizeof(ComboCidKeyData);
227 : 19370 : hash_ctl.entrysize = sizeof(ComboCidEntryData);
228 : 19370 : hash_ctl.hcxt = TopTransactionContext;
229 : :
230 : 19370 : comboHash = hash_create("Combo CIDs",
231 : : CCID_HASH_SIZE,
232 : : &hash_ctl,
233 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
234 : : }
235 : :
236 : : /*
237 : : * Grow the array if there's not at least one free slot. We must do this
238 : : * before possibly entering a new hashtable entry, else failure to
239 : : * repalloc would leave a corrupt hashtable entry behind.
240 : : */
3062 241 [ + + ]: 224787 : if (usedComboCids >= sizeComboCids)
242 : : {
243 : 76 : int newsize = sizeComboCids * 2;
244 : :
6274 245 : 76 : comboCids = (ComboCidKeyData *)
3062 246 : 76 : repalloc(comboCids, sizeof(ComboCidKeyData) * newsize);
247 : 76 : sizeComboCids = newsize;
248 : : }
249 : :
250 : : /* Lookup or create a hash entry with the desired cmin/cmax */
251 : :
252 : : /* We assume there is no struct padding in ComboCidKeyData! */
6274 253 : 224787 : key.cmin = cmin;
254 : 224787 : key.cmax = cmax;
255 : 224787 : entry = (ComboCidEntry) hash_search(comboHash,
256 : : &key,
257 : : HASH_ENTER,
258 : : &found);
259 : :
260 [ + + ]: 224787 : if (found)
261 : : {
262 : : /* Reuse an existing combo CID */
263 : 118347 : return entry->combocid;
264 : : }
265 : :
266 : : /* We have to create a new combo CID; we already made room in the array */
267 : 106440 : combocid = usedComboCids;
268 : :
269 : 106440 : comboCids[combocid].cmin = cmin;
270 : 106440 : comboCids[combocid].cmax = cmax;
271 : 106440 : usedComboCids++;
272 : :
273 : 106440 : entry->combocid = combocid;
274 : :
275 : 106440 : return combocid;
276 : : }
277 : :
278 : : static CommandId
279 : 2870387 : GetRealCmin(CommandId combocid)
280 : : {
281 [ - + ]: 2870387 : Assert(combocid < usedComboCids);
282 : 2870387 : return comboCids[combocid].cmin;
283 : : }
284 : :
285 : : static CommandId
286 : 2870315 : GetRealCmax(CommandId combocid)
287 : : {
288 [ - + ]: 2870315 : Assert(combocid < usedComboCids);
289 : 2870315 : return comboCids[combocid].cmax;
290 : : }
291 : :
292 : : /*
293 : : * Estimate the amount of space required to serialize the current combo CID
294 : : * state.
295 : : */
296 : : Size
3272 rhaas@postgresql.org 297 : 414 : EstimateComboCIDStateSpace(void)
298 : : {
299 : : Size size;
300 : :
301 : : /* Add space required for saving usedComboCids */
302 : 414 : size = sizeof(int);
303 : :
304 : : /* Add space required for saving ComboCidKeyData */
305 : 414 : size = add_size(size, mul_size(sizeof(ComboCidKeyData), usedComboCids));
306 : :
307 : 414 : return size;
308 : : }
309 : :
310 : : /*
311 : : * Serialize the combo CID state into the memory, beginning at start_address.
312 : : * maxsize should be at least as large as the value returned by
313 : : * EstimateComboCIDStateSpace.
314 : : */
315 : : void
316 : 414 : SerializeComboCIDState(Size maxsize, char *start_address)
317 : : {
318 : : char *endptr;
319 : :
320 : : /* First, we store the number of currently-existing combo CIDs. */
3249 bruce@momjian.us 321 : 414 : *(int *) start_address = usedComboCids;
322 : :
323 : : /* If maxsize is too small, throw an error. */
3272 rhaas@postgresql.org 324 : 414 : endptr = start_address + sizeof(int) +
325 : 414 : (sizeof(ComboCidKeyData) * usedComboCids);
326 [ + - - + ]: 414 : if (endptr < start_address || endptr > start_address + maxsize)
3272 rhaas@postgresql.org 327 [ # # ]:UBC 0 : elog(ERROR, "not enough space to serialize ComboCID state");
328 : :
329 : : /* Now, copy the actual cmin/cmax pairs. */
3062 tgl@sss.pgh.pa.us 330 [ + + ]:CBC 414 : if (usedComboCids > 0)
331 : 238 : memcpy(start_address + sizeof(int), comboCids,
332 : : (sizeof(ComboCidKeyData) * usedComboCids));
3272 rhaas@postgresql.org 333 : 414 : }
334 : :
335 : : /*
336 : : * Read the combo CID state at the specified address and initialize this
337 : : * backend with the same combo CIDs. This is only valid in a backend that
338 : : * currently has no combo CIDs (and only makes sense if the transaction state
339 : : * is serialized and restored as well).
340 : : */
341 : : void
342 : 1322 : RestoreComboCIDState(char *comboCIDstate)
343 : : {
344 : : int num_elements;
345 : : ComboCidKeyData *keydata;
346 : : int i;
347 : : CommandId cid;
348 : :
349 [ + - - + ]: 1322 : Assert(!comboCids && !comboHash);
350 : :
351 : : /* First, we retrieve the number of combo CIDs that were serialized. */
3249 bruce@momjian.us 352 : 1322 : num_elements = *(int *) comboCIDstate;
3272 rhaas@postgresql.org 353 : 1322 : keydata = (ComboCidKeyData *) (comboCIDstate + sizeof(int));
354 : :
355 : : /* Use GetComboCommandId to restore each combo CID. */
356 [ + + ]: 28893 : for (i = 0; i < num_elements; i++)
357 : : {
358 : 27571 : cid = GetComboCommandId(keydata[i].cmin, keydata[i].cmax);
359 : :
360 : : /* Verify that we got the expected answer. */
361 [ - + ]: 27571 : if (cid != i)
3272 rhaas@postgresql.org 362 [ # # ]:UBC 0 : elog(ERROR, "unexpected command ID while restoring combo CIDs");
363 : : }
3272 rhaas@postgresql.org 364 :CBC 1322 : }
|