mirror of https://github.com/postgres/postgres
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.compull/11/head
parent
6f56b41ac0
commit
ba0a198fb1
@ -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 |
||||
@ -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; |
||||
@ -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; |
||||
} |
||||
@ -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 |
||||
@ -0,0 +1,110 @@ |
||||
<!-- doc/src/sgml/pgvisibility.sgml --> |
||||
|
||||
<sect1 id="pgvisibility" xreflabel="pg_visibility"> |
||||
<title>pg_visibility</title> |
||||
|
||||
<indexterm zone="pgvisibility"> |
||||
<primary>pg_visibility</primary> |
||||
</indexterm> |
||||
|
||||
<para> |
||||
The <filename>pg_visibility</> module provides a means for examining the |
||||
visibility map (VM) and page-level visibility information. |
||||
</para> |
||||
|
||||
<para> |
||||
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 <literal>PD_ALL_VISIBLE</literal> 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 <literal>pg_visibility</> examines |
||||
the visibility map and before it examines the data page. |
||||
</para> |
||||
|
||||
<para> |
||||
Functions which display information about <literal>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. |
||||
</para> |
||||
|
||||
<sect2> |
||||
<title>Functions</title> |
||||
|
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><function>pg_visibility_map(regclass, blkno bigint, all_visible OUT boolean, all_frozen OUT boolean) returns record</function></term> |
||||
<listitem> |
||||
<para> |
||||
Returns the all-visible and all-frozen bits in the visibility map for |
||||
the given block of the given relation. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><function>pg_visibility(regclass, blkno bigint, all_visible OUT boolean, all_frozen OUT boolean, pd_all_visible OUT boolean) returns record</function></term> |
||||
<listitem> |
||||
<para> |
||||
Returns the all-visible and all-frozen bits in the visibility map for |
||||
the given block of the given relation, plus the |
||||
<literal>PD_ALL_VISIBILE</> bit for that block. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><function>pg_visibility_map(regclass, blkno OUT bigint, all_visible OUT boolean, all_frozen OUT boolean) returns record</function></term> |
||||
<listitem> |
||||
<para> |
||||
Returns the all-visible and all-frozen bits in the visibility map for |
||||
each block the given relation. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><function>pg_visibility(regclass, blkno OUT bigint, all_visible OUT boolean, all_frozen OUT boolean, pd_all_visible OUT boolean) returns record</function></term> |
||||
|
||||
<listitem> |
||||
<para> |
||||
Returns the all-visible and all-frozen bits in the visibility map for |
||||
each block the given relation, plus the <literal>PD_ALL_VISIBLE</> |
||||
bit for each block. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><function>pg_visibility_map_summary(regclass, all_visible OUT bigint, all_frozen OUT bigint) returns record</function></term> |
||||
|
||||
<listitem> |
||||
<para> |
||||
Returns the number of all-visible pages and the number of all-frozen |
||||
pages in the relation according to the visibility map. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
|
||||
<para> |
||||
By default, these functions are not publicly executable. |
||||
</para> |
||||
</sect2> |
||||
|
||||
<sect2> |
||||
<title>Author</title> |
||||
|
||||
<para> |
||||
Robert Haas <email>rhaas@postgresql.org</email> |
||||
</para> |
||||
</sect2> |
||||
|
||||
</sect1> |
||||
Loading…
Reference in new issue