Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 71d05a2

Browse files
committed
pg_visibility: Add pg_truncate_visibility_map function.
This requires some core changes as well so that we can properly WAL-log the truncation. Specifically, it changes the format of the XLOG_SMGR_TRUNCATE WAL record, so bump XLOG_PAGE_MAGIC. Patch by me, reviewed but not fully endorsed by Andres Freund.
1 parent 54f5c51 commit 71d05a2

File tree

8 files changed

+133
-13
lines changed

8 files changed

+133
-13
lines changed

contrib/pg_visibility/pg_visibility--1.0--1.1.sql

+7
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,12 @@ RETURNS SETOF tid
1313
AS 'MODULE_PATHNAME', 'pg_check_visible'
1414
LANGUAGE C STRICT;
1515

16+
CREATE FUNCTION pg_truncate_visibility_map(regclass)
17+
RETURNS void
18+
AS 'MODULE_PATHNAME', 'pg_truncate_visibility_map'
19+
LANGUAGE C STRICT
20+
PARALLEL UNSAFE; -- let's not make this any more dangerous
21+
1622
REVOKE ALL ON FUNCTION pg_check_frozen(regclass) FROM PUBLIC;
1723
REVOKE ALL ON FUNCTION pg_check_visible(regclass) FROM PUBLIC;
24+
REVOKE ALL ON FUNCTION pg_truncate_visibility_map(regclass) FROM PUBLIC;

contrib/pg_visibility/pg_visibility--1.1.sql

+8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ RETURNS SETOF tid
5757
AS 'MODULE_PATHNAME', 'pg_check_visible'
5858
LANGUAGE C STRICT;
5959

60+
-- Truncate the visibility map fork.
61+
CREATE FUNCTION pg_truncate_visibility_map(regclass)
62+
RETURNS void
63+
AS 'MODULE_PATHNAME', 'pg_truncate_visibility_map'
64+
LANGUAGE C STRICT
65+
PARALLEL UNSAFE; -- let's not make this any more dangerous
66+
6067
-- Don't want these to be available to public.
6168
REVOKE ALL ON FUNCTION pg_visibility_map(regclass, bigint) FROM PUBLIC;
6269
REVOKE ALL ON FUNCTION pg_visibility(regclass, bigint) FROM PUBLIC;
@@ -65,3 +72,4 @@ REVOKE ALL ON FUNCTION pg_visibility(regclass) FROM PUBLIC;
6572
REVOKE ALL ON FUNCTION pg_visibility_map_summary(regclass) FROM PUBLIC;
6673
REVOKE ALL ON FUNCTION pg_check_frozen(regclass) FROM PUBLIC;
6774
REVOKE ALL ON FUNCTION pg_check_visible(regclass) FROM PUBLIC;
75+
REVOKE ALL ON FUNCTION pg_truncate_visibility_map(regclass) FROM PUBLIC;

contrib/pg_visibility/pg_visibility.c

+72
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
#include "access/htup_details.h"
1212
#include "access/visibilitymap.h"
1313
#include "catalog/pg_type.h"
14+
#include "catalog/storage_xlog.h"
1415
#include "funcapi.h"
1516
#include "miscadmin.h"
1617
#include "storage/bufmgr.h"
1718
#include "storage/procarray.h"
19+
#include "storage/smgr.h"
1820
#include "utils/rel.h"
1921

2022
PG_MODULE_MAGIC;
@@ -40,6 +42,7 @@ PG_FUNCTION_INFO_V1(pg_visibility_rel);
4042
PG_FUNCTION_INFO_V1(pg_visibility_map_summary);
4143
PG_FUNCTION_INFO_V1(pg_check_frozen);
4244
PG_FUNCTION_INFO_V1(pg_check_visible);
45+
PG_FUNCTION_INFO_V1(pg_truncate_visibility_map);
4346

4447
static TupleDesc pg_visibility_tupdesc(bool include_blkno, bool include_pd);
4548
static vbits *collect_visibility_data(Oid relid, bool include_pd);
@@ -335,6 +338,75 @@ pg_check_visible(PG_FUNCTION_ARGS)
335338
SRF_RETURN_DONE(funcctx);
336339
}
337340

