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