pgstattuple-relation_free_space.patch
text/x-patch
Filename: pgstattuple-relation_free_space.patch
Type: text/x-patch
Part: 1
Message:
Re: Measuring relation free space
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| contrib/pgstattuple/Makefile | 0 | 0 |
| contrib/pgstattuple/pgstattuple--1.0--1.1.sql | 0 | 0 |
| contrib/pgstattuple/pgstattuple--1.1.sql | 0 | 0 |
| contrib/pgstattuple/pgstattuple.c | 0 | 0 |
| contrib/pgstattuple/pgstattuple.control | 0 | 0 |
| doc/src/sgml/pgstattuple.sgml | 0 | 0 |
diff --git a/contrib/pgstattuple/Makefile b/contrib/pgstattuple/Makefile
index 6ac2775..75c4405 100644
*** a/contrib/pgstattuple/Makefile
--- b/contrib/pgstattuple/Makefile
*************** MODULE_big = pgstattuple
*** 4,10 ****
OBJS = pgstattuple.o pgstatindex.o
EXTENSION = pgstattuple
! DATA = pgstattuple--1.0.sql pgstattuple--unpackaged--1.0.sql
REGRESS = pgstattuple
--- 4,13 ----
OBJS = pgstattuple.o pgstatindex.o
EXTENSION = pgstattuple
! DATA = pgstattuple--1.0.sql pgstattuple--1.1.sql \
! pgstattuple--1.0--1.1.sql \
! pgstattuple--unpackaged--1.0.sql
!
REGRESS = pgstattuple
diff --git a/contrib/pgstattuple/pgstattuple--1.0--1.1.sql b/contrib/pgstattuple/pgstattuple--1.0--1.1.sql
index ...d7ad6ca .
*** a/contrib/pgstattuple/pgstattuple--1.0--1.1.sql
--- b/contrib/pgstattuple/pgstattuple--1.0--1.1.sql
***************
*** 0 ****
--- 1,12 ----
+ /* contrib/pgstattuple/pgstattuple--1.0--1.1.sql */
+
+ -- complain if script is sourced in psql, rather than via CREATE EXTENSION
+ \echo Use "ALTER EXTENSION pgstattuple UPDATE TO '1.1'" to load this file. \quit
+
+ --
+ -- relation_free_space()
+ --
+ CREATE FUNCTION relation_free_space(IN relname text)
+ RETURNS real
+ AS 'MODULE_PATHNAME', 'relation_free_space'
+ LANGUAGE C STRICT;
diff --git a/contrib/pgstattuple/pgstattuple--1.1.sql b/contrib/pgstattuple/pgstattuple--1.1.sql
index ...d94c20f .
*** a/contrib/pgstattuple/pgstattuple--1.1.sql
--- b/contrib/pgstattuple/pgstattuple--1.1.sql
***************
*** 0 ****
--- 1,57 ----
+ /* contrib/pgstattuple/pgstattuple--1.0.sql */
+
+ -- complain if script is sourced in psql, rather than via CREATE EXTENSION
+ \echo Use "CREATE EXTENSION pgstattuple" to load this file. \quit
+
+ CREATE FUNCTION pgstattuple(IN relname text,
+ OUT table_len BIGINT, -- physical table length in bytes
+ OUT tuple_count BIGINT, -- number of live tuples
+ OUT tuple_len BIGINT, -- total tuples length in bytes
+ OUT tuple_percent FLOAT8, -- live tuples in %
+ OUT dead_tuple_count BIGINT, -- number of dead tuples
+ OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes
+ OUT dead_tuple_percent FLOAT8, -- dead tuples in %
+ OUT free_space BIGINT, -- free space in bytes
+ OUT free_percent FLOAT8) -- free space in %
+ AS 'MODULE_PATHNAME', 'pgstattuple'
+ LANGUAGE C STRICT;
+
+ CREATE FUNCTION pgstattuple(IN reloid oid,
+ OUT table_len BIGINT, -- physical table length in bytes
+ OUT tuple_count BIGINT, -- number of live tuples
+ OUT tuple_len BIGINT, -- total tuples length in bytes
+ OUT tuple_percent FLOAT8, -- live tuples in %
+ OUT dead_tuple_count BIGINT, -- number of dead tuples
+ OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes
+ OUT dead_tuple_percent FLOAT8, -- dead tuples in %
+ OUT free_space BIGINT, -- free space in bytes
+ OUT free_percent FLOAT8) -- free space in %
+ AS 'MODULE_PATHNAME', 'pgstattuplebyid'
+ LANGUAGE C STRICT;
+
+ CREATE FUNCTION pgstatindex(IN relname text,
+ OUT version INT,
+ OUT tree_level INT,
+ OUT index_size BIGINT,
+ OUT root_block_no BIGINT,
+ OUT internal_pages BIGINT,
+ OUT leaf_pages BIGINT,
+ OUT empty_pages BIGINT,
+ OUT deleted_pages BIGINT,
+ OUT avg_leaf_density FLOAT8,
+ OUT leaf_fragmentation FLOAT8)
+ AS 'MODULE_PATHNAME', 'pgstatindex'
+ LANGUAGE C STRICT;
+
+ CREATE FUNCTION pg_relpages(IN relname text)
+ RETURNS BIGINT
+ AS 'MODULE_PATHNAME', 'pg_relpages'
+ LANGUAGE C STRICT;
+
+ --
+ -- relation_free_space()
+ --
+ CREATE FUNCTION relation_free_space(IN relname text)
+ RETURNS real
+ AS 'MODULE_PATHNAME', 'relation_free_space'
+ LANGUAGE C STRICT;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 89ec4da..1001035 100644
*** a/contrib/pgstattuple/pgstattuple.c
--- b/contrib/pgstattuple/pgstattuple.c
*************** PG_MODULE_MAGIC;
*** 43,51 ****
--- 43,53 ----
PG_FUNCTION_INFO_V1(pgstattuple);
PG_FUNCTION_INFO_V1(pgstattuplebyid);
+ PG_FUNCTION_INFO_V1(relation_free_space);
extern Datum pgstattuple(PG_FUNCTION_ARGS);
extern Datum pgstattuplebyid(PG_FUNCTION_ARGS);
+ extern Datum relation_free_space(PG_FUNCTION_ARGS);
/*
* struct pgstattuple_type
*************** typedef struct pgstattuple_type
*** 63,93 ****
uint64 free_space; /* free/reusable space in bytes */
} pgstattuple_type;
! typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber);
static Datum build_pgstattuple_type(pgstattuple_type *stat,
FunctionCallInfo fcinfo);
static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
static void pgstat_btree_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno);
static void pgstat_hash_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno);
static void pgstat_gist_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno);
static void pgstat_gin_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno);
static void pgstat_spgist_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno);
static Datum pgstat_index(Relation rel, BlockNumber start,
pgstat_page pagefn, FunctionCallInfo fcinfo);
static void pgstat_index_page(pgstattuple_type *stat, Page page,
! OffsetNumber minoff, OffsetNumber maxoff);
/*
* Buffer access strategy for reading relations, it's simpler to keep it
* global because pgstat_*_page() functions read one buffer at a time.
! * pgstat_heap() and pgstat_index() should initialize it before use.
*/
BufferAccessStrategy bstrategy;
--- 65,98 ----
uint64 free_space; /* free/reusable space in bytes */
} pgstattuple_type;
! typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber, bool);
static Datum build_pgstattuple_type(pgstattuple_type *stat,
FunctionCallInfo fcinfo);
static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
static void pgstat_btree_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno, bool enable_counters);
static void pgstat_hash_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno, bool enable_counters);
static void pgstat_gist_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno, bool enable_counters);
static void pgstat_gin_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno, bool enable_counters);
static void pgstat_spgist_page(pgstattuple_type *stat,
! Relation rel, BlockNumber blkno, bool enable_counters);
static Datum pgstat_index(Relation rel, BlockNumber start,
pgstat_page pagefn, FunctionCallInfo fcinfo);
static void pgstat_index_page(pgstattuple_type *stat, Page page,
! OffsetNumber minoff, OffsetNumber maxoff, bool enable_counters);
!
! static float4 GetHeapRelationFreeSpace(Relation rel);
! static float4 GetIndexRelationFreeSpace(Relation rel);
/*
* Buffer access strategy for reading relations, it's simpler to keep it
* global because pgstat_*_page() functions read one buffer at a time.
! * every consumer of bstrategy should initialize it before use.
*/
BufferAccessStrategy bstrategy;
*************** pgstat_heap(Relation rel, FunctionCallIn
*** 362,368 ****
* pgstat_btree_page -- check tuples in a btree page
*/
static void
! pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
{
Buffer buf;
Page page;
--- 367,373 ----
* pgstat_btree_page -- check tuples in a btree page
*/
static void
! pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, bool enable_counters)
{
Buffer buf;
Page page;
*************** pgstat_btree_page(pgstattuple_type *stat
*** 390,396 ****
else if (P_ISLEAF(opaque))
{
pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque),
! PageGetMaxOffsetNumber(page));
}
else
{
--- 395,401 ----
else if (P_ISLEAF(opaque))
{
pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque),
! PageGetMaxOffsetNumber(page), enable_counters);
}
else
{
*************** pgstat_btree_page(pgstattuple_type *stat
*** 405,411 ****
* pgstat_hash_page -- check tuples in a hash page
*/
static void
! pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
{
Buffer buf;
Page page;
--- 410,416 ----
* pgstat_hash_page -- check tuples in a hash page
*/
static void
! pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, bool enable_counters)
{
Buffer buf;
Page page;
*************** pgstat_hash_page(pgstattuple_type *stat,
*** 427,433 ****
case LH_BUCKET_PAGE:
case LH_OVERFLOW_PAGE:
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page));
break;
case LH_BITMAP_PAGE:
case LH_META_PAGE:
--- 432,438 ----
case LH_BUCKET_PAGE:
case LH_OVERFLOW_PAGE:
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page), enable_counters);
break;
case LH_BITMAP_PAGE:
case LH_META_PAGE:
*************** pgstat_hash_page(pgstattuple_type *stat,
*** 448,454 ****
* pgstat_gist_page -- check tuples in a gist page
*/
static void
! pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
{
Buffer buf;
Page page;
--- 453,459 ----
* pgstat_gist_page -- check tuples in a gist page
*/
static void
! pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, bool enable_counters)
{
Buffer buf;
Page page;
*************** pgstat_gist_page(pgstattuple_type *stat,
*** 461,467 ****
if (GistPageIsLeaf(page))
{
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page));
}
else
{
--- 466,472 ----
if (GistPageIsLeaf(page))
{
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page), enable_counters);
}
else
{
*************** pgstat_gist_page(pgstattuple_type *stat,
*** 475,481 ****
* pgstat_gin_page -- check tuples in a gin page
*/
static void
! pgstat_gin_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
{
Buffer buf;
Page page;
--- 480,486 ----
* pgstat_gin_page -- check tuples in a gin page
*/
static void
! pgstat_gin_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, bool enable_counters)
{
Buffer buf;
Page page;
*************** pgstat_gin_page(pgstattuple_type *stat,
*** 492,498 ****
else if (GinPageIsLeaf(page) || GinPageIsData(page))
{
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page));
}
else
{
--- 497,503 ----
else if (GinPageIsLeaf(page) || GinPageIsData(page))
{
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page), enable_counters);
}
else
{
*************** pgstat_gin_page(pgstattuple_type *stat,
*** 506,512 ****
* pgstat_spgist_page -- check tuples in a spgist page
*/
static void
! pgstat_spgist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
{
Buffer buf;
Page page;
--- 511,517 ----
* pgstat_spgist_page -- check tuples in a spgist page
*/
static void
! pgstat_spgist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, bool enable_counters)
{
Buffer buf;
Page page;
*************** pgstat_spgist_page(pgstattuple_type *sta
*** 523,529 ****
else if (SpGistPageIsLeaf(page))
{
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page));
}
else
{
--- 528,534 ----
else if (SpGistPageIsLeaf(page))
{
pgstat_index_page(stat, page, FirstOffsetNumber,
! PageGetMaxOffsetNumber(page), enable_counters);
}
else
{
*************** pgstat_index(Relation rel, BlockNumber s
*** 567,573 ****
{
CHECK_FOR_INTERRUPTS();
! pagefn(&stat, rel, blkno);
}
}
--- 572,578 ----
{
CHECK_FOR_INTERRUPTS();
! pagefn(&stat, rel, blkno, true);
}
}
*************** pgstat_index(Relation rel, BlockNumber s
*** 581,605 ****
*/
static void
pgstat_index_page(pgstattuple_type *stat, Page page,
! OffsetNumber minoff, OffsetNumber maxoff)
{
OffsetNumber i;
stat->free_space += PageGetFreeSpace(page);
! for (i = minoff; i <= maxoff; i = OffsetNumberNext(i))
{
! ItemId itemid = PageGetItemId(page, i);
! if (ItemIdIsDead(itemid))
{
! stat->dead_tuple_count++;
! stat->dead_tuple_len += ItemIdGetLength(itemid);
}
! else
{
! stat->tuple_count++;
! stat->tuple_len += ItemIdGetLength(itemid);
}
}
}
--- 586,808 ----
*/
static void
pgstat_index_page(pgstattuple_type *stat, Page page,
! OffsetNumber minoff, OffsetNumber maxoff, bool enable_counters)
{
OffsetNumber i;
stat->free_space += PageGetFreeSpace(page);
! if (enable_counters)
{
! for (i = minoff; i <= maxoff; i = OffsetNumberNext(i))
! {
! ItemId itemid = PageGetItemId(page, i);
!
! if (ItemIdIsDead(itemid))
! {
! stat->dead_tuple_count++;
! stat->dead_tuple_len += ItemIdGetLength(itemid);
! }
! else
! {
! stat->tuple_count++;
! stat->tuple_len += ItemIdGetLength(itemid);
! }
! }
! }
! }
! /*
! * relation_free_space
! *
! * Returns the percentage of free space for a given relation.
! */
! Datum
! relation_free_space(PG_FUNCTION_ARGS)
! {
! text *relname = PG_GETARG_TEXT_P(0);
! RangeVar *relrv;
! Relation rel;
! float4 free_space = 0;
!
! relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
! rel = relation_openrv(relrv, AccessShareLock);
!
! /* Only allow tables or indexes */
! if (rel->rd_rel->relkind != RELKIND_INDEX &&
! rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_TOASTVALUE)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("cannot get free space from object \"%s\"",
! RelationGetRelationName(rel))));
!
! /*
! * Reject attempts to read non-local temporary relations. We would be
! * likely to get wrong data, since we have no visibility into the owning
! * session's local buffers.
! */
! if (RELATION_IS_OTHER_TEMP(rel))
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot access temporary tables of other sessions")));
!
! switch (rel->rd_rel->relkind)
! {
! case RELKIND_RELATION:
! case RELKIND_TOASTVALUE:
! free_space = GetHeapRelationFreeSpace(rel);
! break;
! case RELKIND_INDEX:
! free_space = GetIndexRelationFreeSpace(rel);
! break;
! default:
! /* we are not supposed to get here */
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("cannot get free space from object \"%s\"",
! RelationGetRelationName(rel))));
! }
! relation_close(rel, AccessShareLock);
!
! PG_RETURN_FLOAT4(free_space);
! }
!
! /*
! * GetHeapRelationFreeSpace()
! *
! * Get the free space for a heap relation.
! * This is a helper function for relation_free_space()
! */
! static float4
! GetHeapRelationFreeSpace(Relation rel)
! {
! Buffer buffer;
! Page page;
! BlockNumber blkno = 0;
! BlockNumber nblocks;
! Size free_space = 0;
! double free_percent = 0;
!
! /* prepare access strategy for reading the table */
! bstrategy = GetAccessStrategy(BAS_BULKREAD);
!
! for (;;)
! {
! /* Get the current relation length */
! LockRelationForExtension(rel, ExclusiveLock);
! nblocks = RelationGetNumberOfBlocks(rel);
! UnlockRelationForExtension(rel, ExclusiveLock);
!
! /* Quit if we've scanned the whole relation */
! if (blkno >= nblocks)
{
! break;
}
!
! for (; blkno < nblocks; blkno++)
{
! CHECK_FOR_INTERRUPTS();
!
! buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
! LockBuffer(buffer, BUFFER_LOCK_SHARE);
!
! page = BufferGetPage(buffer);
! free_space += PageGetHeapFreeSpace(page);
!
! UnlockReleaseBuffer(buffer);
}
}
+
+ if (nblocks > 0)
+ free_percent = ((float4) free_space) / (nblocks * BLCKSZ);
+
+ return free_percent;
+ }
+
+ /*------------------------------------------------
+ * GetIndexRelationFreeSpace
+ *
+ * Look the type of the index and call the appropiate
+ * function to get the free space
+ *------------------------------------------------
+ */
+ static float4
+ GetIndexRelationFreeSpace(Relation rel)
+ {
+ BlockNumber nblocks;
+ BlockNumber blkno;
+ pgstattuple_type stat = {0};
+ double free_percent = 0;
+
+ /* prepare access strategy for reading the index */
+ bstrategy = GetAccessStrategy(BAS_BULKREAD);
+
+ /* start at block */
+ switch (rel->rd_rel->relam)
+ {
+ case BTREE_AM_OID:
+ blkno = BTREE_METAPAGE + 1;
+ break;
+ case HASH_AM_OID:
+ blkno = HASH_METAPAGE + 1;
+ break;
+ case GIST_AM_OID:
+ blkno = GIST_ROOT_BLKNO + 1;
+ break;
+ case GIN_AM_OID:
+ blkno = GIN_METAPAGE_BLKNO + 1;
+ break;
+ case SPGIST_AM_OID:
+ blkno = SPGIST_METAPAGE_BLKNO + 1;
+ break;
+ default:
+ blkno = 0;
+ }
+
+ for (;;)
+ {
+ /* Get the current relation length */
+ LockRelationForExtension(rel, ExclusiveLock);
+ nblocks = RelationGetNumberOfBlocks(rel);
+ UnlockRelationForExtension(rel, ExclusiveLock);
+
+ /* Quit if we've scanned the whole relation */
+ if (blkno >= nblocks)
+ {
+ break;
+ }
+
+ for (; blkno < nblocks; blkno++)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ switch (rel->rd_rel->relam)
+ {
+ case BTREE_AM_OID:
+ pgstat_btree_page(&stat, rel, blkno, false);
+ break;
+ case HASH_AM_OID:
+ pgstat_hash_page(&stat, rel, blkno, false);
+ break;
+ case GIST_AM_OID:
+ pgstat_gist_page(&stat, rel, blkno, false);
+ break;
+ case GIN_AM_OID:
+ pgstat_gin_page(&stat, rel, blkno, false);
+ break;
+ case SPGIST_AM_OID:
+ pgstat_spgist_page(&stat, rel, blkno, false);
+ break;
+ default:
+ elog(ERROR, "Unknown index type %d", rel->rd_rel->relam);
+ break;
+ }
+ }
+ }
+
+ if (nblocks > 0)
+ free_percent = ((float4) stat.free_space) / (nblocks * BLCKSZ);
+
+ return free_percent;
}
diff --git a/contrib/pgstattuple/pgstattuple.control b/contrib/pgstattuple/pgstattuple.control
index 7b5129b..fcfd36f 100644
*** a/contrib/pgstattuple/pgstattuple.control
--- b/contrib/pgstattuple/pgstattuple.control
***************
*** 1,5 ****
# pgstattuple extension
comment = 'show tuple-level statistics'
! default_version = '1.0'
module_pathname = '$libdir/pgstattuple'
relocatable = true
--- 1,5 ----
# pgstattuple extension
comment = 'show tuple-level statistics'
! default_version = '1.1'
module_pathname = '$libdir/pgstattuple'
relocatable = true
diff --git a/doc/src/sgml/pgstattuple.sgml b/doc/src/sgml/pgstattuple.sgml
index a55b35c..4e99a54 100644
*** a/doc/src/sgml/pgstattuple.sgml
--- b/doc/src/sgml/pgstattuple.sgml
*************** leaf_fragmentation | 0
*** 256,261 ****
--- 256,277 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>relation_free_space(relname text) returns float</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>relation_free_space</function> returns a factor that represents
+ the total amount of free space in a table or index. The entire relation
+ is scanned in order to compute this amount, which may generate a large
+ amount of disk reads.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</sect2>