mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-22 14:53:23 +02:00
This is a back-patch of the v15-era commitf10f0ae42
into older supported branches. The idea is to design out bugs in which an ill-timed relcache flush clears rel->rd_smgr partway through some code sequence that wasn't expecting that. We had another report today of a corner case that reliably crashes v14 under debug_discard_caches (nee CLOBBER_CACHE_ALWAYS), and therefore would crash once in a blue moon in the field. We're unlikely to get rid of all such code paths unless we adopt the more rigorous coding rules instituted byf10f0ae42
. Therefore, even though this is a bit invasive, it's time to back-patch. Some comfort can be taken in the fact thatf10f0ae42
has been in v15 for 16 months without problems. I left the RelationOpenSmgr macro present in the back branches, even though no core code should use it anymore, in order to not break third-party extensions in minor releases. Such extensions might opt to start using RelationGetSmgr instead, to reduce their code differential between v15 and earlier branches. This carries a hazard of failing to compile against headers from existing minor releases. However, once compiled the extension should work fine even with such releases, because RelationGetSmgr is a "static inline" function so it creates no link-time dependency. So depending on distribution practices, that might be an OK tradeoff. Per report from Spyridon Dimitrios Agathos. Original patch by Amul Sul. Discussion: https://postgr.es/m/CAFM5RaqdgyusQvmWkyPYaWMwoK5gigdtW-7HcgHgOeAw7mqJ_Q@mail.gmail.com Discussion: https://postgr.es/m/CANiYTQsU7yMFpQYnv=BrcRVqK_3U3mtAzAsJCaqtzsDHfsUbdQ@mail.gmail.com
205 lines
5.6 KiB
C
205 lines
5.6 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pg_prewarm.c
|
|
* prewarming utilities
|
|
*
|
|
* Copyright (c) 2010-2021, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* contrib/pg_prewarm/pg_prewarm.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "access/relation.h"
|
|
#include "fmgr.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/bufmgr.h"
|
|
#include "storage/smgr.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
PG_FUNCTION_INFO_V1(pg_prewarm);
|
|
|
|
typedef enum
|
|
{
|
|
PREWARM_PREFETCH,
|
|
PREWARM_READ,
|
|
PREWARM_BUFFER
|
|
} PrewarmType;
|
|
|
|
static PGAlignedBlock blockbuffer;
|
|
|
|
/*
|
|
* pg_prewarm(regclass, mode text, fork text,
|
|
* first_block int8, last_block int8)
|
|
*
|
|
* The first argument is the relation to be prewarmed; the second controls
|
|
* how prewarming is done; legal options are 'prefetch', 'read', and 'buffer'.
|
|
* The third is the name of the relation fork to be prewarmed. The fourth
|
|
* and fifth arguments specify the first and last block to be prewarmed.
|
|
* If the fourth argument is NULL, it will be taken as 0; if the fifth argument
|
|
* is NULL, it will be taken as the number of blocks in the relation. The
|
|
* return value is the number of blocks successfully prewarmed.
|
|
*/
|
|
Datum
|
|
pg_prewarm(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid relOid;
|
|
text *forkName;
|
|
text *type;
|
|
int64 first_block;
|
|
int64 last_block;
|
|
int64 nblocks;
|
|
int64 blocks_done = 0;
|
|
int64 block;
|
|
Relation rel;
|
|
ForkNumber forkNumber;
|
|
char *forkString;
|
|
char *ttype;
|
|
PrewarmType ptype;
|
|
AclResult aclresult;
|
|
|
|
/* Basic sanity checking. */
|
|
if (PG_ARGISNULL(0))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("relation cannot be null")));
|
|
relOid = PG_GETARG_OID(0);
|
|
if (PG_ARGISNULL(1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("prewarm type cannot be null")));
|
|
type = PG_GETARG_TEXT_PP(1);
|
|
ttype = text_to_cstring(type);
|
|
if (strcmp(ttype, "prefetch") == 0)
|
|
ptype = PREWARM_PREFETCH;
|
|
else if (strcmp(ttype, "read") == 0)
|
|
ptype = PREWARM_READ;
|
|
else if (strcmp(ttype, "buffer") == 0)
|
|
ptype = PREWARM_BUFFER;
|
|
else
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid prewarm type"),
|
|
errhint("Valid prewarm types are \"prefetch\", \"read\", and \"buffer\".")));
|
|
PG_RETURN_INT64(0); /* Placate compiler. */
|
|
}
|
|
if (PG_ARGISNULL(2))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("relation fork cannot be null")));
|
|
forkName = PG_GETARG_TEXT_PP(2);
|
|
forkString = text_to_cstring(forkName);
|
|
forkNumber = forkname_to_number(forkString);
|
|
|
|
/* Open relation and check privileges. */
|
|
rel = relation_open(relOid, AccessShareLock);
|
|
aclresult = pg_class_aclcheck(relOid, GetUserId(), ACL_SELECT);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid));
|
|
|
|
/* Check that the fork exists. */
|
|
if (!smgrexists(RelationGetSmgr(rel), forkNumber))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("fork \"%s\" does not exist for this relation",
|
|
forkString)));
|
|
|
|
/* Validate block numbers, or handle nulls. */
|
|
nblocks = RelationGetNumberOfBlocksInFork(rel, forkNumber);
|
|
if (PG_ARGISNULL(3))
|
|
first_block = 0;
|
|
else
|
|
{
|
|
first_block = PG_GETARG_INT64(3);
|
|
if (first_block < 0 || first_block >= nblocks)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("starting block number must be between 0 and %lld",
|
|
(long long) (nblocks - 1))));
|
|
}
|
|
if (PG_ARGISNULL(4))
|
|
last_block = nblocks - 1;
|
|
else
|
|
{
|
|
last_block = PG_GETARG_INT64(4);
|
|
if (last_block < 0 || last_block >= nblocks)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("ending block number must be between 0 and %lld",
|
|
(long long) (nblocks - 1))));
|
|
}
|
|
|
|
/* Now we're ready to do the real work. */
|
|
if (ptype == PREWARM_PREFETCH)
|
|
{
|
|
#ifdef USE_PREFETCH
|
|
|
|
/*
|
|
* In prefetch mode, we just hint the OS to read the blocks, but we
|
|
* don't know whether it really does it, and we don't wait for it to
|
|
* finish.
|
|
*
|
|
* It would probably be better to pass our prefetch requests in chunks
|
|
* of a megabyte or maybe even a whole segment at a time, but there's
|
|
* no practical way to do that at present without a gross modularity
|
|
* violation, so we just do this.
|
|
*/
|
|
for (block = first_block; block <= last_block; ++block)
|
|
{
|
|
CHECK_FOR_INTERRUPTS();
|
|
PrefetchBuffer(rel, forkNumber, block);
|
|
++blocks_done;
|
|
}
|
|
#else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("prefetch is not supported by this build")));
|
|
#endif
|
|
}
|
|
else if (ptype == PREWARM_READ)
|
|
{
|
|
/*
|
|
* In read mode, we actually read the blocks, but not into shared
|
|
* buffers. This is more portable than prefetch mode (it works
|
|
* everywhere) and is synchronous.
|
|
*/
|
|
for (block = first_block; block <= last_block; ++block)
|
|
{
|
|
CHECK_FOR_INTERRUPTS();
|
|
smgrread(RelationGetSmgr(rel), forkNumber, block, blockbuffer.data);
|
|
++blocks_done;
|
|
}
|
|
}
|
|
else if (ptype == PREWARM_BUFFER)
|
|
{
|
|
/*
|
|
* In buffer mode, we actually pull the data into shared_buffers.
|
|
*/
|
|
for (block = first_block; block <= last_block; ++block)
|
|
{
|
|
Buffer buf;
|
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
buf = ReadBufferExtended(rel, forkNumber, block, RBM_NORMAL, NULL);
|
|
ReleaseBuffer(buf);
|
|
++blocks_done;
|
|
}
|
|
}
|
|
|
|
/* Close relation, release lock. */
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
PG_RETURN_INT64(blocks_done);
|
|
}
|