2022-03-21 20:02:25 +01:00
|
|
|
/* -------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* pgstat_relation.c
|
|
|
|
* Implementation of relation statistics.
|
|
|
|
*
|
|
|
|
* This file contains the implementation of function relation. It is kept
|
|
|
|
* separate from pgstat.c to enforce the line between the statistics access /
|
|
|
|
* storage implementation and the details about individual types of
|
|
|
|
* statistics.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2001-2022, PostgreSQL Global Development Group
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/backend/utils/activity/pgstat_relation.c
|
|
|
|
* -------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/twophase_rmgr.h"
|
|
|
|
#include "access/xact.h"
|
2022-04-07 06:29:46 +02:00
|
|
|
#include "catalog/partition.h"
|
2022-03-21 20:02:25 +01:00
|
|
|
#include "postmaster/autovacuum.h"
|
|
|
|
#include "utils/memutils.h"
|
|
|
|
#include "utils/pgstat_internal.h"
|
|
|
|
#include "utils/rel.h"
|
|
|
|
#include "utils/timestamp.h"
|
2022-11-20 19:53:31 +01:00
|
|
|
#include "catalog/catalog.h"
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
/* Record that's written to 2PC state file when pgstat state is persisted */
|
|
|
|
typedef struct TwoPhasePgStatRecord
|
|
|
|
{
|
|
|
|
PgStat_Counter tuples_inserted; /* tuples inserted in xact */
|
|
|
|
PgStat_Counter tuples_updated; /* tuples updated in xact */
|
|
|
|
PgStat_Counter tuples_deleted; /* tuples deleted in xact */
|
|
|
|
/* tuples i/u/d prior to truncate/drop */
|
|
|
|
PgStat_Counter inserted_pre_truncdrop;
|
|
|
|
PgStat_Counter updated_pre_truncdrop;
|
|
|
|
PgStat_Counter deleted_pre_truncdrop;
|
|
|
|
Oid t_id; /* table's OID */
|
|
|
|
bool t_shared; /* is it a shared catalog? */
|
|
|
|
bool t_truncdropped; /* was the relation truncated/dropped? */
|
|
|
|
} TwoPhasePgStatRecord;
|
|
|
|
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
|
2022-03-21 20:02:25 +01:00
|
|
|
static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
|
|
|
|
static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
|
2022-04-07 06:29:46 +02:00
|
|
|
static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
|
|
|
|
static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
|
2022-04-06 23:09:18 +02:00
|
|
|
/*
|
|
|
|
* Copy stats between relations. This is used for things like REINDEX
|
|
|
|
* CONCURRENTLY.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_copy_relation_stats(Relation dst, Relation src)
|
|
|
|
{
|
|
|
|
PgStat_StatTabEntry *srcstats;
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStatShared_Relation *dstshstats;
|
|
|
|
PgStat_EntryRef *dst_ref;
|
2022-04-06 23:09:18 +02:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
|
|
|
|
RelationGetRelid(src));
|
2022-04-06 23:09:18 +02:00
|
|
|
if (!srcstats)
|
|
|
|
return;
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
|
|
|
|
dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
|
|
|
|
RelationGetRelid(dst),
|
|
|
|
false);
|
|
|
|
|
|
|
|
dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
|
|
|
|
dstshstats->stats = *srcstats;
|
|
|
|
|
|
|
|
pgstat_unlock_entry(dst_ref);
|
2022-04-06 23:09:18 +02:00
|
|
|
}
|
|
|
|
|
2022-04-04 21:14:34 +02:00
|
|
|
/*
|
2022-04-07 06:29:46 +02:00
|
|
|
* Initialize a relcache entry to count access statistics. Called whenever a
|
|
|
|
* relation is opened.
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
2022-04-07 06:29:46 +02:00
|
|
|
* We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
|
|
|
|
* when the relcache entry is made; thereafter it is long-lived data.
|
|
|
|
*
|
|
|
|
* This does not create a reference to a stats entry in shared memory, nor
|
|
|
|
* allocate memory for the pending stats. That happens in
|
|
|
|
* pgstat_assoc_relation().
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
2022-04-07 06:29:46 +02:00
|
|
|
pgstat_init_relation(Relation rel)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
char relkind = rel->rd_rel->relkind;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only count stats for relations with storage and partitioned tables
|
|
|
|
*/
|
|
|
|
if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
rel->pgstat_enabled = false;
|
2022-03-21 20:02:25 +01:00
|
|
|
rel->pgstat_info = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
if (!pgstat_track_counts)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
if (rel->pgstat_info)
|
|
|
|
pgstat_unlink_relation(rel);
|
|
|
|
|
2022-03-21 20:02:25 +01:00
|
|
|
/* We're not counting at all */
|
2022-04-07 06:29:46 +02:00
|
|
|
rel->pgstat_enabled = false;
|
2022-03-21 20:02:25 +01:00
|
|
|
rel->pgstat_info = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
rel->pgstat_enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prepare for statistics for this relation to be collected.
|
|
|
|
*
|
|
|
|
* This ensures we have a reference to the stats entry before stats can be
|
|
|
|
* generated. That is important because a relation drop in another connection
|
|
|
|
* could otherwise lead to the stats entry being dropped, which then later
|
|
|
|
* would get recreated when flushing stats.
|
|
|
|
*
|
|
|
|
* This is separate from pgstat_init_relation() as it is not uncommon for
|
|
|
|
* relcache entries to be opened without ever getting stats reported.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_assoc_relation(Relation rel)
|
|
|
|
{
|
|
|
|
Assert(rel->pgstat_enabled);
|
|
|
|
Assert(rel->pgstat_info == NULL);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
/* Else find or make the PgStat_TableStatus entry, and update link */
|
2022-04-07 06:29:46 +02:00
|
|
|
rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
|
|
|
|
rel->rd_rel->relisshared);
|
|
|
|
|
|
|
|
/* don't allow link a stats to multiple relcache entries */
|
|
|
|
Assert(rel->pgstat_info->relation == NULL);
|
|
|
|
|
|
|
|
/* mark this relation as the owner */
|
|
|
|
rel->pgstat_info->relation = rel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Break the mutual link between a relcache entry and pending stats entry.
|
|
|
|
* This must be called whenever one end of the link is removed.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_unlink_relation(Relation rel)
|
|
|
|
{
|
|
|
|
/* remove the link to stats info if any */
|
|
|
|
if (rel->pgstat_info == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* link sanity check */
|
|
|
|
Assert(rel->pgstat_info->relation == rel);
|
|
|
|
rel->pgstat_info->relation = NULL;
|
|
|
|
rel->pgstat_info = NULL;
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
2022-04-04 21:14:34 +02:00
|
|
|
/*
|
pgstat: scaffolding for transactional stats creation / drop.
One problematic part of the current statistics collector design is that there
is no reliable way of getting rid of statistics entries. Because of that
pgstat_vacuum_stat() (called by [auto-]vacuum) matches all stats for the
current database with the catalog contents and tries to drop now-superfluous
entries. That's quite expensive. What's worse, it doesn't work on physical
replicas, despite physical replicas collection statistics entries.
This commit introduces infrastructure to create / drop statistics entries
transactionally, together with the underlying catalog objects (functions,
relations, subscriptions). pgstat_xact.c maintains a list of stats entries
created / dropped transactionally in the current transaction. To ensure the
removal of statistics entries is durable dropped statistics entries are
included in commit / abort (and prepare) records, which also ensures that
stats entries are dropped on standbys.
Statistics entries created separately from creating the underlying catalog
object (e.g. when stats were previously lost due to an immediate restart)
are *not* WAL logged. However that can only happen outside of the transaction
creating the catalog object, so it does not lead to "leaked" statistics
entries.
For this to work, functions creating / dropping functions / relations /
subscriptions need to call into pgstat. For subscriptions this was already
done when dropping subscriptions, via pgstat_report_subscription_drop() (now
renamed to pgstat_drop_subscription()).
This commit does not actually drop stats yet, it just provides the
infrastructure. It is however a largely independent piece of infrastructure,
so committing it separately makes sense.
Bumps XLOG_PAGE_MAGIC.
Author: Andres Freund <andres@anarazel.de>
Reviewed-By: Thomas Munro <thomas.munro@gmail.com>
Reviewed-By: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
2022-04-07 03:22:22 +02:00
|
|
|
* Ensure that stats are dropped if transaction aborts.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
pgstat: scaffolding for transactional stats creation / drop.
One problematic part of the current statistics collector design is that there
is no reliable way of getting rid of statistics entries. Because of that
pgstat_vacuum_stat() (called by [auto-]vacuum) matches all stats for the
current database with the catalog contents and tries to drop now-superfluous
entries. That's quite expensive. What's worse, it doesn't work on physical
replicas, despite physical replicas collection statistics entries.
This commit introduces infrastructure to create / drop statistics entries
transactionally, together with the underlying catalog objects (functions,
relations, subscriptions). pgstat_xact.c maintains a list of stats entries
created / dropped transactionally in the current transaction. To ensure the
removal of statistics entries is durable dropped statistics entries are
included in commit / abort (and prepare) records, which also ensures that
stats entries are dropped on standbys.
Statistics entries created separately from creating the underlying catalog
object (e.g. when stats were previously lost due to an immediate restart)
are *not* WAL logged. However that can only happen outside of the transaction
creating the catalog object, so it does not lead to "leaked" statistics
entries.
For this to work, functions creating / dropping functions / relations /
subscriptions need to call into pgstat. For subscriptions this was already
done when dropping subscriptions, via pgstat_report_subscription_drop() (now
renamed to pgstat_drop_subscription()).
This commit does not actually drop stats yet, it just provides the
infrastructure. It is however a largely independent piece of infrastructure,
so committing it separately makes sense.
Bumps XLOG_PAGE_MAGIC.
Author: Andres Freund <andres@anarazel.de>
Reviewed-By: Thomas Munro <thomas.munro@gmail.com>
Reviewed-By: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
2022-04-07 03:22:22 +02:00
|
|
|
pgstat_create_relation(Relation rel)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
pgstat: scaffolding for transactional stats creation / drop.
One problematic part of the current statistics collector design is that there
is no reliable way of getting rid of statistics entries. Because of that
pgstat_vacuum_stat() (called by [auto-]vacuum) matches all stats for the
current database with the catalog contents and tries to drop now-superfluous
entries. That's quite expensive. What's worse, it doesn't work on physical
replicas, despite physical replicas collection statistics entries.
This commit introduces infrastructure to create / drop statistics entries
transactionally, together with the underlying catalog objects (functions,
relations, subscriptions). pgstat_xact.c maintains a list of stats entries
created / dropped transactionally in the current transaction. To ensure the
removal of statistics entries is durable dropped statistics entries are
included in commit / abort (and prepare) records, which also ensures that
stats entries are dropped on standbys.
Statistics entries created separately from creating the underlying catalog
object (e.g. when stats were previously lost due to an immediate restart)
are *not* WAL logged. However that can only happen outside of the transaction
creating the catalog object, so it does not lead to "leaked" statistics
entries.
For this to work, functions creating / dropping functions / relations /
subscriptions need to call into pgstat. For subscriptions this was already
done when dropping subscriptions, via pgstat_report_subscription_drop() (now
renamed to pgstat_drop_subscription()).
This commit does not actually drop stats yet, it just provides the
infrastructure. It is however a largely independent piece of infrastructure,
so committing it separately makes sense.
Bumps XLOG_PAGE_MAGIC.
Author: Andres Freund <andres@anarazel.de>
Reviewed-By: Thomas Munro <thomas.munro@gmail.com>
Reviewed-By: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
2022-04-07 03:22:22 +02:00
|
|
|
pgstat_create_transactional(PGSTAT_KIND_RELATION,
|
|
|
|
rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
|
|
|
|
RelationGetRelid(rel));
|
|
|
|
}
|
2022-03-21 20:02:25 +01:00
|
|
|
|
pgstat: scaffolding for transactional stats creation / drop.
One problematic part of the current statistics collector design is that there
is no reliable way of getting rid of statistics entries. Because of that
pgstat_vacuum_stat() (called by [auto-]vacuum) matches all stats for the
current database with the catalog contents and tries to drop now-superfluous
entries. That's quite expensive. What's worse, it doesn't work on physical
replicas, despite physical replicas collection statistics entries.
This commit introduces infrastructure to create / drop statistics entries
transactionally, together with the underlying catalog objects (functions,
relations, subscriptions). pgstat_xact.c maintains a list of stats entries
created / dropped transactionally in the current transaction. To ensure the
removal of statistics entries is durable dropped statistics entries are
included in commit / abort (and prepare) records, which also ensures that
stats entries are dropped on standbys.
Statistics entries created separately from creating the underlying catalog
object (e.g. when stats were previously lost due to an immediate restart)
are *not* WAL logged. However that can only happen outside of the transaction
creating the catalog object, so it does not lead to "leaked" statistics
entries.
For this to work, functions creating / dropping functions / relations /
subscriptions need to call into pgstat. For subscriptions this was already
done when dropping subscriptions, via pgstat_report_subscription_drop() (now
renamed to pgstat_drop_subscription()).
This commit does not actually drop stats yet, it just provides the
infrastructure. It is however a largely independent piece of infrastructure,
so committing it separately makes sense.
Bumps XLOG_PAGE_MAGIC.
Author: Andres Freund <andres@anarazel.de>
Reviewed-By: Thomas Munro <thomas.munro@gmail.com>
Reviewed-By: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
2022-04-07 03:22:22 +02:00
|
|
|
/*
|
|
|
|
* Ensure that stats are dropped if transaction commits.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_drop_relation(Relation rel)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
int nest_level = GetCurrentTransactionNestLevel();
|
2022-04-07 08:35:56 +02:00
|
|
|
PgStat_TableStatus *pgstat_info;
|
2022-04-07 06:29:46 +02:00
|
|
|
|
pgstat: scaffolding for transactional stats creation / drop.
One problematic part of the current statistics collector design is that there
is no reliable way of getting rid of statistics entries. Because of that
pgstat_vacuum_stat() (called by [auto-]vacuum) matches all stats for the
current database with the catalog contents and tries to drop now-superfluous
entries. That's quite expensive. What's worse, it doesn't work on physical
replicas, despite physical replicas collection statistics entries.
This commit introduces infrastructure to create / drop statistics entries
transactionally, together with the underlying catalog objects (functions,
relations, subscriptions). pgstat_xact.c maintains a list of stats entries
created / dropped transactionally in the current transaction. To ensure the
removal of statistics entries is durable dropped statistics entries are
included in commit / abort (and prepare) records, which also ensures that
stats entries are dropped on standbys.
Statistics entries created separately from creating the underlying catalog
object (e.g. when stats were previously lost due to an immediate restart)
are *not* WAL logged. However that can only happen outside of the transaction
creating the catalog object, so it does not lead to "leaked" statistics
entries.
For this to work, functions creating / dropping functions / relations /
subscriptions need to call into pgstat. For subscriptions this was already
done when dropping subscriptions, via pgstat_report_subscription_drop() (now
renamed to pgstat_drop_subscription()).
This commit does not actually drop stats yet, it just provides the
infrastructure. It is however a largely independent piece of infrastructure,
so committing it separately makes sense.
Bumps XLOG_PAGE_MAGIC.
Author: Andres Freund <andres@anarazel.de>
Reviewed-By: Thomas Munro <thomas.munro@gmail.com>
Reviewed-By: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
2022-04-07 03:22:22 +02:00
|
|
|
pgstat_drop_transactional(PGSTAT_KIND_RELATION,
|
|
|
|
rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
|
|
|
|
RelationGetRelid(rel));
|
2022-04-07 06:29:46 +02:00
|
|
|
|
2022-04-07 08:35:56 +02:00
|
|
|
if (!pgstat_should_count_relation(rel))
|
|
|
|
return;
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
/*
|
|
|
|
* Transactionally set counters to 0. That ensures that accesses to
|
|
|
|
* pg_stat_xact_all_tables inside the transaction show 0.
|
|
|
|
*/
|
2022-04-07 08:35:56 +02:00
|
|
|
pgstat_info = rel->pgstat_info;
|
|
|
|
if (pgstat_info->trans &&
|
2022-04-07 06:29:46 +02:00
|
|
|
pgstat_info->trans->nest_level == nest_level)
|
|
|
|
{
|
|
|
|
save_truncdrop_counters(pgstat_info->trans, true);
|
|
|
|
pgstat_info->trans->tuples_inserted = 0;
|
|
|
|
pgstat_info->trans->tuples_updated = 0;
|
|
|
|
pgstat_info->trans->tuples_deleted = 0;
|
|
|
|
}
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
2022-04-04 21:14:34 +02:00
|
|
|
/*
|
2022-04-06 22:56:06 +02:00
|
|
|
* Report that the table was just vacuumed.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_report_vacuum(Oid tableoid, bool shared,
|
|
|
|
PgStat_Counter livetuples, PgStat_Counter deadtuples)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStat_EntryRef *entry_ref;
|
|
|
|
PgStatShared_Relation *shtabentry;
|
|
|
|
PgStat_StatTabEntry *tabentry;
|
|
|
|
Oid dboid = (shared ? InvalidOid : MyDatabaseId);
|
|
|
|
TimestampTz ts;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
if (!pgstat_track_counts)
|
2022-03-21 20:02:25 +01:00
|
|
|
return;
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
/* Store the data in the table's hash table entry. */
|
|
|
|
ts = GetCurrentTimestamp();
|
|
|
|
|
|
|
|
/* block acquiring lock for the same reason as pgstat_report_autovac() */
|
|
|
|
entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
|
|
|
|
dboid, tableoid, false);
|
|
|
|
|
|
|
|
shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
|
|
|
|
tabentry = &shtabentry->stats;
|
|
|
|
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->live_tuples = livetuples;
|
|
|
|
tabentry->dead_tuples = deadtuples;
|
2022-04-07 06:29:46 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* It is quite possible that a non-aggressive VACUUM ended up skipping
|
|
|
|
* various pages, however, we'll zero the insert counter here regardless.
|
|
|
|
* It's currently used only to track when we need to perform an "insert"
|
|
|
|
* autovacuum, which are mainly intended to freeze newly inserted tuples.
|
|
|
|
* Zeroing this may just mean we'll not try to vacuum the table again
|
|
|
|
* until enough tuples have been inserted to trigger another insert
|
|
|
|
* autovacuum. An anti-wraparound autovacuum will catch any persistent
|
|
|
|
* stragglers.
|
|
|
|
*/
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->ins_since_vacuum = 0;
|
2022-04-07 06:29:46 +02:00
|
|
|
|
|
|
|
if (IsAutoVacuumWorkerProcess())
|
|
|
|
{
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->last_autovacuum_time = ts;
|
|
|
|
tabentry->autovacuum_count++;
|
2022-04-07 06:29:46 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->last_vacuum_time = ts;
|
2022-04-07 06:29:46 +02:00
|
|
|
tabentry->vacuum_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
pgstat_unlock_entry(entry_ref);
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
2022-04-04 21:14:34 +02:00
|
|
|
/*
|
2022-04-06 22:56:06 +02:00
|
|
|
* Report that the table was just analyzed.
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
|
|
|
* Caller must provide new live- and dead-tuples estimates, as well as a
|
2022-12-06 02:46:35 +01:00
|
|
|
* flag indicating whether to reset the mod_since_analyze counter.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_report_analyze(Relation rel,
|
|
|
|
PgStat_Counter livetuples, PgStat_Counter deadtuples,
|
|
|
|
bool resetcounter)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStat_EntryRef *entry_ref;
|
|
|
|
PgStatShared_Relation *shtabentry;
|
|
|
|
PgStat_StatTabEntry *tabentry;
|
|
|
|
Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
if (!pgstat_track_counts)
|
2022-03-21 20:02:25 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unlike VACUUM, ANALYZE might be running inside a transaction that has
|
|
|
|
* already inserted and/or deleted rows in the target table. ANALYZE will
|
|
|
|
* have counted such rows as live or dead respectively. Because we will
|
|
|
|
* report our counts of such rows at transaction end, we should subtract
|
2022-04-06 22:56:06 +02:00
|
|
|
* off these counts from the update we're making now, else they'll be
|
|
|
|
* double-counted after commit. (This approach also ensures that the
|
|
|
|
* shared stats entry ends up with the right numbers if we abort instead
|
|
|
|
* of committing.)
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
|
|
|
* Waste no time on partitioned tables, though.
|
|
|
|
*/
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pgstat_should_count_relation(rel) &&
|
2022-03-21 20:02:25 +01:00
|
|
|
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
|
|
|
{
|
|
|
|
PgStat_TableXactStatus *trans;
|
|
|
|
|
|
|
|
for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
|
|
|
|
{
|
|
|
|
livetuples -= trans->tuples_inserted - trans->tuples_deleted;
|
|
|
|
deadtuples -= trans->tuples_updated + trans->tuples_deleted;
|
|
|
|
}
|
|
|
|
/* count stuff inserted by already-aborted subxacts, too */
|
|
|
|
deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
|
|
|
|
/* Since ANALYZE's counts are estimates, we could have underflowed */
|
|
|
|
livetuples = Max(livetuples, 0);
|
|
|
|
deadtuples = Max(deadtuples, 0);
|
|
|
|
}
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
/* block acquiring lock for the same reason as pgstat_report_autovac() */
|
|
|
|
entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
|
|
|
|
RelationGetRelid(rel),
|
|
|
|
false);
|
|
|
|
/* can't get dropped while accessed */
|
|
|
|
Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
|
|
|
|
|
|
|
|
shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
|
|
|
|
tabentry = &shtabentry->stats;
|
|
|
|
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->live_tuples = livetuples;
|
|
|
|
tabentry->dead_tuples = deadtuples;
|
2022-04-07 06:29:46 +02:00
|
|
|
|
|
|
|
/*
|
2022-12-06 02:46:35 +01:00
|
|
|
* If commanded, reset mod_since_analyze to zero. This forgets any
|
2022-04-07 06:29:46 +02:00
|
|
|
* changes that were committed while the ANALYZE was in progress, but we
|
|
|
|
* have no good way to estimate how many of those there were.
|
|
|
|
*/
|
|
|
|
if (resetcounter)
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->mod_since_analyze = 0;
|
2022-04-07 06:29:46 +02:00
|
|
|
|
|
|
|
if (IsAutoVacuumWorkerProcess())
|
|
|
|
{
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->last_autoanalyze_time = GetCurrentTimestamp();
|
|
|
|
tabentry->autoanalyze_count++;
|
2022-04-07 06:29:46 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->last_analyze_time = GetCurrentTimestamp();
|
2022-04-07 06:29:46 +02:00
|
|
|
tabentry->analyze_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
pgstat_unlock_entry(entry_ref);
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* count a tuple insertion of n tuples
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pgstat_should_count_relation(rel))
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
|
|
|
|
|
|
|
|
ensure_tabstat_xact_level(pgstat_info);
|
|
|
|
pgstat_info->trans->tuples_inserted += n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* count a tuple update
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_count_heap_update(Relation rel, bool hot)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pgstat_should_count_relation(rel))
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
|
|
|
|
|
|
|
|
ensure_tabstat_xact_level(pgstat_info);
|
|
|
|
pgstat_info->trans->tuples_updated++;
|
|
|
|
|
|
|
|
/* t_tuples_hot_updated is nontransactional, so just advance it */
|
|
|
|
if (hot)
|
|
|
|
pgstat_info->t_counts.t_tuples_hot_updated++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* count a tuple deletion
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_count_heap_delete(Relation rel)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pgstat_should_count_relation(rel))
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
|
|
|
|
|
|
|
|
ensure_tabstat_xact_level(pgstat_info);
|
|
|
|
pgstat_info->trans->tuples_deleted++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* update tuple counters due to truncate
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_count_truncate(Relation rel)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pgstat_should_count_relation(rel))
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
|
|
|
|
|
|
|
|
ensure_tabstat_xact_level(pgstat_info);
|
2022-04-07 06:29:46 +02:00
|
|
|
save_truncdrop_counters(pgstat_info->trans, false);
|
2022-03-21 20:02:25 +01:00
|
|
|
pgstat_info->trans->tuples_inserted = 0;
|
|
|
|
pgstat_info->trans->tuples_updated = 0;
|
|
|
|
pgstat_info->trans->tuples_deleted = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* update dead-tuples count
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
|
|
|
* The semantics of this are that we are reporting the nontransactional
|
|
|
|
* recovery of "delta" dead tuples; so t_delta_dead_tuples decreases
|
|
|
|
* rather than increasing, and the change goes straight into the per-table
|
|
|
|
* counter, not into transactional state.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_update_heap_dead_tuples(Relation rel, int delta)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pgstat_should_count_relation(rel))
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
PgStat_TableStatus *pgstat_info = rel->pgstat_info;
|
|
|
|
|
|
|
|
pgstat_info->t_counts.t_delta_dead_tuples -= delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
/*
|
|
|
|
* Support function for the SQL-callable pgstat* functions. Returns
|
|
|
|
* the collected statistics for one table or NULL. NULL doesn't mean
|
|
|
|
* that the table doesn't exist, just that there are no statistics, so the
|
|
|
|
* caller is better off to report ZERO instead.
|
|
|
|
*/
|
|
|
|
PgStat_StatTabEntry *
|
|
|
|
pgstat_fetch_stat_tabentry(Oid relid)
|
|
|
|
{
|
2022-11-20 19:53:31 +01:00
|
|
|
return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid);
|
2022-04-07 06:29:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
|
|
|
|
* whether the to-be-accessed table is a shared relation or not.
|
|
|
|
*/
|
|
|
|
PgStat_StatTabEntry *
|
|
|
|
pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
|
|
|
|
{
|
|
|
|
Oid dboid = (shared ? InvalidOid : MyDatabaseId);
|
|
|
|
|
|
|
|
return (PgStat_StatTabEntry *)
|
|
|
|
pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
|
|
|
|
}
|
|
|
|
|
2022-03-21 20:02:25 +01:00
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* find any existing PgStat_TableStatus entry for rel
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
2022-04-07 06:29:46 +02:00
|
|
|
* Find any existing PgStat_TableStatus entry for rel_id in the current
|
|
|
|
* database. If not found, try finding from shared tables.
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
2022-04-07 06:29:46 +02:00
|
|
|
* If no entry found, return NULL, don't create a new one
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
PgStat_TableStatus *
|
|
|
|
find_tabstat_entry(Oid rel_id)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStat_EntryRef *entry_ref;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
|
|
|
|
if (!entry_ref)
|
|
|
|
entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
if (entry_ref)
|
|
|
|
return entry_ref->pending;
|
|
|
|
return NULL;
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Perform relation stats specific end-of-transaction work. Helper for
|
|
|
|
* AtEOXact_PgStat.
|
|
|
|
*
|
|
|
|
* Transfer transactional insert/update counts into the base tabstat entries.
|
|
|
|
* We don't bother to free any of the transactional state, since it's all in
|
|
|
|
* TopTransactionContext and will go away anyway.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
|
|
|
|
{
|
|
|
|
PgStat_TableXactStatus *trans;
|
|
|
|
|
|
|
|
for (trans = xact_state->first; trans != NULL; trans = trans->next)
|
|
|
|
{
|
|
|
|
PgStat_TableStatus *tabstat;
|
|
|
|
|
|
|
|
Assert(trans->nest_level == 1);
|
|
|
|
Assert(trans->upper == NULL);
|
|
|
|
tabstat = trans->parent;
|
|
|
|
Assert(tabstat->trans == trans);
|
|
|
|
/* restore pre-truncate/drop stats (if any) in case of aborted xact */
|
|
|
|
if (!isCommit)
|
2022-04-07 06:29:46 +02:00
|
|
|
restore_truncdrop_counters(trans);
|
2022-03-21 20:02:25 +01:00
|
|
|
/* count attempted actions regardless of commit/abort */
|
|
|
|
tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
|
|
|
|
tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
|
|
|
|
tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
|
|
|
|
if (isCommit)
|
|
|
|
{
|
|
|
|
tabstat->t_counts.t_truncdropped = trans->truncdropped;
|
|
|
|
if (trans->truncdropped)
|
|
|
|
{
|
|
|
|
/* forget live/dead stats seen by backend thus far */
|
|
|
|
tabstat->t_counts.t_delta_live_tuples = 0;
|
|
|
|
tabstat->t_counts.t_delta_dead_tuples = 0;
|
|
|
|
}
|
|
|
|
/* insert adds a live tuple, delete removes one */
|
|
|
|
tabstat->t_counts.t_delta_live_tuples +=
|
|
|
|
trans->tuples_inserted - trans->tuples_deleted;
|
|
|
|
/* update and delete each create a dead tuple */
|
|
|
|
tabstat->t_counts.t_delta_dead_tuples +=
|
|
|
|
trans->tuples_updated + trans->tuples_deleted;
|
|
|
|
/* insert, update, delete each count as one change event */
|
|
|
|
tabstat->t_counts.t_changed_tuples +=
|
|
|
|
trans->tuples_inserted + trans->tuples_updated +
|
|
|
|
trans->tuples_deleted;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* inserted tuples are dead, deleted tuples are unaffected */
|
|
|
|
tabstat->t_counts.t_delta_dead_tuples +=
|
|
|
|
trans->tuples_inserted + trans->tuples_updated;
|
|
|
|
/* an aborted xact generates no changed_tuple events */
|
|
|
|
}
|
|
|
|
tabstat->trans = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Perform relation stats specific end-of-sub-transaction work. Helper for
|
|
|
|
* AtEOSubXact_PgStat.
|
|
|
|
*
|
|
|
|
* Transfer transactional insert/update counts into the next higher
|
|
|
|
* subtransaction state.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
|
|
|
|
{
|
|
|
|
PgStat_TableXactStatus *trans;
|
|
|
|
PgStat_TableXactStatus *next_trans;
|
|
|
|
|
|
|
|
for (trans = xact_state->first; trans != NULL; trans = next_trans)
|
|
|
|
{
|
|
|
|
PgStat_TableStatus *tabstat;
|
|
|
|
|
|
|
|
next_trans = trans->next;
|
|
|
|
Assert(trans->nest_level == nestDepth);
|
|
|
|
tabstat = trans->parent;
|
|
|
|
Assert(tabstat->trans == trans);
|
|
|
|
|
|
|
|
if (isCommit)
|
|
|
|
{
|
|
|
|
if (trans->upper && trans->upper->nest_level == nestDepth - 1)
|
|
|
|
{
|
|
|
|
if (trans->truncdropped)
|
|
|
|
{
|
|
|
|
/* propagate the truncate/drop status one level up */
|
2022-04-07 06:29:46 +02:00
|
|
|
save_truncdrop_counters(trans->upper, false);
|
2022-03-21 20:02:25 +01:00
|
|
|
/* replace upper xact stats with ours */
|
|
|
|
trans->upper->tuples_inserted = trans->tuples_inserted;
|
|
|
|
trans->upper->tuples_updated = trans->tuples_updated;
|
|
|
|
trans->upper->tuples_deleted = trans->tuples_deleted;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trans->upper->tuples_inserted += trans->tuples_inserted;
|
|
|
|
trans->upper->tuples_updated += trans->tuples_updated;
|
|
|
|
trans->upper->tuples_deleted += trans->tuples_deleted;
|
|
|
|
}
|
|
|
|
tabstat->trans = trans->upper;
|
|
|
|
pfree(trans);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* When there isn't an immediate parent state, we can just
|
|
|
|
* reuse the record instead of going through a palloc/pfree
|
|
|
|
* pushup (this works since it's all in TopTransactionContext
|
|
|
|
* anyway). We have to re-link it into the parent level,
|
|
|
|
* though, and that might mean pushing a new entry into the
|
|
|
|
* pgStatXactStack.
|
|
|
|
*/
|
|
|
|
PgStat_SubXactStatus *upper_xact_state;
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
|
2022-03-21 20:02:25 +01:00
|
|
|
trans->next = upper_xact_state->first;
|
|
|
|
upper_xact_state->first = trans;
|
|
|
|
trans->nest_level = nestDepth - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* On abort, update top-level tabstat counts, then forget the
|
|
|
|
* subtransaction
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* first restore values obliterated by truncate/drop */
|
2022-04-07 06:29:46 +02:00
|
|
|
restore_truncdrop_counters(trans);
|
2022-03-21 20:02:25 +01:00
|
|
|
/* count attempted actions regardless of commit/abort */
|
|
|
|
tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
|
|
|
|
tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
|
|
|
|
tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
|
|
|
|
/* inserted tuples are dead, deleted tuples are unaffected */
|
|
|
|
tabstat->t_counts.t_delta_dead_tuples +=
|
|
|
|
trans->tuples_inserted + trans->tuples_updated;
|
|
|
|
tabstat->trans = trans->upper;
|
|
|
|
pfree(trans);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate 2PC records for all the pending transaction-dependent relation
|
|
|
|
* stats.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
|
|
|
|
{
|
|
|
|
PgStat_TableXactStatus *trans;
|
|
|
|
|
|
|
|
for (trans = xact_state->first; trans != NULL; trans = trans->next)
|
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
|
2022-03-21 20:02:25 +01:00
|
|
|
TwoPhasePgStatRecord record;
|
|
|
|
|
|
|
|
Assert(trans->nest_level == 1);
|
|
|
|
Assert(trans->upper == NULL);
|
|
|
|
tabstat = trans->parent;
|
|
|
|
Assert(tabstat->trans == trans);
|
|
|
|
|
|
|
|
record.tuples_inserted = trans->tuples_inserted;
|
|
|
|
record.tuples_updated = trans->tuples_updated;
|
|
|
|
record.tuples_deleted = trans->tuples_deleted;
|
|
|
|
record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
|
|
|
|
record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
|
|
|
|
record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
|
|
|
|
record.t_id = tabstat->t_id;
|
|
|
|
record.t_shared = tabstat->t_shared;
|
|
|
|
record.t_truncdropped = trans->truncdropped;
|
|
|
|
|
|
|
|
RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
|
|
|
|
&record, sizeof(TwoPhasePgStatRecord));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* All we need do here is unlink the transaction stats state from the
|
|
|
|
* nontransactional state. The nontransactional action counts will be
|
2022-04-06 22:56:06 +02:00
|
|
|
* reported to the stats system immediately, while the effects on live and
|
|
|
|
* dead tuple counts are preserved in the 2PC state file.
|
2022-03-21 20:02:25 +01:00
|
|
|
*
|
|
|
|
* Note: AtEOXact_PgStat_Relations is not called during PREPARE.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
|
|
|
|
{
|
|
|
|
PgStat_TableXactStatus *trans;
|
|
|
|
|
|
|
|
for (trans = xact_state->first; trans != NULL; trans = trans->next)
|
|
|
|
{
|
|
|
|
PgStat_TableStatus *tabstat;
|
|
|
|
|
|
|
|
tabstat = trans->parent;
|
|
|
|
tabstat->trans = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 2PC processing routine for COMMIT PREPARED case.
|
|
|
|
*
|
|
|
|
* Load the saved counts into our local pgstats state.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_twophase_postcommit(TransactionId xid, uint16 info,
|
|
|
|
void *recdata, uint32 len)
|
|
|
|
{
|
|
|
|
TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
|
|
|
|
PgStat_TableStatus *pgstat_info;
|
|
|
|
|
|
|
|
/* Find or create a tabstat entry for the rel */
|
2022-04-07 06:29:46 +02:00
|
|
|
pgstat_info = pgstat_prep_relation_pending(rec->t_id, rec->t_shared);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
/* Same math as in AtEOXact_PgStat, commit case */
|
|
|
|
pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
|
|
|
|
pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
|
|
|
|
pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
|
|
|
|
pgstat_info->t_counts.t_truncdropped = rec->t_truncdropped;
|
|
|
|
if (rec->t_truncdropped)
|
|
|
|
{
|
|
|
|
/* forget live/dead stats seen by backend thus far */
|
|
|
|
pgstat_info->t_counts.t_delta_live_tuples = 0;
|
|
|
|
pgstat_info->t_counts.t_delta_dead_tuples = 0;
|
|
|
|
}
|
|
|
|
pgstat_info->t_counts.t_delta_live_tuples +=
|
|
|
|
rec->tuples_inserted - rec->tuples_deleted;
|
|
|
|
pgstat_info->t_counts.t_delta_dead_tuples +=
|
|
|
|
rec->tuples_updated + rec->tuples_deleted;
|
|
|
|
pgstat_info->t_counts.t_changed_tuples +=
|
|
|
|
rec->tuples_inserted + rec->tuples_updated +
|
|
|
|
rec->tuples_deleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 2PC processing routine for ROLLBACK PREPARED case.
|
|
|
|
*
|
|
|
|
* Load the saved counts into our local pgstats state, but treat them
|
|
|
|
* as aborted.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
pgstat_twophase_postabort(TransactionId xid, uint16 info,
|
|
|
|
void *recdata, uint32 len)
|
|
|
|
{
|
|
|
|
TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
|
|
|
|
PgStat_TableStatus *pgstat_info;
|
|
|
|
|
|
|
|
/* Find or create a tabstat entry for the rel */
|
2022-04-07 06:29:46 +02:00
|
|
|
pgstat_info = pgstat_prep_relation_pending(rec->t_id, rec->t_shared);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
/* Same math as in AtEOXact_PgStat, abort case */
|
|
|
|
if (rec->t_truncdropped)
|
|
|
|
{
|
|
|
|
rec->tuples_inserted = rec->inserted_pre_truncdrop;
|
|
|
|
rec->tuples_updated = rec->updated_pre_truncdrop;
|
|
|
|
rec->tuples_deleted = rec->deleted_pre_truncdrop;
|
|
|
|
}
|
|
|
|
pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
|
|
|
|
pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
|
|
|
|
pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
|
|
|
|
pgstat_info->t_counts.t_delta_dead_tuples +=
|
|
|
|
rec->tuples_inserted + rec->tuples_updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-07 06:29:46 +02:00
|
|
|
* Flush out pending stats for the entry
|
|
|
|
*
|
|
|
|
* If nowait is true, this function returns false if lock could not
|
|
|
|
* immediately acquired, otherwise true is returned.
|
|
|
|
*
|
|
|
|
* Some of the stats are copied to the corresponding pending database stats
|
|
|
|
* entry when successfully flushing.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
2022-04-07 06:29:46 +02:00
|
|
|
bool
|
|
|
|
pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
static const PgStat_TableCounts all_zeroes;
|
2022-04-07 06:29:46 +02:00
|
|
|
Oid dboid;
|
|
|
|
PgStat_TableStatus *lstats; /* pending stats entry */
|
|
|
|
PgStatShared_Relation *shtabstats;
|
|
|
|
PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
|
|
|
|
PgStat_StatDBEntry *dbentry; /* pending database entry */
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
dboid = entry_ref->shared_entry->key.dboid;
|
|
|
|
lstats = (PgStat_TableStatus *) entry_ref->pending;
|
|
|
|
shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
/*
|
2022-04-07 06:29:46 +02:00
|
|
|
* Ignore entries that didn't accumulate any actual counts, such as
|
|
|
|
* indexes that were opened by the planner but not used.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
2022-04-07 06:29:46 +02:00
|
|
|
if (memcmp(&lstats->t_counts, &all_zeroes,
|
|
|
|
sizeof(PgStat_TableCounts)) == 0)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
return true;
|
|
|
|
}
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
if (!pgstat_lock_entry(entry_ref, nowait))
|
|
|
|
return false;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
/* add the values to the shared entry. */
|
|
|
|
tabentry = &shtabstats->stats;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
tabentry->numscans += lstats->t_counts.t_numscans;
|
2022-10-14 20:11:34 +02:00
|
|
|
if (lstats->t_counts.t_numscans)
|
|
|
|
{
|
|
|
|
TimestampTz t = GetCurrentTransactionStopTimestamp();
|
|
|
|
if (t > tabentry->lastscan)
|
|
|
|
tabentry->lastscan = t;
|
|
|
|
}
|
2022-04-07 06:29:46 +02:00
|
|
|
tabentry->tuples_returned += lstats->t_counts.t_tuples_returned;
|
|
|
|
tabentry->tuples_fetched += lstats->t_counts.t_tuples_fetched;
|
|
|
|
tabentry->tuples_inserted += lstats->t_counts.t_tuples_inserted;
|
|
|
|
tabentry->tuples_updated += lstats->t_counts.t_tuples_updated;
|
|
|
|
tabentry->tuples_deleted += lstats->t_counts.t_tuples_deleted;
|
|
|
|
tabentry->tuples_hot_updated += lstats->t_counts.t_tuples_hot_updated;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
/*
|
2022-04-07 06:29:46 +02:00
|
|
|
* If table was truncated/dropped, first reset the live/dead counters.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
2022-04-07 06:29:46 +02:00
|
|
|
if (lstats->t_counts.t_truncdropped)
|
|
|
|
{
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->live_tuples = 0;
|
|
|
|
tabentry->dead_tuples = 0;
|
|
|
|
tabentry->ins_since_vacuum = 0;
|
2022-04-07 06:29:46 +02:00
|
|
|
}
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-12-06 02:46:35 +01:00
|
|
|
tabentry->live_tuples += lstats->t_counts.t_delta_live_tuples;
|
|
|
|
tabentry->dead_tuples += lstats->t_counts.t_delta_dead_tuples;
|
|
|
|
tabentry->mod_since_analyze += lstats->t_counts.t_changed_tuples;
|
|
|
|
tabentry->ins_since_vacuum += lstats->t_counts.t_tuples_inserted;
|
2022-04-07 06:29:46 +02:00
|
|
|
tabentry->blocks_fetched += lstats->t_counts.t_blocks_fetched;
|
|
|
|
tabentry->blocks_hit += lstats->t_counts.t_blocks_hit;
|
|
|
|
|
2022-12-06 02:46:35 +01:00
|
|
|
/* Clamp live_tuples in case of negative delta_live_tuples */
|
|
|
|
tabentry->live_tuples = Max(tabentry->live_tuples, 0);
|
|
|
|
/* Likewise for dead_tuples */
|
|
|
|
tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
|
2022-04-07 06:29:46 +02:00
|
|
|
|
|
|
|
pgstat_unlock_entry(entry_ref);
|
|
|
|
|
|
|
|
/* The entry was successfully flushed, add the same to database stats */
|
|
|
|
dbentry = pgstat_prep_database_pending(dboid);
|
2022-12-07 01:11:48 +01:00
|
|
|
dbentry->tuples_returned += lstats->t_counts.t_tuples_returned;
|
|
|
|
dbentry->tuples_fetched += lstats->t_counts.t_tuples_fetched;
|
|
|
|
dbentry->tuples_inserted += lstats->t_counts.t_tuples_inserted;
|
|
|
|
dbentry->tuples_updated += lstats->t_counts.t_tuples_updated;
|
|
|
|
dbentry->tuples_deleted += lstats->t_counts.t_tuples_deleted;
|
|
|
|
dbentry->blocks_fetched += lstats->t_counts.t_blocks_fetched;
|
|
|
|
dbentry->blocks_hit += lstats->t_counts.t_blocks_hit;
|
2022-04-07 06:29:46 +02:00
|
|
|
|
|
|
|
return true;
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
void
|
|
|
|
pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
if (pending->relation)
|
|
|
|
pgstat_unlink_relation(pending->relation);
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-07 06:29:46 +02:00
|
|
|
* Find or create a PgStat_TableStatus entry for rel. New entry is created and
|
|
|
|
* initialized if not exists.
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
static PgStat_TableStatus *
|
2022-04-07 06:29:46 +02:00
|
|
|
pgstat_prep_relation_pending(Oid rel_id, bool isshared)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
2022-04-07 06:29:46 +02:00
|
|
|
PgStat_EntryRef *entry_ref;
|
|
|
|
PgStat_TableStatus *pending;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
|
|
|
|
isshared ? InvalidOid : MyDatabaseId,
|
|
|
|
rel_id, NULL);
|
|
|
|
pending = entry_ref->pending;
|
|
|
|
pending->t_id = rel_id;
|
|
|
|
pending->t_shared = isshared;
|
2022-03-21 20:02:25 +01:00
|
|
|
|
2022-04-07 06:29:46 +02:00
|
|
|
return pending;
|
2022-03-21 20:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* add a new (sub)transaction state record
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
|
|
|
|
{
|
|
|
|
PgStat_SubXactStatus *xact_state;
|
|
|
|
PgStat_TableXactStatus *trans;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is the first rel to be modified at the current nest level, we
|
|
|
|
* first have to push a transaction stack entry.
|
|
|
|
*/
|
2022-04-07 06:29:46 +02:00
|
|
|
xact_state = pgstat_get_xact_stack_level(nest_level);
|
2022-03-21 20:02:25 +01:00
|
|
|
|
|
|
|
/* Now make a per-table stack entry */
|
|
|
|
trans = (PgStat_TableXactStatus *)
|
|
|
|
MemoryContextAllocZero(TopTransactionContext,
|
|
|
|
sizeof(PgStat_TableXactStatus));
|
|
|
|
trans->nest_level = nest_level;
|
|
|
|
trans->upper = pgstat_info->trans;
|
|
|
|
trans->parent = pgstat_info;
|
|
|
|
trans->next = xact_state->first;
|
|
|
|
xact_state->first = trans;
|
|
|
|
pgstat_info->trans = trans;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a new (sub)transaction record if needed.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
|
|
|
|
{
|
|
|
|
int nest_level = GetCurrentTransactionNestLevel();
|
|
|
|
|
|
|
|
if (pgstat_info->trans == NULL ||
|
|
|
|
pgstat_info->trans->nest_level != nest_level)
|
|
|
|
add_tabstat_xact_level(pgstat_info, nest_level);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Whenever a table is truncated/dropped, we save its i/u/d counters so that
|
|
|
|
* they can be cleared, and if the (sub)xact that executed the truncate/drop
|
|
|
|
* later aborts, the counters can be restored to the saved (pre-truncate/drop)
|
|
|
|
* values.
|
|
|
|
*
|
|
|
|
* Note that for truncate we do this on the first truncate in any particular
|
|
|
|
* subxact level only.
|
|
|
|
*/
|
|
|
|
static void
|
2022-04-07 06:29:46 +02:00
|
|
|
save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
if (!trans->truncdropped || is_drop)
|
|
|
|
{
|
|
|
|
trans->inserted_pre_truncdrop = trans->tuples_inserted;
|
|
|
|
trans->updated_pre_truncdrop = trans->tuples_updated;
|
|
|
|
trans->deleted_pre_truncdrop = trans->tuples_deleted;
|
|
|
|
trans->truncdropped = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-04 21:14:34 +02:00
|
|
|
* restore counters when a truncate aborts
|
2022-03-21 20:02:25 +01:00
|
|
|
*/
|
|
|
|
static void
|
2022-04-07 06:29:46 +02:00
|
|
|
restore_truncdrop_counters(PgStat_TableXactStatus *trans)
|
2022-03-21 20:02:25 +01:00
|
|
|
{
|
|
|
|
if (trans->truncdropped)
|
|
|
|
{
|
|
|
|
trans->tuples_inserted = trans->inserted_pre_truncdrop;
|
|
|
|
trans->tuples_updated = trans->updated_pre_truncdrop;
|
|
|
|
trans->tuples_deleted = trans->deleted_pre_truncdrop;
|
|
|
|
}
|
|
|
|
}
|