/src/postgres/src/backend/access/table/tableam.c
Line | Count | Source |
1 | | /*---------------------------------------------------------------------- |
2 | | * |
3 | | * tableam.c |
4 | | * Table access method routines too big to be inline functions. |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/access/table/tableam.c |
12 | | * |
13 | | * NOTES |
14 | | * Note that most functions in here are documented in tableam.h, rather than |
15 | | * here. That's because there's a lot of inline functions in tableam.h and |
16 | | * it'd be harder to understand if one constantly had to switch between files. |
17 | | * |
18 | | *---------------------------------------------------------------------- |
19 | | */ |
20 | | #include "postgres.h" |
21 | | |
22 | | #include <math.h> |
23 | | |
24 | | #include "access/syncscan.h" |
25 | | #include "access/tableam.h" |
26 | | #include "access/xact.h" |
27 | | #include "optimizer/optimizer.h" |
28 | | #include "optimizer/plancat.h" |
29 | | #include "port/pg_bitutils.h" |
30 | | #include "storage/bufmgr.h" |
31 | | #include "storage/shmem.h" |
32 | | #include "storage/smgr.h" |
33 | | |
34 | | /* |
35 | | * Constants to control the behavior of block allocation to parallel workers |
36 | | * during a parallel seqscan. Technically these values do not need to be |
37 | | * powers of 2, but having them as powers of 2 makes the math more optimal |
38 | | * and makes the ramp-down stepping more even. |
39 | | */ |
40 | | |
41 | | /* The number of I/O chunks we try to break a parallel seqscan down into */ |
42 | | #define PARALLEL_SEQSCAN_NCHUNKS 2048 |
43 | | /* Ramp down size of allocations when we've only this number of chunks left */ |
44 | 0 | #define PARALLEL_SEQSCAN_RAMPDOWN_CHUNKS 64 |
45 | | /* Cap the size of parallel I/O chunks to this number of blocks */ |
46 | | #define PARALLEL_SEQSCAN_MAX_CHUNK_SIZE 8192 |
47 | | |
48 | | /* GUC variables */ |
49 | | char *default_table_access_method = DEFAULT_TABLE_ACCESS_METHOD; |
50 | | bool synchronize_seqscans = true; |
51 | | |
52 | | |
53 | | /* ---------------------------------------------------------------------------- |
54 | | * Slot functions. |
55 | | * ---------------------------------------------------------------------------- |
56 | | */ |
57 | | |
58 | | const TupleTableSlotOps * |
59 | | table_slot_callbacks(Relation relation) |
60 | 0 | { |
61 | 0 | const TupleTableSlotOps *tts_cb; |
62 | |
|
63 | 0 | if (relation->rd_tableam) |
64 | 0 | tts_cb = relation->rd_tableam->slot_callbacks(relation); |
65 | 0 | else if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) |
66 | 0 | { |
67 | | /* |
68 | | * Historically FDWs expect to store heap tuples in slots. Continue |
69 | | * handing them one, to make it less painful to adapt FDWs to new |
70 | | * versions. The cost of a heap slot over a virtual slot is pretty |
71 | | * small. |
72 | | */ |
73 | 0 | tts_cb = &TTSOpsHeapTuple; |
74 | 0 | } |
75 | 0 | else |
76 | 0 | { |
77 | | /* |
78 | | * These need to be supported, as some parts of the code (like COPY) |
79 | | * need to create slots for such relations too. It seems better to |
80 | | * centralize the knowledge that a heap slot is the right thing in |
81 | | * that case here. |
82 | | */ |
83 | 0 | Assert(relation->rd_rel->relkind == RELKIND_VIEW || |
84 | 0 | relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); |
85 | 0 | tts_cb = &TTSOpsVirtual; |
86 | 0 | } |
87 | |
|
88 | 0 | return tts_cb; |
89 | 0 | } |
90 | | |
91 | | TupleTableSlot * |
92 | | table_slot_create(Relation relation, List **reglist) |
93 | 0 | { |
94 | 0 | const TupleTableSlotOps *tts_cb; |
95 | 0 | TupleTableSlot *slot; |
96 | |
|
97 | 0 | tts_cb = table_slot_callbacks(relation); |
98 | 0 | slot = MakeSingleTupleTableSlot(RelationGetDescr(relation), tts_cb); |
99 | |
|
100 | 0 | if (reglist) |
101 | 0 | *reglist = lappend(*reglist, slot); |
102 | |
|
103 | 0 | return slot; |
104 | 0 | } |
105 | | |
106 | | |
107 | | /* ---------------------------------------------------------------------------- |
108 | | * Table scan functions. |
109 | | * ---------------------------------------------------------------------------- |
110 | | */ |
111 | | |
112 | | TableScanDesc |
113 | | table_beginscan_catalog(Relation relation, int nkeys, ScanKeyData *key) |
114 | 0 | { |
115 | 0 | uint32 flags = SO_TYPE_SEQSCAN | |
116 | 0 | SO_ALLOW_STRAT | SO_ALLOW_SYNC | SO_ALLOW_PAGEMODE | SO_TEMP_SNAPSHOT; |
117 | 0 | Oid relid = RelationGetRelid(relation); |
118 | 0 | Snapshot snapshot = RegisterSnapshot(GetCatalogSnapshot(relid)); |
119 | |
|
120 | 0 | return table_beginscan_common(relation, snapshot, nkeys, key, |
121 | 0 | NULL, flags, SO_NONE); |
122 | 0 | } |
123 | | |
124 | | |
125 | | /* ---------------------------------------------------------------------------- |
126 | | * Parallel table scan related functions. |
127 | | * ---------------------------------------------------------------------------- |
128 | | */ |
129 | | |
130 | | Size |
131 | | table_parallelscan_estimate(Relation rel, Snapshot snapshot) |
132 | 0 | { |
133 | 0 | Size sz = 0; |
134 | |
|
135 | 0 | if (IsMVCCSnapshot(snapshot)) |
136 | 0 | sz = add_size(sz, EstimateSnapshotSpace(snapshot)); |
137 | 0 | else |
138 | 0 | Assert(snapshot == SnapshotAny); |
139 | |
|
140 | 0 | sz = add_size(sz, rel->rd_tableam->parallelscan_estimate(rel)); |
141 | |
|
142 | 0 | return sz; |
143 | 0 | } |
144 | | |
145 | | void |
146 | | table_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan, |
147 | | Snapshot snapshot) |
148 | 0 | { |
149 | 0 | Size snapshot_off = rel->rd_tableam->parallelscan_initialize(rel, pscan); |
150 | |
|
151 | 0 | pscan->phs_snapshot_off = snapshot_off; |
152 | |
|
153 | 0 | if (IsMVCCSnapshot(snapshot)) |
154 | 0 | { |
155 | 0 | SerializeSnapshot(snapshot, (char *) pscan + pscan->phs_snapshot_off); |
156 | 0 | pscan->phs_snapshot_any = false; |
157 | 0 | } |
158 | 0 | else |
159 | 0 | { |
160 | 0 | Assert(snapshot == SnapshotAny); |
161 | 0 | pscan->phs_snapshot_any = true; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | TableScanDesc |
166 | | table_beginscan_parallel(Relation relation, ParallelTableScanDesc pscan, |
167 | | uint32 flags) |
168 | 0 | { |
169 | 0 | Snapshot snapshot; |
170 | 0 | uint32 internal_flags = SO_TYPE_SEQSCAN | |
171 | 0 | SO_ALLOW_STRAT | SO_ALLOW_SYNC | SO_ALLOW_PAGEMODE; |
172 | |
|
173 | 0 | Assert(RelFileLocatorEquals(relation->rd_locator, pscan->phs_locator)); |
174 | |
|
175 | 0 | if (!pscan->phs_snapshot_any) |
176 | 0 | { |
177 | | /* Snapshot was serialized -- restore it */ |
178 | 0 | snapshot = RestoreSnapshot((char *) pscan + pscan->phs_snapshot_off); |
179 | 0 | RegisterSnapshot(snapshot); |
180 | 0 | internal_flags |= SO_TEMP_SNAPSHOT; |
181 | 0 | } |
182 | 0 | else |
183 | 0 | { |
184 | | /* SnapshotAny passed by caller (not serialized) */ |
185 | 0 | snapshot = SnapshotAny; |
186 | 0 | } |
187 | |
|
188 | 0 | return table_beginscan_common(relation, snapshot, 0, NULL, |
189 | 0 | pscan, internal_flags, flags); |
190 | 0 | } |
191 | | |
192 | | TableScanDesc |
193 | | table_beginscan_parallel_tidrange(Relation relation, |
194 | | ParallelTableScanDesc pscan, |
195 | | uint32 flags) |
196 | 0 | { |
197 | 0 | Snapshot snapshot; |
198 | 0 | TableScanDesc sscan; |
199 | 0 | uint32 internal_flags = SO_TYPE_TIDRANGESCAN | SO_ALLOW_PAGEMODE; |
200 | |
|
201 | 0 | Assert(RelFileLocatorEquals(relation->rd_locator, pscan->phs_locator)); |
202 | | |
203 | | /* disable syncscan in parallel tid range scan. */ |
204 | 0 | pscan->phs_syncscan = false; |
205 | |
|
206 | 0 | if (!pscan->phs_snapshot_any) |
207 | 0 | { |
208 | | /* Snapshot was serialized -- restore it */ |
209 | 0 | snapshot = RestoreSnapshot((char *) pscan + pscan->phs_snapshot_off); |
210 | 0 | RegisterSnapshot(snapshot); |
211 | 0 | internal_flags |= SO_TEMP_SNAPSHOT; |
212 | 0 | } |
213 | 0 | else |
214 | 0 | { |
215 | | /* SnapshotAny passed by caller (not serialized) */ |
216 | 0 | snapshot = SnapshotAny; |
217 | 0 | } |
218 | |
|
219 | 0 | sscan = table_beginscan_common(relation, snapshot, 0, NULL, |
220 | 0 | pscan, internal_flags, flags); |
221 | 0 | return sscan; |
222 | 0 | } |
223 | | |
224 | | |
225 | | /* ------------------------------------------------------------------------ |
226 | | * Functions for non-modifying operations on individual tuples |
227 | | * ------------------------------------------------------------------------ |
228 | | */ |
229 | | |
230 | | void |
231 | | table_tuple_get_latest_tid(TableScanDesc scan, ItemPointer tid) |
232 | 0 | { |
233 | 0 | Relation rel = scan->rs_rd; |
234 | 0 | const TableAmRoutine *tableam = rel->rd_tableam; |
235 | | |
236 | | /* |
237 | | * Since this can be called with user-supplied TID, don't trust the input |
238 | | * too much. |
239 | | */ |
240 | 0 | if (!tableam->tuple_tid_valid(scan, tid)) |
241 | 0 | ereport(ERROR, |
242 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
243 | 0 | errmsg("tid (%u, %u) is not valid for relation \"%s\"", |
244 | 0 | ItemPointerGetBlockNumberNoCheck(tid), |
245 | 0 | ItemPointerGetOffsetNumberNoCheck(tid), |
246 | 0 | RelationGetRelationName(rel)))); |
247 | | |
248 | 0 | tableam->tuple_get_latest_tid(scan, tid); |
249 | 0 | } |
250 | | |
251 | | |
252 | | /* ---------------------------------------------------------------------------- |
253 | | * Functions to make modifications a bit simpler. |
254 | | * ---------------------------------------------------------------------------- |
255 | | */ |
256 | | |
257 | | /* |
258 | | * simple_table_tuple_insert - insert a tuple |
259 | | * |
260 | | * Currently, this routine differs from table_tuple_insert only in supplying a |
261 | | * default command ID and not allowing access to the speedup options. |
262 | | */ |
263 | | void |
264 | | simple_table_tuple_insert(Relation rel, TupleTableSlot *slot) |
265 | 0 | { |
266 | 0 | table_tuple_insert(rel, slot, GetCurrentCommandId(true), 0, NULL); |
267 | 0 | } |
268 | | |
269 | | /* |
270 | | * simple_table_tuple_delete - delete a tuple |
271 | | * |
272 | | * This routine may be used to delete a tuple when concurrent updates of |
273 | | * the target tuple are not expected (for example, because we have a lock |
274 | | * on the relation associated with the tuple). Any failure is reported |
275 | | * via ereport(). |
276 | | */ |
277 | | void |
278 | | simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot) |
279 | 0 | { |
280 | 0 | TM_Result result; |
281 | 0 | TM_FailureData tmfd; |
282 | |
|
283 | 0 | result = table_tuple_delete(rel, tid, |
284 | 0 | GetCurrentCommandId(true), |
285 | 0 | 0, snapshot, InvalidSnapshot, |
286 | 0 | true /* wait for commit */ , |
287 | 0 | &tmfd); |
288 | |
|
289 | 0 | switch (result) |
290 | 0 | { |
291 | 0 | case TM_SelfModified: |
292 | | /* Tuple was already updated in current command? */ |
293 | 0 | elog(ERROR, "tuple already updated by self"); |
294 | 0 | break; |
295 | | |
296 | 0 | case TM_Ok: |
297 | | /* done successfully */ |
298 | 0 | break; |
299 | | |
300 | 0 | case TM_Updated: |
301 | 0 | elog(ERROR, "tuple concurrently updated"); |
302 | 0 | break; |
303 | | |
304 | 0 | case TM_Deleted: |
305 | 0 | elog(ERROR, "tuple concurrently deleted"); |
306 | 0 | break; |
307 | | |
308 | 0 | default: |
309 | 0 | elog(ERROR, "unrecognized table_tuple_delete status: %u", result); |
310 | 0 | break; |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | /* |
315 | | * simple_table_tuple_update - replace a tuple |
316 | | * |
317 | | * This routine may be used to update a tuple when concurrent updates of |
318 | | * the target tuple are not expected (for example, because we have a lock |
319 | | * on the relation associated with the tuple). Any failure is reported |
320 | | * via ereport(). |
321 | | */ |
322 | | void |
323 | | simple_table_tuple_update(Relation rel, ItemPointer otid, |
324 | | TupleTableSlot *slot, |
325 | | Snapshot snapshot, |
326 | | TU_UpdateIndexes *update_indexes) |
327 | 0 | { |
328 | 0 | TM_Result result; |
329 | 0 | TM_FailureData tmfd; |
330 | 0 | LockTupleMode lockmode; |
331 | |
|
332 | 0 | result = table_tuple_update(rel, otid, slot, |
333 | 0 | GetCurrentCommandId(true), |
334 | 0 | 0, snapshot, InvalidSnapshot, |
335 | 0 | true /* wait for commit */ , |
336 | 0 | &tmfd, &lockmode, update_indexes); |
337 | |
|
338 | 0 | switch (result) |
339 | 0 | { |
340 | 0 | case TM_SelfModified: |
341 | | /* Tuple was already updated in current command? */ |
342 | 0 | elog(ERROR, "tuple already updated by self"); |
343 | 0 | break; |
344 | | |
345 | 0 | case TM_Ok: |
346 | | /* done successfully */ |
347 | 0 | break; |
348 | | |
349 | 0 | case TM_Updated: |
350 | 0 | elog(ERROR, "tuple concurrently updated"); |
351 | 0 | break; |
352 | | |
353 | 0 | case TM_Deleted: |
354 | 0 | elog(ERROR, "tuple concurrently deleted"); |
355 | 0 | break; |
356 | | |
357 | 0 | default: |
358 | 0 | elog(ERROR, "unrecognized table_tuple_update status: %u", result); |
359 | 0 | break; |
360 | 0 | } |
361 | 0 | } |
362 | | |
363 | | |
364 | | /* ---------------------------------------------------------------------------- |
365 | | * Helper functions to implement parallel scans for block oriented AMs. |
366 | | * ---------------------------------------------------------------------------- |
367 | | */ |
368 | | |
369 | | Size |
370 | | table_block_parallelscan_estimate(Relation rel) |
371 | 0 | { |
372 | 0 | return sizeof(ParallelBlockTableScanDescData); |
373 | 0 | } |
374 | | |
375 | | Size |
376 | | table_block_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan) |
377 | 0 | { |
378 | 0 | ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan; |
379 | |
|
380 | 0 | bpscan->base.phs_locator = rel->rd_locator; |
381 | 0 | bpscan->phs_nblocks = RelationGetNumberOfBlocks(rel); |
382 | | /* compare phs_syncscan initialization to similar logic in initscan */ |
383 | 0 | bpscan->base.phs_syncscan = synchronize_seqscans && |
384 | 0 | !RelationUsesLocalBuffers(rel) && |
385 | 0 | bpscan->phs_nblocks > NBuffers / 4; |
386 | 0 | pg_atomic_init_u32(&bpscan->phs_startblock, InvalidBlockNumber); |
387 | 0 | pg_atomic_init_u32(&bpscan->phs_numblock, InvalidBlockNumber); |
388 | 0 | pg_atomic_init_u64(&bpscan->phs_nallocated, 0); |
389 | |
|
390 | 0 | return sizeof(ParallelBlockTableScanDescData); |
391 | 0 | } |
392 | | |
393 | | void |
394 | | table_block_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan) |
395 | 0 | { |
396 | 0 | ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan; |
397 | | |
398 | | /* |
399 | | * Reset the start block and block limit too. These are set by the first |
400 | | * participant to call table_block_parallelscan_startblock_init(), and a |
401 | | * rescan may want to scan a different range of blocks, e.g. a TID Range |
402 | | * Scan with different TID bounds. |
403 | | */ |
404 | 0 | pg_atomic_write_u32(&bpscan->phs_startblock, InvalidBlockNumber); |
405 | 0 | pg_atomic_write_u32(&bpscan->phs_numblock, InvalidBlockNumber); |
406 | 0 | pg_atomic_write_u64(&bpscan->phs_nallocated, 0); |
407 | 0 | } |
408 | | |
409 | | /* |
410 | | * find and set the scan's startblock |
411 | | * |
412 | | * Determine where the parallel seq scan should start. This function may be |
413 | | * called many times, once by each parallel worker. We must be careful only |
414 | | * to set the phs_startblock and phs_numblock fields once. |
415 | | * |
416 | | * Callers may optionally specify a non-InvalidBlockNumber value for |
417 | | * 'startblock' to force the scan to start at the given page. Likewise, |
418 | | * 'numblocks' can be specified as a non-InvalidBlockNumber to limit the |
419 | | * number of blocks to scan to that many blocks. |
420 | | */ |
421 | | void |
422 | | table_block_parallelscan_startblock_init(Relation rel, |
423 | | ParallelBlockTableScanWorker pbscanwork, |
424 | | ParallelBlockTableScanDesc pbscan, |
425 | | BlockNumber startblock, |
426 | | BlockNumber numblocks) |
427 | 0 | { |
428 | 0 | StaticAssertDecl(MaxBlockNumber <= 0xFFFFFFFE, |
429 | 0 | "pg_nextpower2_32 may be too small for non-standard BlockNumber width"); |
430 | |
|
431 | 0 | BlockNumber scan_nblocks; |
432 | | |
433 | | /* Reset the state we use for controlling allocation size. */ |
434 | 0 | memset(pbscanwork, 0, sizeof(*pbscanwork)); |
435 | | |
436 | | /* |
437 | | * When the caller specified a limit on the number of blocks to scan, set |
438 | | * that in the ParallelBlockTableScanDesc, if it's not been done by |
439 | | * another worker already. |
440 | | */ |
441 | 0 | if (numblocks != InvalidBlockNumber) |
442 | 0 | { |
443 | 0 | uint32 expected = InvalidBlockNumber; |
444 | |
|
445 | 0 | pg_atomic_compare_exchange_u32(&pbscan->phs_numblock, &expected, |
446 | 0 | numblocks); |
447 | 0 | } |
448 | | |
449 | | /* |
450 | | * If the scan's phs_startblock has not yet been initialized, we must do |
451 | | * so now. If a startblock was specified, start there, otherwise if this |
452 | | * is not a synchronized scan, we just start at block 0, but if it is a |
453 | | * synchronized scan, we must get the starting position from the |
454 | | * synchronized scan machinery. |
455 | | * |
456 | | * If another worker initializes phs_startblock concurrently, just use |
457 | | * their value. |
458 | | */ |
459 | 0 | if (pg_atomic_read_u32(&pbscan->phs_startblock) == InvalidBlockNumber) |
460 | 0 | { |
461 | 0 | BlockNumber newstartblock; |
462 | 0 | uint32 expected = InvalidBlockNumber; |
463 | |
|
464 | 0 | if (startblock != InvalidBlockNumber) |
465 | 0 | newstartblock = startblock; |
466 | 0 | else if (!pbscan->base.phs_syncscan) |
467 | 0 | newstartblock = 0; |
468 | 0 | else |
469 | 0 | newstartblock = ss_get_location(rel, pbscan->phs_nblocks); |
470 | |
|
471 | 0 | pg_atomic_compare_exchange_u32(&pbscan->phs_startblock, &expected, |
472 | 0 | newstartblock); |
473 | 0 | } |
474 | | |
475 | | /* |
476 | | * Figure out how many blocks we're going to scan; either all of them, or |
477 | | * just phs_numblock's worth, if a limit has been imposed. |
478 | | */ |
479 | 0 | if (pg_atomic_read_u32(&pbscan->phs_numblock) == InvalidBlockNumber) |
480 | 0 | scan_nblocks = pbscan->phs_nblocks; |
481 | 0 | else |
482 | 0 | scan_nblocks = pg_atomic_read_u32(&pbscan->phs_numblock); |
483 | | |
484 | | /* |
485 | | * We determine the chunk size based on scan_nblocks. First we split |
486 | | * scan_nblocks into PARALLEL_SEQSCAN_NCHUNKS chunks then we calculate the |
487 | | * next highest power of 2 number of the result. This means we split the |
488 | | * blocks we're scanning into somewhere between PARALLEL_SEQSCAN_NCHUNKS |
489 | | * and PARALLEL_SEQSCAN_NCHUNKS / 2 chunks. |
490 | | */ |
491 | 0 | pbscanwork->phsw_chunk_size = pg_nextpower2_32(Max(scan_nblocks / |
492 | 0 | PARALLEL_SEQSCAN_NCHUNKS, 1)); |
493 | | |
494 | | /* |
495 | | * Ensure we don't go over the maximum chunk size with larger tables. This |
496 | | * means we may get much more than PARALLEL_SEQSCAN_NCHUNKS for larger |
497 | | * tables. Too large a chunk size has been shown to be detrimental to |
498 | | * sequential scan performance. |
499 | | */ |
500 | 0 | pbscanwork->phsw_chunk_size = Min(pbscanwork->phsw_chunk_size, |
501 | 0 | PARALLEL_SEQSCAN_MAX_CHUNK_SIZE); |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | * get the next page to scan |
506 | | * |
507 | | * Get the next page to scan. Even if there are no pages left to scan, |
508 | | * another backend could have grabbed a page to scan and not yet finished |
509 | | * looking at it, so it doesn't follow that the scan is done when the first |
510 | | * backend gets an InvalidBlockNumber return. |
511 | | */ |
512 | | BlockNumber |
513 | | table_block_parallelscan_nextpage(Relation rel, |
514 | | ParallelBlockTableScanWorker pbscanwork, |
515 | | ParallelBlockTableScanDesc pbscan) |
516 | 0 | { |
517 | 0 | BlockNumber scan_nblocks; |
518 | 0 | BlockNumber page; |
519 | 0 | uint64 nallocated; |
520 | | |
521 | | /* |
522 | | * The logic below allocates block numbers out to parallel workers in a |
523 | | * way that each worker will receive a set of consecutive block numbers to |
524 | | * scan. Earlier versions of this would allocate the next highest block |
525 | | * number to the next worker to call this function. This would generally |
526 | | * result in workers never receiving consecutive block numbers. Some |
527 | | * operating systems would not detect the sequential I/O pattern due to |
528 | | * each backend being a different process which could result in poor |
529 | | * performance due to inefficient or no readahead. To work around this |
530 | | * issue, we now allocate a range of block numbers for each worker and |
531 | | * when they come back for another block, we give them the next one in |
532 | | * that range until the range is complete. When the worker completes the |
533 | | * range of blocks we then allocate another range for it and return the |
534 | | * first block number from that range. |
535 | | * |
536 | | * Here we name these ranges of blocks "chunks". The initial size of |
537 | | * these chunks is determined in table_block_parallelscan_startblock_init |
538 | | * based on the number of blocks to scan. Towards the end of the scan, we |
539 | | * start making reductions in the size of the chunks in order to attempt |
540 | | * to divide the remaining work over all the workers as evenly as |
541 | | * possible. |
542 | | * |
543 | | * Here pbscanwork is local worker memory. phsw_chunk_remaining tracks |
544 | | * the number of blocks remaining in the chunk. When that reaches 0 then |
545 | | * we must allocate a new chunk for the worker. |
546 | | * |
547 | | * phs_nallocated tracks how many blocks have been allocated to workers |
548 | | * already. When phs_nallocated >= rs_nblocks, all blocks have been |
549 | | * allocated. |
550 | | * |
551 | | * Because we use an atomic fetch-and-add to fetch the current value, the |
552 | | * phs_nallocated counter will exceed rs_nblocks, because workers will |
553 | | * still increment the value, when they try to allocate the next block but |
554 | | * all blocks have been allocated already. The counter must be 64 bits |
555 | | * wide because of that, to avoid wrapping around when scan_nblocks is |
556 | | * close to 2^32. |
557 | | * |
558 | | * The actual block to return is calculated by adding the counter to the |
559 | | * starting block number, modulo phs_nblocks. |
560 | | */ |
561 | | |
562 | | /* First, figure out how many blocks we're planning on scanning */ |
563 | 0 | if (pg_atomic_read_u32(&pbscan->phs_numblock) == InvalidBlockNumber) |
564 | 0 | scan_nblocks = pbscan->phs_nblocks; |
565 | 0 | else |
566 | 0 | scan_nblocks = pg_atomic_read_u32(&pbscan->phs_numblock); |
567 | | |
568 | | /* |
569 | | * Now check if we have any remaining blocks in a previous chunk for this |
570 | | * worker. We must consume all of the blocks from that before we allocate |
571 | | * a new chunk to the worker. |
572 | | */ |
573 | 0 | if (pbscanwork->phsw_chunk_remaining > 0) |
574 | 0 | { |
575 | | /* |
576 | | * Give them the next block in the range and update the remaining |
577 | | * number of blocks. |
578 | | */ |
579 | 0 | nallocated = ++pbscanwork->phsw_nallocated; |
580 | 0 | pbscanwork->phsw_chunk_remaining--; |
581 | 0 | } |
582 | 0 | else |
583 | 0 | { |
584 | | /* |
585 | | * When we've only got PARALLEL_SEQSCAN_RAMPDOWN_CHUNKS chunks |
586 | | * remaining in the scan, we half the chunk size. Since we reduce the |
587 | | * chunk size here, we'll hit this again after doing |
588 | | * PARALLEL_SEQSCAN_RAMPDOWN_CHUNKS at the new size. After a few |
589 | | * iterations of this, we'll end up doing the last few blocks with the |
590 | | * chunk size set to 1. |
591 | | */ |
592 | 0 | if (pbscanwork->phsw_chunk_size > 1 && |
593 | 0 | pbscanwork->phsw_nallocated > scan_nblocks - |
594 | 0 | (pbscanwork->phsw_chunk_size * PARALLEL_SEQSCAN_RAMPDOWN_CHUNKS)) |
595 | 0 | pbscanwork->phsw_chunk_size >>= 1; |
596 | |
|
597 | 0 | nallocated = pbscanwork->phsw_nallocated = |
598 | 0 | pg_atomic_fetch_add_u64(&pbscan->phs_nallocated, |
599 | 0 | pbscanwork->phsw_chunk_size); |
600 | | |
601 | | /* |
602 | | * Set the remaining number of blocks in this chunk so that subsequent |
603 | | * calls from this worker continue on with this chunk until it's done. |
604 | | */ |
605 | 0 | pbscanwork->phsw_chunk_remaining = pbscanwork->phsw_chunk_size - 1; |
606 | 0 | } |
607 | | |
608 | | /* Check if we've run out of blocks to scan */ |
609 | 0 | if (nallocated >= scan_nblocks) |
610 | 0 | page = InvalidBlockNumber; /* all blocks have been allocated */ |
611 | 0 | else |
612 | 0 | page = (nallocated + pg_atomic_read_u32(&pbscan->phs_startblock)) % pbscan->phs_nblocks; |
613 | | |
614 | | /* |
615 | | * Report scan location. Normally, we report the current page number. |
616 | | * When we reach the end of the scan, though, we report the starting page, |
617 | | * not the ending page, just so the starting positions for later scans |
618 | | * doesn't slew backwards. We only report the position at the end of the |
619 | | * scan once, though: subsequent callers will report nothing. |
620 | | */ |
621 | 0 | if (pbscan->base.phs_syncscan) |
622 | 0 | { |
623 | 0 | if (page != InvalidBlockNumber) |
624 | 0 | ss_report_location(rel, page); |
625 | 0 | else if (nallocated == pbscan->phs_nblocks) |
626 | 0 | ss_report_location(rel, pg_atomic_read_u32(&pbscan->phs_startblock)); |
627 | 0 | } |
628 | |
|
629 | 0 | return page; |
630 | 0 | } |
631 | | |
632 | | /* ---------------------------------------------------------------------------- |
633 | | * Helper functions to implement relation sizing for block oriented AMs. |
634 | | * ---------------------------------------------------------------------------- |
635 | | */ |
636 | | |
637 | | /* |
638 | | * table_block_relation_size |
639 | | * |
640 | | * If a table AM uses the various relation forks as the sole place where data |
641 | | * is stored, and if it uses them in the expected manner (e.g. the actual data |
642 | | * is in the main fork rather than some other), it can use this implementation |
643 | | * of the relation_size callback rather than implementing its own. |
644 | | */ |
645 | | uint64 |
646 | | table_block_relation_size(Relation rel, ForkNumber forkNumber) |
647 | 0 | { |
648 | 0 | uint64 nblocks = 0; |
649 | | |
650 | | /* InvalidForkNumber indicates returning the size for all forks */ |
651 | 0 | if (forkNumber == InvalidForkNumber) |
652 | 0 | { |
653 | 0 | for (int i = 0; i < MAX_FORKNUM; i++) |
654 | 0 | nblocks += smgrnblocks(RelationGetSmgr(rel), i); |
655 | 0 | } |
656 | 0 | else |
657 | 0 | nblocks = smgrnblocks(RelationGetSmgr(rel), forkNumber); |
658 | |
|
659 | 0 | return nblocks * BLCKSZ; |
660 | 0 | } |
661 | | |
662 | | /* |
663 | | * table_block_relation_estimate_size |
664 | | * |
665 | | * This function can't be directly used as the implementation of the |
666 | | * relation_estimate_size callback, because it has a few additional parameters. |
667 | | * Instead, it is intended to be used as a helper function; the caller can |
668 | | * pass through the arguments to its relation_estimate_size function plus the |
669 | | * additional values required here. |
670 | | * |
671 | | * overhead_bytes_per_tuple should contain the approximate number of bytes |
672 | | * of storage required to store a tuple above and beyond what is required for |
673 | | * the tuple data proper. Typically, this would include things like the |
674 | | * size of the tuple header and item pointer. This is only used for query |
675 | | * planning, so a table AM where the value is not constant could choose to |
676 | | * pass a "best guess". |
677 | | * |
678 | | * usable_bytes_per_page should contain the approximate number of bytes per |
679 | | * page usable for tuple data, excluding the page header and any anticipated |
680 | | * special space. |
681 | | */ |
682 | | void |
683 | | table_block_relation_estimate_size(Relation rel, int32 *attr_widths, |
684 | | BlockNumber *pages, double *tuples, |
685 | | double *allvisfrac, |
686 | | Size overhead_bytes_per_tuple, |
687 | | Size usable_bytes_per_page) |
688 | 0 | { |
689 | 0 | BlockNumber curpages; |
690 | 0 | BlockNumber relpages; |
691 | 0 | double reltuples; |
692 | 0 | BlockNumber relallvisible; |
693 | 0 | double density; |
694 | | |
695 | | /* it should have storage, so we can call the smgr */ |
696 | 0 | curpages = RelationGetNumberOfBlocks(rel); |
697 | | |
698 | | /* coerce values in pg_class to more desirable types */ |
699 | 0 | relpages = (BlockNumber) rel->rd_rel->relpages; |
700 | 0 | reltuples = (double) rel->rd_rel->reltuples; |
701 | 0 | relallvisible = (BlockNumber) rel->rd_rel->relallvisible; |
702 | | |
703 | | /* |
704 | | * HACK: if the relation has never yet been vacuumed, use a minimum size |
705 | | * estimate of 10 pages. The idea here is to avoid assuming a |
706 | | * newly-created table is really small, even if it currently is, because |
707 | | * that may not be true once some data gets loaded into it. Once a vacuum |
708 | | * or analyze cycle has been done on it, it's more reasonable to believe |
709 | | * the size is somewhat stable. |
710 | | * |
711 | | * (Note that this is only an issue if the plan gets cached and used again |
712 | | * after the table has been filled. What we're trying to avoid is using a |
713 | | * nestloop-type plan on a table that has grown substantially since the |
714 | | * plan was made. Normally, autovacuum/autoanalyze will occur once enough |
715 | | * inserts have happened and cause cached-plan invalidation; but that |
716 | | * doesn't happen instantaneously, and it won't happen at all for cases |
717 | | * such as temporary tables.) |
718 | | * |
719 | | * We test "never vacuumed" by seeing whether reltuples < 0. |
720 | | * |
721 | | * If the table has inheritance children, we don't apply this heuristic. |
722 | | * Totally empty parent tables are quite common, so we should be willing |
723 | | * to believe that they are empty. |
724 | | */ |
725 | 0 | if (curpages < 10 && |
726 | 0 | reltuples < 0 && |
727 | 0 | !rel->rd_rel->relhassubclass) |
728 | 0 | curpages = 10; |
729 | | |
730 | | /* report estimated # pages */ |
731 | 0 | *pages = curpages; |
732 | | /* quick exit if rel is clearly empty */ |
733 | 0 | if (curpages == 0) |
734 | 0 | { |
735 | 0 | *tuples = 0; |
736 | 0 | *allvisfrac = 0; |
737 | 0 | return; |
738 | 0 | } |
739 | | |
740 | | /* estimate number of tuples from previous tuple density */ |
741 | 0 | if (reltuples >= 0 && relpages > 0) |
742 | 0 | density = reltuples / (double) relpages; |
743 | 0 | else |
744 | 0 | { |
745 | | /* |
746 | | * When we have no data because the relation was never yet vacuumed, |
747 | | * estimate tuple width from attribute datatypes. We assume here that |
748 | | * the pages are completely full, which is OK for tables but is |
749 | | * probably an overestimate for indexes. Fortunately |
750 | | * get_relation_info() can clamp the overestimate to the parent |
751 | | * table's size. |
752 | | * |
753 | | * Note: this code intentionally disregards alignment considerations, |
754 | | * because (a) that would be gilding the lily considering how crude |
755 | | * the estimate is, (b) it creates platform dependencies in the |
756 | | * default plans which are kind of a headache for regression testing, |
757 | | * and (c) different table AMs might use different padding schemes. |
758 | | */ |
759 | 0 | int32 tuple_width; |
760 | 0 | int fillfactor; |
761 | | |
762 | | /* |
763 | | * Without reltuples/relpages, we also need to consider fillfactor. |
764 | | * The other branch considers it implicitly by calculating density |
765 | | * from actual relpages/reltuples statistics. |
766 | | */ |
767 | 0 | fillfactor = RelationGetFillFactor(rel, HEAP_DEFAULT_FILLFACTOR); |
768 | |
|
769 | 0 | tuple_width = get_rel_data_width(rel, attr_widths); |
770 | 0 | tuple_width += overhead_bytes_per_tuple; |
771 | | /* note: integer division is intentional here */ |
772 | 0 | density = (usable_bytes_per_page * fillfactor / 100) / tuple_width; |
773 | | /* There's at least one row on the page, even with low fillfactor. */ |
774 | 0 | density = clamp_row_est(density); |
775 | 0 | } |
776 | 0 | *tuples = rint(density * (double) curpages); |
777 | | |
778 | | /* |
779 | | * We use relallvisible as-is, rather than scaling it up like we do for |
780 | | * the pages and tuples counts, on the theory that any pages added since |
781 | | * the last VACUUM are most likely not marked all-visible. But costsize.c |
782 | | * wants it converted to a fraction. |
783 | | */ |
784 | 0 | if (relallvisible == 0 || curpages <= 0) |
785 | 0 | *allvisfrac = 0; |
786 | 0 | else if ((double) relallvisible >= curpages) |
787 | 0 | *allvisfrac = 1; |
788 | 0 | else |
789 | 0 | *allvisfrac = (double) relallvisible / curpages; |
790 | 0 | } |