341+
/*
342+
* Remove the visibility map fork for a relation. If there turn out to be
343+
* any bugs in the visibility map code that require rebuilding the VM, this
344+
* provides users with a way to do it that is cleaner than shutting down the
345+
* server and removing files by hand.
346+
*
347+
* This is a cut-down version of RelationTruncate.
348+
*/
349+
Datum
350+
pg_truncate_visibility_map(PG_FUNCTION_ARGS)
351+
{
352+
Oid relid = PG_GETARG_OID(0);
353+
Relation rel;
354+
355+
rel = relation_open(relid, AccessExclusiveLock);
356+
357+
if (rel->rd_rel->relkind != RELKIND_RELATION &&
358+
rel->rd_rel->relkind != RELKIND_MATVIEW &&
359+
rel->rd_rel->relkind != RELKIND_TOASTVALUE)
360+
ereport(ERROR,
361+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
362+
errmsg("\"%s\" is not a table, materialized view, or TOAST table",
363+
RelationGetRelationName(rel))));
364+
365+
RelationOpenSmgr(rel);
366+
rel->rd_smgr->smgr_vm_nblocks = InvalidBlockNumber;
367+
368+
visibilitymap_truncate(rel, 0);
369+
370+
if (RelationNeedsWAL(rel))
371+
{
372+
xl_smgr_truncate xlrec;
373+
374+
xlrec.blkno = 0;
375+
xlrec.rnode = rel->rd_node;
376+
xlrec.flags = SMGR_TRUNCATE_VM;
377+
378+
XLogBeginInsert();
379+
XLogRegisterData((char *) &xlrec, sizeof(xlrec));
380+
381+
XLogInsert(RM_SMGR_ID, XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
382+
}
383+
384+
/*
385+
* Release the lock right away, not at commit time.
386+
*
387+
* It would be a problem to release the lock prior to commit if this
388+
* truncate operation sends any transactional invalidation messages. Other
389+
* backends would potentially be able to lock the relation without
390+
* processing them in the window of time between when we release the lock
391+
* here and when we sent the messages at our eventual commit. However,
392+
* we're currently only sending a non-transactional smgr invalidation,
393+
* which will have been posted to shared memory immediately from within
394+
* visibilitymap_truncate. Therefore, there should be no race here.
395+
*
396+
* The reason why it's desirable to release the lock early here is because
397+
* of the possibility that someone will need to use this to blow away many
398+
* visibility map forks at once. If we can't release the lock until
399+
* commit time, the transaction doing this will accumulate
400+
* AccessExclusiveLocks on all of those relations at the same time, which
401+
* is undesirable. However, if this turns out to be unsafe we may have no
402+
* choice...
403+
*/
404+
relation_close(rel, AccessExclusiveLock);
405+
406+
/* Nothing to return. */
407+
PG_RETURN_VOID();
408+
}
409+
338410
/*
339411
* Helper function to construct whichever TupleDesc we need for a particular
340412
* call.

doc/src/sgml/pgvisibility.sgml

+24-6
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99

1010
<para>
1111
The <filename>pg_visibility</> module provides a means for examining the
12-
visibility map (VM) and page-level visibility information.
12+
visibility map (VM) and page-level visibility information. It also
13+
provides functions to check the integrity of the visibility map and to
14+
force it to be rebuilt.
1315
</para>
1416

1517
<para>
16-
These routines return information about three different bits. The
17-
all-visible bit in the visibility map indicates that every tuple on
18-
a given page of a relation is visible to every current transaction. The
19-
all-frozen bit in the visibility map indicates that every tuple on the
18+
Three different bits are used to store information about page-level
19+
visibility. The all-visible bit in the visibility map indicates that every
20+
tuple on a given page of a relation is visible to every current transaction.
21+
The all-frozen bit in the visibility map indicates that every tuple on the
2022
page is frozen; that is, no future vacuum will need to modify the page
2123
until such time as a tuple is inserted, updated, deleted, or locked on
2224
that page. The page-level <literal>PD_ALL_VISIBLE</literal> bit has the
@@ -25,7 +27,8 @@
2527
will normally agree, but the page-level bit can sometimes be set while the
2628
visibility map bit is clear after a crash recovery; or they can disagree
2729
because of a change which occurs after <literal>pg_visibility</> examines
28-
the visibility map and before it examines the data page.
30+
the visibility map and before it examines the data page. Any event which
31+
causes data corruption can also cause these bits to disagree.
2932
</para>
3033

3134
<para>
@@ -118,6 +121,21 @@
118121
</para>
119122
</listitem>
120123
</varlistentry>
124+
125+
<varlistentry>
126+
<term><function>pg_truncate_visibility_map(regclass) returns void</function></term>
127+
128+
<listitem>
129+
<para>
130+
Truncates the visibility map for the given relation. This function
131+
is only expected to be useful if you suspect that the visibility map
132+
for the indicated relation is corrupt and wish to rebuild it. The first
133+
<command>VACUUM</> executed on the given relation after this function
134+
is executed will scan every page in the relation and rebuild the
135+
visibility map.
136+
</para>
137+
</listitem>
138+
</varlistentry>
121139
</variablelist>
122140

123141
<para>

src/backend/access/rmgrdesc/smgrdesc.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ smgr_desc(StringInfo buf, XLogReaderState *record)
3737
xl_smgr_truncate *xlrec = (xl_smgr_truncate *) rec;
3838
char *path = relpathperm(xlrec->rnode, MAIN_FORKNUM);
3939

40-
appendStringInfo(buf, "%s to %u blocks", path, xlrec->blkno);
40+
appendStringInfo(buf, "%s to %u blocks flags %d", path,
41+
xlrec->blkno, xlrec->flags);
4142
pfree(path);
4243
}
4344
}

src/backend/catalog/storage.c

+11-5
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
268268

269269
xlrec.blkno = nblocks;
270270
xlrec.rnode = rel->rd_node;
271+
xlrec.flags = SMGR_TRUNCATE_ALL;
271272

272273
XLogBeginInsert();
273274
XLogRegisterData((char *) &xlrec, sizeof(xlrec));
@@ -522,17 +523,22 @@ smgr_redo(XLogReaderState *record)
522523
*/
523524
XLogFlush(lsn);
524525

