Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_prewarm.c
4 : : * prewarming utilities
5 : : *
6 : : * Copyright (c) 2010-2025, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * contrib/pg_prewarm/pg_prewarm.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include <sys/stat.h>
16 : : #include <unistd.h>
17 : :
18 : : #include "access/relation.h"
19 : : #include "catalog/index.h"
20 : : #include "fmgr.h"
21 : : #include "miscadmin.h"
22 : : #include "storage/bufmgr.h"
23 : : #include "storage/lmgr.h"
24 : : #include "storage/read_stream.h"
25 : : #include "storage/smgr.h"
26 : : #include "utils/acl.h"
27 : : #include "utils/builtins.h"
28 : : #include "utils/lsyscache.h"
29 : : #include "utils/rel.h"
30 : :
215 tgl@sss.pgh.pa.us 31 :CBC 6 : PG_MODULE_MAGIC_EXT(
32 : : .name = "pg_prewarm",
33 : : .version = PG_VERSION
34 : : );
35 : :
4329 rhaas@postgresql.org 36 : 13 : PG_FUNCTION_INFO_V1(pg_prewarm);
37 : :
38 : : typedef enum
39 : : {
40 : : PREWARM_PREFETCH,
41 : : PREWARM_READ,
42 : : PREWARM_BUFFER,
43 : : } PrewarmType;
44 : :
45 : : static PGIOAlignedBlock blockbuffer;
46 : :
47 : : /*
48 : : * pg_prewarm(regclass, mode text, fork text,
49 : : * first_block int8, last_block int8)
50 : : *
51 : : * The first argument is the relation to be prewarmed; the second controls
52 : : * how prewarming is done; legal options are 'prefetch', 'read', and 'buffer'.
53 : : * The third is the name of the relation fork to be prewarmed. The fourth
54 : : * and fifth arguments specify the first and last block to be prewarmed.
55 : : * If the fourth argument is NULL, it will be taken as 0; if the fifth argument
56 : : * is NULL, it will be taken as the number of blocks in the relation. The
57 : : * return value is the number of blocks successfully prewarmed.
58 : : */
59 : : Datum
60 : 2738 : pg_prewarm(PG_FUNCTION_ARGS)
61 : : {
62 : : Oid relOid;
63 : : text *forkName;
64 : : text *type;
65 : : int64 first_block;
66 : : int64 last_block;
67 : : int64 nblocks;
68 : 2738 : int64 blocks_done = 0;
69 : : int64 block;
70 : : Relation rel;
71 : : ForkNumber forkNumber;
72 : : char *forkString;
73 : : char *ttype;
74 : : PrewarmType ptype;
75 : : AclResult aclresult;
76 : : char relkind;
77 : : Oid privOid;
78 : :
79 : : /* Basic sanity checking. */
80 [ - + ]: 2738 : if (PG_ARGISNULL(0))
4329 rhaas@postgresql.org 81 [ # # ]:UBC 0 : ereport(ERROR,
82 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
83 : : errmsg("relation cannot be null")));
4329 rhaas@postgresql.org 84 :CBC 2738 : relOid = PG_GETARG_OID(0);
85 [ - + ]: 2738 : if (PG_ARGISNULL(1))
4329 rhaas@postgresql.org 86 [ # # ]:UBC 0 : ereport(ERROR,
87 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
88 : : errmsg("prewarm type cannot be null")));
3151 noah@leadboat.com 89 :CBC 2738 : type = PG_GETARG_TEXT_PP(1);
4329 rhaas@postgresql.org 90 : 2738 : ttype = text_to_cstring(type);
91 [ + + ]: 2738 : if (strcmp(ttype, "prefetch") == 0)
92 : 1 : ptype = PREWARM_PREFETCH;
93 [ + + ]: 2737 : else if (strcmp(ttype, "read") == 0)
94 : 1 : ptype = PREWARM_READ;
95 [ + - ]: 2736 : else if (strcmp(ttype, "buffer") == 0)
96 : 2736 : ptype = PREWARM_BUFFER;
97 : : else
98 : : {
4329 rhaas@postgresql.org 99 [ # # ]:UBC 0 : ereport(ERROR,
100 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
101 : : errmsg("invalid prewarm type"),
102 : : errhint("Valid prewarm types are \"prefetch\", \"read\", and \"buffer\".")));
103 : : PG_RETURN_INT64(0); /* Placate compiler. */
104 : : }
4329 rhaas@postgresql.org 105 [ - + ]:CBC 2738 : if (PG_ARGISNULL(2))
4329 rhaas@postgresql.org 106 [ # # ]:UBC 0 : ereport(ERROR,
107 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
108 : : errmsg("relation fork cannot be null")));
3151 noah@leadboat.com 109 :CBC 2738 : forkName = PG_GETARG_TEXT_PP(2);
4329 rhaas@postgresql.org 110 : 2738 : forkString = text_to_cstring(forkName);
111 : 2738 : forkNumber = forkname_to_number(forkString);
112 : :
113 : : /*
114 : : * Open relation and check privileges. If the relation is an index, we
115 : : * must check the privileges on its parent table instead.
116 : : */
10 nathan@postgresql.or 117 : 2738 : relkind = get_rel_relkind(relOid);
118 [ + + - + ]: 2738 : if (relkind == RELKIND_INDEX ||
119 : : relkind == RELKIND_PARTITIONED_INDEX)
120 : : {
121 : 1642 : privOid = IndexGetRelation(relOid, true);
122 : :
123 : : /* Lock table before index to avoid deadlock. */
124 [ + - ]: 1642 : if (OidIsValid(privOid))
125 : 1642 : LockRelationOid(privOid, AccessShareLock);
126 : : }
127 : : else
128 : 1096 : privOid = relOid;
129 : :
4329 rhaas@postgresql.org 130 : 2738 : rel = relation_open(relOid, AccessShareLock);
131 : :
132 : : /*
133 : : * It's possible that the relation with OID "privOid" was dropped and the
134 : : * OID was reused before we locked it. If that happens, we could be left
135 : : * with the wrong parent table OID, in which case we must ERROR. It's
136 : : * possible that such a race would change the outcome of
137 : : * get_rel_relkind(), too, but the worst case scenario there is that we'll
138 : : * check privileges on the index instead of its parent table, which isn't
139 : : * too terrible.
140 : : */
10 nathan@postgresql.or 141 [ + - + + ]: 2738 : if (!OidIsValid(privOid) ||
142 [ - + ]: 1642 : (privOid != relOid &&
143 : 1642 : privOid != IndexGetRelation(relOid, true)))
10 nathan@postgresql.or 144 [ # # ]:UBC 0 : ereport(ERROR,
145 : : (errcode(ERRCODE_UNDEFINED_TABLE),
146 : : errmsg("could not find parent table of index \"%s\"",
147 : : RelationGetRelationName(rel))));
148 : :
10 nathan@postgresql.or 149 :CBC 2738 : aclresult = pg_class_aclcheck(privOid, GetUserId(), ACL_SELECT);
4329 rhaas@postgresql.org 150 [ + + ]: 2738 : if (aclresult != ACLCHECK_OK)
2886 peter_e@gmx.net 151 : 2 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid));
152 : :
153 : : /* Check that the relation has storage. */
151 fujii@postgresql.org 154 [ + + + + : 2736 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ - + + +
- ]
155 [ + - ]: 1 : ereport(ERROR,
156 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
157 : : errmsg("relation \"%s\" does not have storage",
158 : : RelationGetRelationName(rel)),
159 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
160 : :
161 : : /* Check that the fork exists. */
1568 tgl@sss.pgh.pa.us 162 [ - + ]: 2735 : if (!smgrexists(RelationGetSmgr(rel), forkNumber))
4329 rhaas@postgresql.org 163 [ # # ]:UBC 0 : ereport(ERROR,
164 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
165 : : errmsg("fork \"%s\" does not exist for this relation",
166 : : forkString)));
167 : :
168 : : /* Validate block numbers, or handle nulls. */
4329 rhaas@postgresql.org 169 :CBC 2735 : nblocks = RelationGetNumberOfBlocksInFork(rel, forkNumber);
170 [ + - ]: 2735 : if (PG_ARGISNULL(3))
171 : 2735 : first_block = 0;
172 : : else
173 : : {
4329 rhaas@postgresql.org 174 :UBC 0 : first_block = PG_GETARG_INT64(3);
175 [ # # # # ]: 0 : if (first_block < 0 || first_block >= nblocks)
176 [ # # ]: 0 : ereport(ERROR,
177 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
178 : : errmsg("starting block number must be between 0 and %" PRId64,
179 : : (nblocks - 1))));
180 : : }
4329 rhaas@postgresql.org 181 [ + - ]:CBC 2735 : if (PG_ARGISNULL(4))
182 : 2735 : last_block = nblocks - 1;
183 : : else
184 : : {
4329 rhaas@postgresql.org 185 :UBC 0 : last_block = PG_GETARG_INT64(4);
186 [ # # # # ]: 0 : if (last_block < 0 || last_block >= nblocks)
187 [ # # ]: 0 : ereport(ERROR,
188 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
189 : : errmsg("ending block number must be between 0 and %" PRId64,
190 : : (nblocks - 1))));
191 : : }
192 : :
193 : : /* Now we're ready to do the real work. */
4329 rhaas@postgresql.org 194 [ + + ]:CBC 2735 : if (ptype == PREWARM_PREFETCH)
195 : : {
196 : : #ifdef USE_PREFETCH
197 : :
198 : : /*
199 : : * In prefetch mode, we just hint the OS to read the blocks, but we
200 : : * don't know whether it really does it, and we don't wait for it to
201 : : * finish.
202 : : *
203 : : * It would probably be better to pass our prefetch requests in chunks
204 : : * of a megabyte or maybe even a whole segment at a time, but there's
205 : : * no practical way to do that at present without a gross modularity
206 : : * violation, so we just do this.
207 : : */
208 [ + + ]: 2 : for (block = first_block; block <= last_block; ++block)
209 : : {
4002 andres@anarazel.de 210 [ - + ]: 1 : CHECK_FOR_INTERRUPTS();
4329 rhaas@postgresql.org 211 : 1 : PrefetchBuffer(rel, forkNumber, block);
212 : 1 : ++blocks_done;
213 : : }
214 : : #else
215 : : ereport(ERROR,
216 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
217 : : errmsg("prefetch is not supported by this build")));
218 : : #endif
219 : : }
220 [ + + ]: 2734 : else if (ptype == PREWARM_READ)
221 : : {
222 : : /*
223 : : * In read mode, we actually read the blocks, but not into shared
224 : : * buffers. This is more portable than prefetch mode (it works
225 : : * everywhere) and is synchronous.
226 : : */
227 [ + + ]: 2 : for (block = first_block; block <= last_block; ++block)
228 : : {
4002 andres@anarazel.de 229 [ - + ]: 1 : CHECK_FOR_INTERRUPTS();
1568 tgl@sss.pgh.pa.us 230 : 1 : smgrread(RelationGetSmgr(rel), forkNumber, block, blockbuffer.data);
4329 rhaas@postgresql.org 231 : 1 : ++blocks_done;
232 : : }
233 : : }
234 [ + - ]: 2733 : else if (ptype == PREWARM_BUFFER)
235 : : {
236 : : BlockRangeReadStreamPrivate p;
237 : : ReadStream *stream;
238 : :
239 : : /*
240 : : * In buffer mode, we actually pull the data into shared_buffers.
241 : : */
242 : :
243 : : /* Set up the private state for our streaming buffer read callback. */
419 noah@leadboat.com 244 : 2733 : p.current_blocknum = first_block;
245 : 2733 : p.last_exclusive = last_block + 1;
246 : :
247 : : /*
248 : : * It is safe to use batchmode as block_range_read_stream_cb takes no
249 : : * locks.
250 : : */
182 melanieplageman@gmai 251 : 2733 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
252 : : READ_STREAM_FULL |
253 : : READ_STREAM_USE_BATCHING,
254 : : NULL,
255 : : rel,
256 : : forkNumber,
257 : : block_range_read_stream_cb,
258 : : &p,
259 : : 0);
260 : :
4329 rhaas@postgresql.org 261 [ + + ]: 12277 : for (block = first_block; block <= last_block; ++block)
262 : : {
263 : : Buffer buf;
264 : :
4002 andres@anarazel.de 265 [ - + ]: 9544 : CHECK_FOR_INTERRUPTS();
572 tmunro@postgresql.or 266 : 9544 : buf = read_stream_next_buffer(stream, NULL);
4329 rhaas@postgresql.org 267 : 9544 : ReleaseBuffer(buf);
268 : 9544 : ++blocks_done;
269 : : }
572 tmunro@postgresql.or 270 [ - + ]: 2733 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
271 : 2733 : read_stream_end(stream);
272 : : }
273 : :
274 : : /* Close relation, release locks. */
4329 rhaas@postgresql.org 275 : 2735 : relation_close(rel, AccessShareLock);
276 : :
10 nathan@postgresql.or 277 [ + + ]: 2735 : if (privOid != relOid)
278 : 1641 : UnlockRelationOid(privOid, AccessShareLock);
279 : :
4329 rhaas@postgresql.org 280 : 2735 : PG_RETURN_INT64(blocks_done);
281 : : }
|