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 "fmgr.h"
20 : #include "miscadmin.h"
21 : #include "storage/bufmgr.h"
22 : #include "storage/read_stream.h"
23 : #include "storage/smgr.h"
24 : #include "utils/acl.h"
25 : #include "utils/builtins.h"
26 : #include "utils/lsyscache.h"
27 : #include "utils/rel.h"
28 :
29 12 : PG_MODULE_MAGIC_EXT(
30 : .name = "pg_prewarm",
31 : .version = PG_VERSION
32 : );
33 :
34 18 : PG_FUNCTION_INFO_V1(pg_prewarm);
35 :
36 : typedef enum
37 : {
38 : PREWARM_PREFETCH,
39 : PREWARM_READ,
40 : PREWARM_BUFFER,
41 : } PrewarmType;
42 :
43 : static PGIOAlignedBlock blockbuffer;
44 :
45 : /*
46 : * pg_prewarm(regclass, mode text, fork text,
47 : * first_block int8, last_block int8)
48 : *
49 : * The first argument is the relation to be prewarmed; the second controls
50 : * how prewarming is done; legal options are 'prefetch', 'read', and 'buffer'.
51 : * The third is the name of the relation fork to be prewarmed. The fourth
52 : * and fifth arguments specify the first and last block to be prewarmed.
53 : * If the fourth argument is NULL, it will be taken as 0; if the fifth argument
54 : * is NULL, it will be taken as the number of blocks in the relation. The
55 : * return value is the number of blocks successfully prewarmed.
56 : */
57 : Datum
58 5468 : pg_prewarm(PG_FUNCTION_ARGS)
59 : {
60 : Oid relOid;
61 : text *forkName;
62 : text *type;
63 : int64 first_block;
64 : int64 last_block;
65 : int64 nblocks;
66 5468 : int64 blocks_done = 0;
67 : int64 block;
68 : Relation rel;
69 : ForkNumber forkNumber;
70 : char *forkString;
71 : char *ttype;
72 : PrewarmType ptype;
73 : AclResult aclresult;
74 :
75 : /* Basic sanity checking. */
76 5468 : if (PG_ARGISNULL(0))
77 0 : ereport(ERROR,
78 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
79 : errmsg("relation cannot be null")));
80 5468 : relOid = PG_GETARG_OID(0);
81 5468 : if (PG_ARGISNULL(1))
82 0 : ereport(ERROR,
83 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
84 : errmsg("prewarm type cannot be null")));
85 5468 : type = PG_GETARG_TEXT_PP(1);
86 5468 : ttype = text_to_cstring(type);
87 5468 : if (strcmp(ttype, "prefetch") == 0)
88 2 : ptype = PREWARM_PREFETCH;
89 5466 : else if (strcmp(ttype, "read") == 0)
90 2 : ptype = PREWARM_READ;
91 5464 : else if (strcmp(ttype, "buffer") == 0)
92 5464 : ptype = PREWARM_BUFFER;
93 : else
94 : {
95 0 : ereport(ERROR,
96 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
97 : errmsg("invalid prewarm type"),
98 : errhint("Valid prewarm types are \"prefetch\", \"read\", and \"buffer\".")));
99 : PG_RETURN_INT64(0); /* Placate compiler. */
100 : }
101 5468 : if (PG_ARGISNULL(2))
102 0 : ereport(ERROR,
103 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
104 : errmsg("relation fork cannot be null")));
105 5468 : forkName = PG_GETARG_TEXT_PP(2);
106 5468 : forkString = text_to_cstring(forkName);
107 5468 : forkNumber = forkname_to_number(forkString);
108 :
109 : /* Open relation and check privileges. */
110 5468 : rel = relation_open(relOid, AccessShareLock);
111 5468 : aclresult = pg_class_aclcheck(relOid, GetUserId(), ACL_SELECT);
112 5468 : if (aclresult != ACLCHECK_OK)
113 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid));
114 :
115 : /* Check that the relation has storage. */
116 5468 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
117 2 : ereport(ERROR,
118 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
119 : errmsg("relation \"%s\" does not have storage",
120 : RelationGetRelationName(rel)),
121 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
122 :
123 : /* Check that the fork exists. */
124 5466 : if (!smgrexists(RelationGetSmgr(rel), forkNumber))
125 0 : ereport(ERROR,
126 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
127 : errmsg("fork \"%s\" does not exist for this relation",
128 : forkString)));
129 :
130 : /* Validate block numbers, or handle nulls. */
131 5466 : nblocks = RelationGetNumberOfBlocksInFork(rel, forkNumber);
132 5466 : if (PG_ARGISNULL(3))
133 5466 : first_block = 0;
134 : else
135 : {
136 0 : first_block = PG_GETARG_INT64(3);
137 0 : if (first_block < 0 || first_block >= nblocks)
138 0 : ereport(ERROR,
139 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
140 : errmsg("starting block number must be between 0 and %" PRId64,
141 : (nblocks - 1))));
142 : }
143 5466 : if (PG_ARGISNULL(4))
144 5466 : last_block = nblocks - 1;
145 : else
146 : {
147 0 : last_block = PG_GETARG_INT64(4);
148 0 : if (last_block < 0 || last_block >= nblocks)
149 0 : ereport(ERROR,
150 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
151 : errmsg("ending block number must be between 0 and %" PRId64,
152 : (nblocks - 1))));
153 : }
154 :
155 : /* Now we're ready to do the real work. */
156 5466 : if (ptype == PREWARM_PREFETCH)
157 : {
158 : #ifdef USE_PREFETCH
159 :
160 : /*
161 : * In prefetch mode, we just hint the OS to read the blocks, but we
162 : * don't know whether it really does it, and we don't wait for it to
163 : * finish.
164 : *
165 : * It would probably be better to pass our prefetch requests in chunks
166 : * of a megabyte or maybe even a whole segment at a time, but there's
167 : * no practical way to do that at present without a gross modularity
168 : * violation, so we just do this.
169 : */
170 4 : for (block = first_block; block <= last_block; ++block)
171 : {
172 2 : CHECK_FOR_INTERRUPTS();
173 2 : PrefetchBuffer(rel, forkNumber, block);
174 2 : ++blocks_done;
175 : }
176 : #else
177 : ereport(ERROR,
178 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
179 : errmsg("prefetch is not supported by this build")));
180 : #endif
181 : }
182 5464 : else if (ptype == PREWARM_READ)
183 : {
184 : /*
185 : * In read mode, we actually read the blocks, but not into shared
186 : * buffers. This is more portable than prefetch mode (it works
187 : * everywhere) and is synchronous.
188 : */
189 4 : for (block = first_block; block <= last_block; ++block)
190 : {
191 2 : CHECK_FOR_INTERRUPTS();
192 2 : smgrread(RelationGetSmgr(rel), forkNumber, block, blockbuffer.data);
193 2 : ++blocks_done;
194 : }
195 : }
196 5462 : else if (ptype == PREWARM_BUFFER)
197 : {
198 : BlockRangeReadStreamPrivate p;
199 : ReadStream *stream;
200 :
201 : /*
202 : * In buffer mode, we actually pull the data into shared_buffers.
203 : */
204 :
205 : /* Set up the private state for our streaming buffer read callback. */
206 5462 : p.current_blocknum = first_block;
207 5462 : p.last_exclusive = last_block + 1;
208 :
209 : /*
210 : * It is safe to use batchmode as block_range_read_stream_cb takes no
211 : * locks.
212 : */
213 5462 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
214 : READ_STREAM_FULL |
215 : READ_STREAM_USE_BATCHING,
216 : NULL,
217 : rel,
218 : forkNumber,
219 : block_range_read_stream_cb,
220 : &p,
221 : 0);
222 :
223 24084 : for (block = first_block; block <= last_block; ++block)
224 : {
225 : Buffer buf;
226 :
227 18622 : CHECK_FOR_INTERRUPTS();
228 18622 : buf = read_stream_next_buffer(stream, NULL);
229 18622 : ReleaseBuffer(buf);
230 18622 : ++blocks_done;
231 : }
232 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
233 5462 : read_stream_end(stream);
234 : }
235 :
236 : /* Close relation, release lock. */
237 5466 : relation_close(rel, AccessShareLock);
238 :
239 5466 : PG_RETURN_INT64(blocks_done);
240 : }
|