525-
smgrtruncate(reln, MAIN_FORKNUM, xlrec->blkno);
526+
if ((xlrec->flags & SMGR_TRUNCATE_HEAP) != 0)
527+
{
528+
smgrtruncate(reln, MAIN_FORKNUM, xlrec->blkno);
526529

527-
/* Also tell xlogutils.c about it */
528-
XLogTruncateRelation(xlrec->rnode, MAIN_FORKNUM, xlrec->blkno);
530+
/* Also tell xlogutils.c about it */
531+
XLogTruncateRelation(xlrec->rnode, MAIN_FORKNUM, xlrec->blkno);
532+
}
529533

530534
/* Truncate FSM and VM too */
531535
rel = CreateFakeRelcacheEntry(xlrec->rnode);
532536

533-
if (smgrexists(reln, FSM_FORKNUM))
537+
if ((xlrec->flags & SMGR_TRUNCATE_FSM) != 0 &&
538+
smgrexists(reln, FSM_FORKNUM))
534539
FreeSpaceMapTruncateRel(rel, xlrec->blkno);
535-
if (smgrexists(reln, VISIBILITYMAP_FORKNUM))
540+
if ((xlrec->flags & SMGR_TRUNCATE_VM) != 0 &&
541+
smgrexists(reln, VISIBILITYMAP_FORKNUM))
536542
visibilitymap_truncate(rel, xlrec->blkno);
537543

538544
FreeFakeRelcacheEntry(rel);

src/include/access/xlog_internal.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
/*
3232
* Each page of XLOG file has a header like this:
3333
*/
34-
#define XLOG_PAGE_MAGIC 0xD091 /* can be used as WAL version indicator */
34+
#define XLOG_PAGE_MAGIC 0xD092 /* can be used as WAL version indicator */
3535

3636
typedef struct XLogPageHeaderData
3737
{

src/include/catalog/storage_xlog.h

+8
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,18 @@ typedef struct xl_smgr_create
3636
ForkNumber forkNum;
3737
} xl_smgr_create;
3838

39+
/* flags for xl_smgr_truncate */
40+
#define SMGR_TRUNCATE_HEAP 0x0001
41+
#define SMGR_TRUNCATE_VM 0x0002
42+
#define SMGR_TRUNCATE_FSM 0x0004
43+
#define SMGR_TRUNCATE_ALL \
44+
(SMGR_TRUNCATE_HEAP|SMGR_TRUNCATE_VM|SMGR_TRUNCATE_FSM)
45+
3946
typedef struct xl_smgr_truncate
4047
{
4148
BlockNumber blkno;
4249
RelFileNode rnode;
50+
int flags;
4351
} xl_smgr_truncate;
4452

4553
extern void log_smgrcreate(RelFileNode *rnode, ForkNumber forkNum);

0 commit comments

Comments
 (0)