Line data Source code
1 : /*--------------------------------------------------------------------------
2 : *
3 : * test_tidstore.c
4 : * Test TidStore data structure.
5 : *
6 : * Note: all locking in this test module is useless since there is only
7 : * a single process to use the TidStore. It is meant to be an example of
8 : * usage.
9 : *
10 : * Copyright (c) 2024, PostgreSQL Global Development Group
11 : *
12 : * IDENTIFICATION
13 : * src/test/modules/test_tidstore/test_tidstore.c
14 : *
15 : * -------------------------------------------------------------------------
16 : */
17 : #include "postgres.h"
18 :
19 : #include "access/tidstore.h"
20 : #include "fmgr.h"
21 : #include "storage/block.h"
22 : #include "storage/itemptr.h"
23 : #include "storage/lwlock.h"
24 : #include "utils/array.h"
25 : #include "utils/memutils.h"
26 :
27 2 : PG_MODULE_MAGIC;
28 :
29 4 : PG_FUNCTION_INFO_V1(test_create);
30 4 : PG_FUNCTION_INFO_V1(do_set_block_offsets);
31 4 : PG_FUNCTION_INFO_V1(check_set_block_offsets);
32 4 : PG_FUNCTION_INFO_V1(test_is_full);
33 4 : PG_FUNCTION_INFO_V1(test_destroy);
34 :
35 : static TidStore *tidstore = NULL;
36 : static size_t tidstore_empty_size;
37 :
38 : /* array for verification of some tests */
39 : typedef struct ItemArray
40 : {
41 : ItemPointerData *insert_tids;
42 : ItemPointerData *lookup_tids;
43 : ItemPointerData *iter_tids;
44 : int max_tids;
45 : int num_tids;
46 : } ItemArray;
47 :
48 : static ItemArray items;
49 :
50 : /* comparator routine for ItemPointer */
51 : static int
52 416168 : itemptr_cmp(const void *left, const void *right)
53 : {
54 : BlockNumber lblk,
55 : rblk;
56 : OffsetNumber loff,
57 : roff;
58 :
59 416168 : lblk = ItemPointerGetBlockNumber((ItemPointer) left);
60 416168 : rblk = ItemPointerGetBlockNumber((ItemPointer) right);
61 :
62 416168 : if (lblk < rblk)
63 128168 : return -1;
64 288000 : if (lblk > rblk)
65 129020 : return 1;
66 :
67 158980 : loff = ItemPointerGetOffsetNumber((ItemPointer) left);
68 158980 : roff = ItemPointerGetOffsetNumber((ItemPointer) right);
69 :
70 158980 : if (loff < roff)
71 70256 : return -1;
72 88724 : if (loff > roff)
73 27684 : return 1;
74 :
75 61040 : return 0;
76 : }
77 :
78 : /*
79 : * Create a TidStore. If shared is false, the tidstore is created
80 : * on TopMemoryContext, otherwise on DSA. Although the tidstore
81 : * is created on DSA, only the same process can subsequently use
82 : * the tidstore. The tidstore handle is not shared anywhere.
83 : */
84 : Datum
85 6 : test_create(PG_FUNCTION_ARGS)
86 : {
87 6 : bool shared = PG_GETARG_BOOL(0);
88 : MemoryContext old_ctx;
89 :
90 : /* doesn't really matter, since it's just a hint */
91 6 : size_t tidstore_max_size = 2 * 1024 * 1024;
92 6 : size_t array_init_size = 1024;
93 :
94 : Assert(tidstore == NULL);
95 :
96 : /*
97 : * Create the TidStore on TopMemoryContext so that the same process use it
98 : * for subsequent tests.
99 : */
100 6 : old_ctx = MemoryContextSwitchTo(TopMemoryContext);
101 :
102 6 : if (shared)
103 : {
104 : int tranche_id;
105 :
106 2 : tranche_id = LWLockNewTrancheId();
107 2 : LWLockRegisterTranche(tranche_id, "test_tidstore");
108 :
109 2 : tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
110 :
111 : /*
112 : * Remain attached until end of backend or explicitly detached so that
113 : * the same process use the tidstore for subsequent tests.
114 : */
115 2 : dsa_pin_mapping(TidStoreGetDSA(tidstore));
116 : }
117 : else
118 : /* VACUUM uses insert only, so we test the other option. */
119 4 : tidstore = TidStoreCreateLocal(tidstore_max_size, false);
120 :
121 6 : tidstore_empty_size = TidStoreMemoryUsage(tidstore);
122 :
123 6 : items.num_tids = 0;
124 6 : items.max_tids = array_init_size / sizeof(ItemPointerData);
125 6 : items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
126 6 : items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
127 6 : items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
128 :
129 6 : MemoryContextSwitchTo(old_ctx);
130 :
131 6 : PG_RETURN_VOID();
132 : }
133 :
134 : static void
135 2242 : sanity_check_array(ArrayType *ta)
136 : {
137 2242 : if (ARR_HASNULL(ta) && array_contains_nulls(ta))
138 0 : ereport(ERROR,
139 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
140 : errmsg("array must not contain nulls")));
141 :
142 2242 : if (ARR_NDIM(ta) > 1)
143 0 : ereport(ERROR,
144 : (errcode(ERRCODE_DATA_EXCEPTION),
145 : errmsg("argument must be empty or one-dimensional array")));
146 2242 : }
147 :
148 : static void
149 2276 : check_tidstore_available(void)
150 : {
151 2276 : if (tidstore == NULL)
152 0 : elog(ERROR, "tidstore is not created");
153 2276 : }
154 :
155 : static void
156 2240 : purge_from_verification_array(BlockNumber blkno)
157 : {
158 2240 : int dst = 0;
159 :
160 6986506 : for (int src = 0; src < items.num_tids; src++)
161 6984266 : if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
162 6984218 : items.insert_tids[dst++] = items.insert_tids[src];
163 2240 : items.num_tids = dst;
164 2240 : }
165 :
166 :
167 : /* Set the given block and offsets pairs */
168 : Datum
169 2242 : do_set_block_offsets(PG_FUNCTION_ARGS)
170 : {
171 2242 : BlockNumber blkno = PG_GETARG_INT64(0);
172 2242 : ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
173 : OffsetNumber *offs;
174 : int noffs;
175 :
176 2242 : check_tidstore_available();
177 2242 : sanity_check_array(ta);
178 :
179 2242 : noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
180 2242 : offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
181 :
182 : /* Set TIDs in the store */
183 2242 : TidStoreLockExclusive(tidstore);
184 2242 : TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
185 2240 : TidStoreUnlock(tidstore);
186 :
187 : /* Remove the existing items of blkno from the verification array */
188 2240 : purge_from_verification_array(blkno);
189 :
190 : /* Set TIDs in verification array */
191 32760 : for (int i = 0; i < noffs; i++)
192 : {
193 : ItemPointer tid;
194 30520 : int idx = items.num_tids + i;
195 :
196 : /* Enlarge the TID arrays if necessary */
197 30520 : if (idx >= items.max_tids)
198 : {
199 24 : items.max_tids *= 2;
200 24 : items.insert_tids = repalloc(items.insert_tids, sizeof(ItemPointerData) * items.max_tids);
201 24 : items.lookup_tids = repalloc(items.lookup_tids, sizeof(ItemPointerData) * items.max_tids);
202 24 : items.iter_tids = repalloc(items.iter_tids, sizeof(ItemPointerData) * items.max_tids);
203 : }
204 :
205 30520 : tid = &(items.insert_tids[idx]);
206 30520 : ItemPointerSet(tid, blkno, offs[i]);
207 : }
208 :
209 : /* Update statistics */
210 2240 : items.num_tids += noffs;
211 :
212 2240 : PG_RETURN_INT64(blkno);
213 : }
214 :
215 : /*
216 : * Verify TIDs in store against the array.
217 : */
218 : Datum
219 24 : check_set_block_offsets(PG_FUNCTION_ARGS)
220 : {
221 : TidStoreIter *iter;
222 : TidStoreIterResult *iter_result;
223 24 : int num_iter_tids = 0;
224 24 : int num_lookup_tids = 0;
225 24 : BlockNumber prevblkno = 0;
226 :
227 24 : check_tidstore_available();
228 :
229 : /* lookup each member in the verification array */
230 30544 : for (int i = 0; i < items.num_tids; i++)
231 30520 : if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
232 0 : elog(ERROR, "missing TID with block %u, offset %u",
233 : ItemPointerGetBlockNumber(&items.insert_tids[i]),
234 : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
235 :
236 : /*
237 : * Lookup all possible TIDs for each distinct block in the verification
238 : * array and save successful lookups in the lookup array.
239 : */
240 :
241 30544 : for (int i = 0; i < items.num_tids; i++)
242 : {
243 30520 : BlockNumber blkno = ItemPointerGetBlockNumber(&items.insert_tids[i]);
244 :
245 30520 : if (i > 0 && blkno == prevblkno)
246 28280 : continue;
247 :
248 4587520 : for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
249 : {
250 : ItemPointerData tid;
251 :
252 4585280 : ItemPointerSet(&tid, blkno, offset);
253 :
254 4585280 : TidStoreLockShare(tidstore);
255 4585280 : if (TidStoreIsMember(tidstore, &tid))
256 30520 : ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
257 4585280 : TidStoreUnlock(tidstore);
258 : }
259 :
260 2240 : prevblkno = blkno;
261 : }
262 :
263 : /* Collect TIDs stored in the tidstore, in order */
264 :
265 24 : TidStoreLockShare(tidstore);
266 24 : iter = TidStoreBeginIterate(tidstore);
267 2264 : while ((iter_result = TidStoreIterateNext(iter)) != NULL)
268 : {
269 : OffsetNumber offsets[MaxOffsetNumber];
270 : int num_offsets;
271 :
272 2240 : num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
273 : Assert(num_offsets <= lengthof(offsets));
274 32760 : for (int i = 0; i < num_offsets; i++)
275 30520 : ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
276 30520 : offsets[i]);
277 : }
278 24 : TidStoreEndIterate(iter);
279 24 : TidStoreUnlock(tidstore);
280 :
281 : /*
282 : * Sort verification and lookup arrays and test that all arrays are the
283 : * same.
284 : */
285 :
286 24 : if (num_lookup_tids != items.num_tids)
287 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
288 24 : if (num_iter_tids != items.num_tids)
289 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
290 :
291 24 : qsort(items.insert_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
292 24 : qsort(items.lookup_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
293 30544 : for (int i = 0; i < items.num_tids; i++)
294 : {
295 30520 : if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.iter_tids[i]) != 0)
296 0 : elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
297 : ItemPointerGetBlockNumber(&items.iter_tids[i]),
298 : ItemPointerGetOffsetNumber(&items.iter_tids[i]),
299 : ItemPointerGetBlockNumber(&items.insert_tids[i]),
300 : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
301 30520 : if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.lookup_tids[i]) != 0)
302 0 : elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
303 : ItemPointerGetBlockNumber(&items.lookup_tids[i]),
304 : ItemPointerGetOffsetNumber(&items.lookup_tids[i]),
305 : ItemPointerGetBlockNumber(&items.insert_tids[i]),
306 : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
307 : }
308 :
309 24 : PG_RETURN_VOID();
310 : }
311 :
312 : /*
313 : * In real world use, we care if the memory usage is greater than
314 : * some configured limit. Here we just want to verify that
315 : * TidStoreMemoryUsage is not broken.
316 : */
317 : Datum
318 4 : test_is_full(PG_FUNCTION_ARGS)
319 : {
320 : bool is_full;
321 :
322 4 : check_tidstore_available();
323 :
324 4 : is_full = (TidStoreMemoryUsage(tidstore) > tidstore_empty_size);
325 :
326 4 : PG_RETURN_BOOL(is_full);
327 : }
328 :
329 : /* Free the tidstore */
330 : Datum
331 6 : test_destroy(PG_FUNCTION_ARGS)
332 : {
333 6 : check_tidstore_available();
334 :
335 6 : TidStoreDestroy(tidstore);
336 6 : tidstore = NULL;
337 6 : items.num_tids = 0;
338 6 : pfree(items.insert_tids);
339 6 : pfree(items.lookup_tids);
340 6 : pfree(items.iter_tids);
341 :
342 6 : PG_RETURN_VOID();
343 : }
|