From: Robert Haas Date: Tue, 8 Mar 2016 13:38:50 +0000 (-0500) Subject: Add pg_visibility contrib module. X-Git-Tag: REL9_6_BETA1~572 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ba0a198fb133eb3426bffdc2e369cce1bafe1612;p=postgresql Add pg_visibility contrib module. This lets you examine the visibility map as well as page-level visibility information. I initially wrote it as a debugging aid, but was encouraged to polish it for commit. Patch by me, reviewed by Masahiko Sawada. Discussion: 56D77803.6080503@BlueTreble.com --- diff --git a/contrib/Makefile b/contrib/Makefile index bd251f6c66..d12dd6379b 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -37,6 +37,7 @@ SUBDIRS = \ pgcrypto \ pgrowlocks \ pgstattuple \ + pg_visibility \ postgres_fdw \ seg \ spi \ diff --git a/contrib/pg_visibility/Makefile b/contrib/pg_visibility/Makefile new file mode 100644 index 0000000000..fbbaa2e512 --- /dev/null +++ b/contrib/pg_visibility/Makefile @@ -0,0 +1,19 @@ +# contrib/pg_visibility/Makefile + +MODULE_big = pg_visibility +OBJS = pg_visibility.o $(WIN32RES) + +EXTENSION = pg_visibility +DATA = pg_visibility--1.0.sql +PGFILEDESC = "pg_visibility - page visibility information" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_visibility +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_visibility/pg_visibility--1.0.sql b/contrib/pg_visibility/pg_visibility--1.0.sql new file mode 100644 index 0000000000..da511e5be9 --- /dev/null +++ b/contrib/pg_visibility/pg_visibility--1.0.sql @@ -0,0 +1,52 @@ +/* contrib/pg_visibility/pg_visibility--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_visibility" to load this file. \quit + +-- Show visibility map information. +CREATE FUNCTION pg_visibility_map(regclass, blkno bigint, + all_visible OUT boolean, + all_frozen OUT boolean) +RETURNS record +AS 'MODULE_PATHNAME', 'pg_visibility_map' +LANGUAGE C STRICT; + +-- Show visibility map and page-level visibility information. +CREATE FUNCTION pg_visibility(regclass, blkno bigint, + all_visible OUT boolean, + all_frozen OUT boolean, + pd_all_visible OUT boolean) +RETURNS record +AS 'MODULE_PATHNAME', 'pg_visibility' +LANGUAGE C STRICT; + +-- Show visibility map information for each block in a relation. +CREATE FUNCTION pg_visibility_map(regclass, blkno OUT bigint, + all_visible OUT boolean, + all_frozen OUT boolean) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_visibility_map_rel' +LANGUAGE C STRICT; + +-- Show visibility map and page-level visibility information for each block. +CREATE FUNCTION pg_visibility(regclass, blkno OUT bigint, + all_visible OUT boolean, + all_frozen OUT boolean, + pd_all_visible OUT boolean) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_visibility_rel' +LANGUAGE C STRICT; + +-- Show summary of visibility map bits for a relation. +CREATE FUNCTION pg_visibility_map_summary(regclass, + OUT all_visible bigint, OUT all_frozen bigint) +RETURNS record +AS 'MODULE_PATHNAME', 'pg_visibility_map_summary' +LANGUAGE C STRICT; + +-- Don't want these to be available to public. +REVOKE ALL ON FUNCTION pg_visibility_map(regclass, bigint) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility(regclass, bigint) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility_map(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility_map_summary(regclass) FROM PUBLIC; diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c new file mode 100644 index 0000000000..5e5c7cce24 --- /dev/null +++ b/contrib/pg_visibility/pg_visibility.c @@ -0,0 +1,350 @@ +/*------------------------------------------------------------------------- + * + * pg_visibility.c + * display visibility map information and page-level visibility bits + * + * contrib/pg_visibility/pg_visibility.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/visibilitymap.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +typedef struct vbits +{ + BlockNumber next; + BlockNumber count; + uint8 bits[FLEXIBLE_ARRAY_MEMBER]; +} vbits; + +PG_FUNCTION_INFO_V1(pg_visibility_map); +PG_FUNCTION_INFO_V1(pg_visibility_map_rel); +PG_FUNCTION_INFO_V1(pg_visibility); +PG_FUNCTION_INFO_V1(pg_visibility_rel); +PG_FUNCTION_INFO_V1(pg_visibility_map_summary); + +static TupleDesc pg_visibility_tupdesc(bool include_blkno, bool include_pd); +static vbits *collect_visibility_data(Oid relid, bool include_pd); + +/* + * Visibility map information for a single block of a relation. + */ +Datum +pg_visibility_map(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 blkno = PG_GETARG_INT64(1); + int32 mapbits; + Relation rel; + Buffer vmbuffer = InvalidBuffer; + TupleDesc tupdesc; + Datum values[2]; + bool nulls[2]; + + rel = relation_open(relid, AccessShareLock); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + tupdesc = pg_visibility_tupdesc(false, false); + MemSet(nulls, 0, sizeof(nulls)); + + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0); + values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0); + + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Visibility map information for a single block of a relation, plus the + * page-level information for the same block. + */ +Datum +pg_visibility(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 blkno = PG_GETARG_INT64(1); + int32 mapbits; + Relation rel; + Buffer vmbuffer = InvalidBuffer; + Buffer buffer; + Page page; + TupleDesc tupdesc; + Datum values[3]; + bool nulls[3]; + + rel = relation_open(relid, AccessShareLock); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + tupdesc = pg_visibility_tupdesc(false, true); + MemSet(nulls, 0, sizeof(nulls)); + + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0); + values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0); + + buffer = ReadBuffer(rel, blkno); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + values[2] = BoolGetDatum(PageIsAllVisible(page)); + + UnlockReleaseBuffer(buffer); + + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Visibility map information for every block in a relation. + */ +Datum +pg_visibility_map_rel(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + vbits *info; + + if (SRF_IS_FIRSTCALL()) + { + Oid relid = PG_GETARG_OID(0); + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + funcctx->tuple_desc = pg_visibility_tupdesc(true, false); + funcctx->user_fctx = collect_visibility_data(relid, false); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + info = (vbits *) funcctx->user_fctx; + + if (info->next < info->count) + { + Datum values[3]; + bool nulls[3]; + HeapTuple tuple; + + MemSet(nulls, 0, sizeof(nulls)); + values[0] = Int64GetDatum(info->next); + values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0); + values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0); + info->next++; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Visibility map information for every block in a relation, plus the page + * level information for each block. + */ +Datum +pg_visibility_rel(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + vbits *info; + + if (SRF_IS_FIRSTCALL()) + { + Oid relid = PG_GETARG_OID(0); + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + funcctx->tuple_desc = pg_visibility_tupdesc(true, true); + funcctx->user_fctx = collect_visibility_data(relid, true); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + info = (vbits *) funcctx->user_fctx; + + if (info->next < info->count) + { + Datum values[4]; + bool nulls[4]; + HeapTuple tuple; + + MemSet(nulls, 0, sizeof(nulls)); + values[0] = Int64GetDatum(info->next); + values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0); + values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0); + values[3] = BoolGetDatum((info->bits[info->next] & (1 << 2)) != 0); + info->next++; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Count the number of all-visible and all-frozen pages in the visibility + * map for a particular relation. + */ +Datum +pg_visibility_map_summary(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + BlockNumber nblocks; + BlockNumber blkno; + Buffer vmbuffer = InvalidBuffer; + int64 all_visible = 0; + int64 all_frozen = 0; + TupleDesc tupdesc; + Datum values[2]; + bool nulls[2]; + + rel = relation_open(relid, AccessShareLock); + nblocks = RelationGetNumberOfBlocks(rel); + + for (blkno = 0; blkno < nblocks; ++blkno) + { + int32 mapbits; + + /* Make sure we are interruptible. */ + CHECK_FOR_INTERRUPTS(); + + /* Get map info. */ + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0) + ++all_visible; + if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0) + ++all_frozen; + } + + /* Clean up. */ + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + relation_close(rel, AccessShareLock); + + tupdesc = CreateTemplateTupleDesc(2, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "all_visible", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "all_frozen", INT8OID, -1, 0); + tupdesc = BlessTupleDesc(tupdesc); + + MemSet(nulls, 0, sizeof(nulls)); + values[0] = Int64GetDatum(all_visible); + values[1] = Int64GetDatum(all_frozen); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Helper function to construct whichever TupleDesc we need for a particular + * call. + */ +static TupleDesc +pg_visibility_tupdesc(bool include_blkno, bool include_pd) +{ + TupleDesc tupdesc; + AttrNumber maxattr = 2; + AttrNumber a = 0; + + if (include_blkno) + ++maxattr; + if (include_pd) + ++maxattr; + tupdesc = CreateTemplateTupleDesc(maxattr, false); + if (include_blkno) + TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "all_visible", BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "all_frozen", BOOLOID, -1, 0); + if (include_pd) + TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1, 0); + Assert(a == maxattr); + + return BlessTupleDesc(tupdesc); +} + +/* + * Collect visibility data about a relation. + */ +static vbits * +collect_visibility_data(Oid relid, bool include_pd) +{ + Relation rel; + BlockNumber nblocks; + vbits *info; + BlockNumber blkno; + Buffer vmbuffer = InvalidBuffer; + BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD); + + rel = relation_open(relid, AccessShareLock); + + nblocks = RelationGetNumberOfBlocks(rel); + info = palloc0(offsetof(vbits, bits) + nblocks); + info->next = 0; + info->count = nblocks; + + for (blkno = 0; blkno < nblocks; ++blkno) + { + int32 mapbits; + + /* Make sure we are interruptible. */ + CHECK_FOR_INTERRUPTS(); + + /* Get map info. */ + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0) + info->bits[blkno] |= (1 << 0); + if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0) + info->bits[blkno] |= (1 << 1); + + /* + * Page-level data requires reading every block, so only get it if + * the caller needs it. Use a buffer access strategy, too, to prevent + * cache-trashing. + */ + if (include_pd) + { + Buffer buffer; + Page page; + + buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, + bstrategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + if (PageIsAllVisible(page)) + info->bits[blkno] |= (1 << 2); + + UnlockReleaseBuffer(buffer); + } + } + + /* Clean up. */ + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + relation_close(rel, AccessShareLock); + + return info; +} diff --git a/contrib/pg_visibility/pg_visibility.control b/contrib/pg_visibility/pg_visibility.control new file mode 100644 index 0000000000..1d7185351e --- /dev/null +++ b/contrib/pg_visibility/pg_visibility.control @@ -0,0 +1,5 @@ +# pg_visibility extension +comment = 'examine the visibility map (VM) and page-level visibility info' +default_version = '1.0' +module_pathname = '$libdir/pg_visibility' +relocatable = true diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 1b3d2d93c7..4e3f337125 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -132,6 +132,7 @@ CREATE EXTENSION module_name FROM unpackaged; &pgstatstatements; &pgstattuple; &pgtrgm; + &pgvisibility; &postgres-fdw; &seg; &sepgsql; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index a12fee73db..30adecee36 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -136,6 +136,7 @@ + diff --git a/doc/src/sgml/pgvisibility.sgml b/doc/src/sgml/pgvisibility.sgml new file mode 100644 index 0000000000..6a98b55949 --- /dev/null +++ b/doc/src/sgml/pgvisibility.sgml @@ -0,0 +1,110 @@ + + + + pg_visibility + + + pg_visibility + + + + The pg_visibility module provides a means for examining the + visibility map (VM) and page-level visibility information. + + + + These routines return information about three different bits. The + all-visible bit in the visibility map indicates that every tuple on + a given page of a relation is visible to every current transaction. The + all-frozen bit in the visibility map indicates that every tuple on the + page is frozen; that is, no future vacuum will need to modify the page + until such time as a tuple is inserted, updated, deleted, or locked on + that page. The page-level PD_ALL_VISIBLE bit has the + same meaning as the all-visible bit in the visibility map, but is stored + within the data page itself rather than a separate data tructure. These + will normally agree, but the page-level bit can sometimes be set while the + visibility map bit is clear after a crash recovery; or they can disagree + because of a change which occurs after pg_visibility examines + the visibility map and before it examines the data page. + + + + Functions which display information about PG_ALL_VISIBLE + are much more costly than those which only consult the visibility map, + because they must read the relation's data blocks rather than only the + (much smaller) visibility map. + + + + Functions + + + + pg_visibility_map(regclass, blkno bigint, all_visible OUT boolean, all_frozen OUT boolean) returns record + + + Returns the all-visible and all-frozen bits in the visibility map for + the given block of the given relation. + + + + + + pg_visibility(regclass, blkno bigint, all_visible OUT boolean, all_frozen OUT boolean, pd_all_visible OUT boolean) returns record + + + Returns the all-visible and all-frozen bits in the visibility map for + the given block of the given relation, plus the + PD_ALL_VISIBILE bit for that block. + + + + + + pg_visibility_map(regclass, blkno OUT bigint, all_visible OUT boolean, all_frozen OUT boolean) returns record + + + Returns the all-visible and all-frozen bits in the visibility map for + each block the given relation. + + + + + + pg_visibility(regclass, blkno OUT bigint, all_visible OUT boolean, all_frozen OUT boolean, pd_all_visible OUT boolean) returns record + + + + Returns the all-visible and all-frozen bits in the visibility map for + each block the given relation, plus the PD_ALL_VISIBLE + bit for each block. + + + + + + pg_visibility_map_summary(regclass, all_visible OUT bigint, all_frozen OUT bigint) returns record + + + + Returns the number of all-visible pages and the number of all-frozen + pages in the relation according to the visibility map. + + + + + + + By default, these functions are not publicly executable. + + + + + Author + + + Robert Haas rhaas@postgresql.org + + + + diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml index e2be43e63d..9b2e09e385 100644 --- a/doc/src/sgml/storage.sgml +++ b/doc/src/sgml/storage.sgml @@ -648,6 +648,11 @@ might not be true. Visibility map bits are only set by vacuum, but are cleared by any data-modifying operations on a page. + +The module can be used to examine the +information stored in the visibility map. + +