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 overlayed |
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-2019, 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 "miscadmin.h" |
45 | #include "access/htup_details.h" |
46 | #include "access/xact.h" |
47 | #include "storage/shmem.h" |
48 | #include "utils/combocid.h" |
49 | #include "utils/hsearch.h" |
50 | #include "utils/memutils.h" |
51 | |
52 | |
53 | /* Hash table to lookup combo cids by cmin and cmax */ |
54 | static HTAB *comboHash = NULL; |
55 | |
56 | /* Key and entry structures for the hash table */ |
57 | typedef struct |
58 | { |
59 | CommandId cmin; |
60 | CommandId cmax; |
61 | } ComboCidKeyData; |
62 | |
63 | typedef ComboCidKeyData *ComboCidKey; |
64 | |
65 | typedef struct |
66 | { |
67 | ComboCidKeyData key; |
68 | CommandId combocid; |
69 | } ComboCidEntryData; |
70 | |
71 | typedef ComboCidEntryData *ComboCidEntry; |
72 | |
73 | /* Initial size of the hash table */ |
74 | #define CCID_HASH_SIZE 100 |
75 | |
76 | |
77 | /* |
78 | * An array of cmin,cmax pairs, indexed by combo command id. |
79 | * To convert a combo cid to cmin and cmax, you do a simple array lookup. |
80 | */ |
81 | static ComboCidKey comboCids = NULL; |
82 | static int usedComboCids = 0; /* number of elements in comboCids */ |
83 | static int sizeComboCids = 0; /* allocated size of array */ |
84 | |
85 | /* Initial size of the array */ |
86 | #define CCID_ARRAY_SIZE 100 |
87 | |
88 | |
89 | /* prototypes for internal functions */ |
90 | static CommandId GetComboCommandId(CommandId cmin, CommandId cmax); |
91 | static CommandId GetRealCmin(CommandId combocid); |
92 | static CommandId GetRealCmax(CommandId combocid); |
93 | |
94 | |
95 | /**** External API ****/ |
96 | |
97 | /* |
98 | * GetCmin and GetCmax assert that they are only called in situations where |
99 | * they make sense, that is, can deliver a useful answer. If you have |
100 | * reason to examine a tuple's t_cid field from a transaction other than |
101 | * the originating one, use HeapTupleHeaderGetRawCommandId() directly. |
102 | */ |
103 | |
104 | CommandId |
105 | (HeapTupleHeader tup) |
106 | { |
107 | CommandId cid = HeapTupleHeaderGetRawCommandId(tup); |
108 | |
109 | Assert(!(tup->t_infomask & HEAP_MOVED)); |
110 | Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup))); |
111 | |
112 | if (tup->t_infomask & HEAP_COMBOCID) |
113 | return GetRealCmin(cid); |
114 | else |
115 | return cid; |
116 | } |
117 | |
118 | CommandId |
119 | (HeapTupleHeader tup) |
120 | { |
121 | CommandId cid = HeapTupleHeaderGetRawCommandId(tup); |
122 | |
123 | Assert(!(tup->t_infomask & HEAP_MOVED)); |
124 | |
125 | /* |
126 | * Because GetUpdateXid() performs memory allocations if xmax is a |
127 | * multixact we can't Assert() if we're inside a critical section. This |
128 | * weakens the check, but not using GetCmax() inside one would complicate |
129 | * things too much. |
130 | */ |
131 | Assert(CritSectionCount > 0 || |
132 | TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup))); |
133 | |
134 | if (tup->t_infomask & HEAP_COMBOCID) |
135 | return GetRealCmax(cid); |
136 | else |
137 | return cid; |
138 | } |
139 | |
140 | /* |
141 | * Given a tuple we are about to delete, determine the correct value to store |
142 | * into its t_cid field. |
143 | * |
144 | * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to |
145 | * false. If we do need one, *cmax is replaced by a combo CID and *iscombo |
146 | * is set to true. |
147 | * |
148 | * The reason this is separate from the actual HeapTupleHeaderSetCmax() |
149 | * operation is that this could fail due to out-of-memory conditions. Hence |
150 | * we need to do this before entering the critical section that actually |
151 | * changes the tuple in shared buffers. |
152 | */ |
153 | void |
154 | (HeapTupleHeader tup, |
155 | CommandId *cmax, |
156 | bool *iscombo) |
157 | { |
158 | /* |
159 | * If we're marking a tuple deleted that was inserted by (any |
160 | * subtransaction of) our transaction, we need to use a combo command id. |
161 | * Test for HeapTupleHeaderXminCommitted() first, because it's cheaper |
162 | * than a TransactionIdIsCurrentTransactionId call. |
163 | */ |
164 | if (!HeapTupleHeaderXminCommitted(tup) && |
165 | TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup))) |
166 | { |
167 | CommandId cmin = HeapTupleHeaderGetCmin(tup); |
168 | |
169 | *cmax = GetComboCommandId(cmin, *cmax); |
170 | *iscombo = true; |
171 | } |
172 | else |
173 | { |
174 | *iscombo = false; |
175 | } |
176 | } |
177 | |
178 | /* |
179 | * Combo command ids are only interesting to the inserting and deleting |
180 | * transaction, so we can forget about them at the end of transaction. |
181 | */ |
182 | void |
183 | AtEOXact_ComboCid(void) |
184 | { |
185 | /* |
186 | * Don't bother to pfree. These are allocated in TopTransactionContext, so |
187 | * they're going to go away at the end of transaction anyway. |
188 | */ |
189 | comboHash = NULL; |
190 | |
191 | comboCids = NULL; |
192 | usedComboCids = 0; |
193 | sizeComboCids = 0; |
194 | } |
195 | |
196 | |
197 | /**** Internal routines ****/ |
198 | |
199 | /* |
200 | * Get a combo command id that maps to cmin and cmax. |
201 | * |
202 | * We try to reuse old combo command ids when possible. |
203 | */ |
204 | static CommandId |
205 | GetComboCommandId(CommandId cmin, CommandId cmax) |
206 | { |
207 | CommandId combocid; |
208 | ComboCidKeyData key; |
209 | ComboCidEntry entry; |
210 | bool found; |
211 | |
212 | /* |
213 | * Create the hash table and array the first time we need to use combo |
214 | * cids in the transaction. |
215 | */ |
216 | if (comboHash == NULL) |
217 | { |
218 | HASHCTL hash_ctl; |
219 | |
220 | /* Make array first; existence of hash table asserts array exists */ |
221 | comboCids = (ComboCidKeyData *) |
222 | MemoryContextAlloc(TopTransactionContext, |
223 | sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE); |
224 | sizeComboCids = CCID_ARRAY_SIZE; |
225 | usedComboCids = 0; |
226 | |
227 | memset(&hash_ctl, 0, sizeof(hash_ctl)); |
228 | hash_ctl.keysize = sizeof(ComboCidKeyData); |
229 | hash_ctl.entrysize = sizeof(ComboCidEntryData); |
230 | hash_ctl.hcxt = TopTransactionContext; |
231 | |
232 | comboHash = hash_create("Combo CIDs" , |
233 | CCID_HASH_SIZE, |
234 | &hash_ctl, |
235 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); |
236 | } |
237 | |
238 | /* |
239 | * Grow the array if there's not at least one free slot. We must do this |
240 | * before possibly entering a new hashtable entry, else failure to |
241 | * repalloc would leave a corrupt hashtable entry behind. |
242 | */ |
243 | if (usedComboCids >= sizeComboCids) |
244 | { |
245 | int newsize = sizeComboCids * 2; |
246 | |
247 | comboCids = (ComboCidKeyData *) |
248 | repalloc(comboCids, sizeof(ComboCidKeyData) * newsize); |
249 | sizeComboCids = newsize; |
250 | } |
251 | |
252 | /* Lookup or create a hash entry with the desired cmin/cmax */ |
253 | |
254 | /* We assume there is no struct padding in ComboCidKeyData! */ |
255 | key.cmin = cmin; |
256 | key.cmax = cmax; |
257 | entry = (ComboCidEntry) hash_search(comboHash, |
258 | (void *) &key, |
259 | HASH_ENTER, |
260 | &found); |
261 | |
262 | if (found) |
263 | { |
264 | /* Reuse an existing combo cid */ |
265 | return entry->combocid; |
266 | } |
267 | |
268 | /* We have to create a new combo cid; we already made room in the array */ |
269 | combocid = usedComboCids; |
270 | |
271 | comboCids[combocid].cmin = cmin; |
272 | comboCids[combocid].cmax = cmax; |
273 | usedComboCids++; |
274 | |
275 | entry->combocid = combocid; |
276 | |
277 | return combocid; |
278 | } |
279 | |
280 | static CommandId |
281 | GetRealCmin(CommandId combocid) |
282 | { |
283 | Assert(combocid < usedComboCids); |
284 | return comboCids[combocid].cmin; |
285 | } |
286 | |
287 | static CommandId |
288 | GetRealCmax(CommandId combocid) |
289 | { |
290 | Assert(combocid < usedComboCids); |
291 | return comboCids[combocid].cmax; |
292 | } |
293 | |
294 | /* |
295 | * Estimate the amount of space required to serialize the current ComboCID |
296 | * state. |
297 | */ |
298 | Size |
299 | EstimateComboCIDStateSpace(void) |
300 | { |
301 | Size size; |
302 | |
303 | /* Add space required for saving usedComboCids */ |
304 | size = sizeof(int); |
305 | |
306 | /* Add space required for saving the combocids key */ |
307 | size = add_size(size, mul_size(sizeof(ComboCidKeyData), usedComboCids)); |
308 | |
309 | return size; |
310 | } |
311 | |
312 | /* |
313 | * Serialize the ComboCID state into the memory, beginning at start_address. |
314 | * maxsize should be at least as large as the value returned by |
315 | * EstimateComboCIDStateSpace. |
316 | */ |
317 | void |
318 | SerializeComboCIDState(Size maxsize, char *start_address) |
319 | { |
320 | char *endptr; |
321 | |
322 | /* First, we store the number of currently-existing ComboCIDs. */ |
323 | *(int *) start_address = usedComboCids; |
324 | |
325 | /* If maxsize is too small, throw an error. */ |
326 | endptr = start_address + sizeof(int) + |
327 | (sizeof(ComboCidKeyData) * usedComboCids); |
328 | if (endptr < start_address || endptr > start_address + maxsize) |
329 | elog(ERROR, "not enough space to serialize ComboCID state" ); |
330 | |
331 | /* Now, copy the actual cmin/cmax pairs. */ |
332 | if (usedComboCids > 0) |
333 | memcpy(start_address + sizeof(int), comboCids, |
334 | (sizeof(ComboCidKeyData) * usedComboCids)); |
335 | } |
336 | |
337 | /* |
338 | * Read the ComboCID state at the specified address and initialize this |
339 | * backend with the same ComboCIDs. This is only valid in a backend that |
340 | * currently has no ComboCIDs (and only makes sense if the transaction state |
341 | * is serialized and restored as well). |
342 | */ |
343 | void |
344 | RestoreComboCIDState(char *comboCIDstate) |
345 | { |
346 | int num_elements; |
347 | ComboCidKeyData *keydata; |
348 | int i; |
349 | CommandId cid; |
350 | |
351 | Assert(!comboCids && !comboHash); |
352 | |
353 | /* First, we retrieve the number of ComboCIDs that were serialized. */ |
354 | num_elements = *(int *) comboCIDstate; |
355 | keydata = (ComboCidKeyData *) (comboCIDstate + sizeof(int)); |
356 | |
357 | /* Use GetComboCommandId to restore each ComboCID. */ |
358 | for (i = 0; i < num_elements; i++) |
359 | { |
360 | cid = GetComboCommandId(keydata[i].cmin, keydata[i].cmax); |
361 | |
362 | /* Verify that we got the expected answer. */ |
363 | if (cid != i) |
364 | elog(ERROR, "unexpected command ID while restoring combo CIDs" ); |
365 | } |
366 | } |
367 | |