autovacuum and TOAST tables

Started by Alvaro Herreraover 17 years ago13 messages
#1Alvaro Herrera
alvherre@commandprompt.com
1 attachment(s)

Hi,

Here's a patch to make autovacuum process TOAST tables separately from
main tables.

The most important change is that when called from autovac, vacuum does
not process the TOAST table at all. It will only do so when the stats
for the TOAST table say that it needs vacuuming. (A user-invoked vacuum
still processes TOAST tables normally.)

Per previous discussion, the autovac code is now doing two passes over
pg_class.

There's two things I'm not happy about in this patch:

1. it uses a List to keep the mapping of heap<->toast Oids. This is
needed to be able to fetch the main rel's pg_autovacuum entry to process
the toast table. This incurs in O(n^2) behavior.

2. the "expected relkind" business is gone; it's not easy to pass the
correct relkind down from autovac, and at the same time have a
reasonable thing to pass down from user-invoked vacuum. Right now what
the patch does is check that the rel to vacuum is either
RELKIND_RELATION or _TOASTVALUE.

(I admit that my unhappiness about the second is mild, though.)

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

autovac-notoast-3.patchtext/x-diff; charset=us-asciiDownload
Index: src/backend/commands/vacuum.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/commands/vacuum.c,v
retrieving revision 1.375
diff -c -p -r1.375 vacuum.c
*** src/backend/commands/vacuum.c	5 Jun 2008 15:47:32 -0000	1.375
--- src/backend/commands/vacuum.c	8 Aug 2008 16:42:02 -0000
*************** static BufferAccessStrategy vac_strategy
*** 213,220 ****
  static List *get_rel_oids(Oid relid, const RangeVar *vacrel,
  			 const char *stmttype);
  static void vac_truncate_clog(TransactionId frozenXID);
! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind,
! 					   bool for_wraparound);
  static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
  static void scan_heap(VRelStats *vacrelstats, Relation onerel,
  		  VacPageList vacuum_pages, VacPageList fraged_pages);
--- 213,220 ----
  static List *get_rel_oids(Oid relid, const RangeVar *vacrel,
  			 const char *stmttype);
  static void vac_truncate_clog(TransactionId frozenXID);
! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast,
! 		   bool for_wraparound);
  static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
  static void scan_heap(VRelStats *vacrelstats, Relation onerel,
  		  VacPageList vacuum_pages, VacPageList fraged_pages);
*************** static Size PageGetFreeSpaceWithFillFact
*** 268,273 ****
--- 268,276 ----
   * OID to be processed, and vacstmt->relation is ignored.  (The non-invalid
   * case is currently only used by autovacuum.)
   *
+  * do_toast is passed as FALSE by autovacuum, because it processes TOAST
+  * tables separately.
+  *
   * for_wraparound is used by autovacuum to let us know when it's forcing
   * a vacuum for wraparound, which should not be auto-cancelled.
   *
*************** static Size PageGetFreeSpaceWithFillFact
*** 281,287 ****
   * at transaction commit.
   */
  void
! vacuum(VacuumStmt *vacstmt, Oid relid,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
  {
  	const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE";
--- 284,290 ----
   * at transaction commit.
   */
  void
! vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
  {
  	const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE";
*************** vacuum(VacuumStmt *vacstmt, Oid relid,
*** 433,439 ****
  			Oid			relid = lfirst_oid(cur);
  
  			if (vacstmt->vacuum)
! 				vacuum_rel(relid, vacstmt, RELKIND_RELATION, for_wraparound);
  
  			if (vacstmt->analyze)
  			{
--- 436,442 ----
  			Oid			relid = lfirst_oid(cur);
  
  			if (vacstmt->vacuum)
! 				vacuum_rel(relid, vacstmt, do_toast, for_wraparound);
  
  			if (vacstmt->analyze)
  			{
*************** vac_truncate_clog(TransactionId frozenXI
*** 975,982 ****
   *		At entry and exit, we are not inside a transaction.
   */
  static void
! vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind,
! 		   bool for_wraparound)
  {
  	LOCKMODE	lmode;
  	Relation	onerel;
--- 978,984 ----
   *		At entry and exit, we are not inside a transaction.
   */
  static void
! vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
  {
  	LOCKMODE	lmode;
  	Relation	onerel;
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1013,1020 ****
  		 * by autovacuum; it's used to avoid cancelling a vacuum that was
  		 * invoked in an emergency.
  		 *
! 		 * Note: this flag remains set until CommitTransaction or
! 		 * AbortTransaction.  We don't want to clear it until we reset
  		 * MyProc->xid/xmin, else OldestXmin might appear to go backwards,
  		 * which is probably Not Good.
  		 */
--- 1015,1022 ----
  		 * by autovacuum; it's used to avoid cancelling a vacuum that was
  		 * invoked in an emergency.
  		 *
! 		 * Note: these flags remain set until CommitTransaction or
! 		 * AbortTransaction.  We don't want to clear them until we reset
  		 * MyProc->xid/xmin, else OldestXmin might appear to go backwards,
  		 * which is probably Not Good.
  		 */
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1087,1096 ****
  	}
  
  	/*
! 	 * Check that it's a plain table; we used to do this in get_rel_oids() but
! 	 * seems safer to check after we've locked the relation.
  	 */
! 	if (onerel->rd_rel->relkind != expected_relkind)
  	{
  		ereport(WARNING,
  				(errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
--- 1089,1099 ----
  	}
  
  	/*
! 	 * Check that it's a vacuumable table; we used to do this in get_rel_oids()
! 	 * but seems safer to check after we've locked the relation.
  	 */
! 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
! 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
  	{
  		ereport(WARNING,
  				(errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1132,1140 ****
  	LockRelationIdForSession(&onerelid, lmode);
  
  	/*
! 	 * Remember the relation's TOAST relation for later
  	 */
! 	toast_relid = onerel->rd_rel->reltoastrelid;
  
  	/*
  	 * Switch to the table owner's userid, so that any index functions are
--- 1135,1147 ----
  	LockRelationIdForSession(&onerelid, lmode);
  
  	/*
! 	 * Remember the relation's TOAST relation for later, if the caller asked
! 	 * us to process it.
  	 */
! 	if (do_toast)
! 		toast_relid = onerel->rd_rel->reltoastrelid;
! 	else
! 		toast_relid = InvalidOid;
  
  	/*
  	 * Switch to the table owner's userid, so that any index functions are
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1173,1179 ****
  	 * totally unimportant for toast relations.
  	 */
  	if (toast_relid != InvalidOid)
! 		vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE, for_wraparound);
  
  	/*
  	 * Now release the session-level lock on the master table.
--- 1180,1186 ----
  	 * totally unimportant for toast relations.
  	 */
  	if (toast_relid != InvalidOid)
! 		vacuum_rel(toast_relid, vacstmt, false, for_wraparound);
  
  	/*
  	 * Now release the session-level lock on the master table.
Index: src/backend/postmaster/autovacuum.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/postmaster/autovacuum.c,v
retrieving revision 1.83
diff -c -p -r1.83 autovacuum.c
*** src/backend/postmaster/autovacuum.c	23 Jul 2008 20:20:10 -0000	1.83
--- src/backend/postmaster/autovacuum.c	8 Aug 2008 15:51:22 -0000
*************** static void autovac_balance_cost(void);
*** 279,285 ****
  static void do_autovacuum(void);
  static void FreeWorkerInfo(int code, Datum arg);
  
! static autovac_table *table_recheck_autovac(Oid relid);
  static void relation_needs_vacanalyze(Oid relid, Form_pg_autovacuum avForm,
  						  Form_pg_class classForm,
  						  PgStat_StatTabEntry *tabentry, bool *dovacuum,
--- 279,285 ----
  static void do_autovacuum(void);
  static void FreeWorkerInfo(int code, Datum arg);
  
! static autovac_table *table_recheck_autovac(Oid relid, List *table_toast_list);
  static void relation_needs_vacanalyze(Oid relid, Form_pg_autovacuum avForm,
  						  Form_pg_class classForm,
  						  PgStat_StatTabEntry *tabentry, bool *dovacuum,
*************** do_autovacuum(void)
*** 1821,1832 ****
  	HeapScanDesc relScan;
  	Form_pg_database dbForm;
  	List	   *table_oids = NIL;
- 	List	   *toast_oids = NIL;
  	List	   *table_toast_list = NIL;
  	ListCell   *volatile cell;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
  	BufferAccessStrategy bstrategy;
  
  	/*
  	 * StartTransactionCommand and CommitTransactionCommand will automatically
--- 1821,1832 ----
  	HeapScanDesc relScan;
  	Form_pg_database dbForm;
  	List	   *table_oids = NIL;
  	List	   *table_toast_list = NIL;
  	ListCell   *volatile cell;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
  	BufferAccessStrategy bstrategy;
+ 	ScanKeyData	key;
  
  	/*
  	 * StartTransactionCommand and CommitTransactionCommand will automatically
*************** do_autovacuum(void)
*** 1885,1908 ****
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
  
  	/*
! 	 * Scan pg_class and determine which tables to vacuum.
  	 *
! 	 * The stats subsystem collects stats for toast tables independently of
! 	 * the stats for their parent tables.  We need to check those stats since
! 	 * in cases with short, wide tables there might be proportionally much
! 	 * more activity in the toast table than in its parent.
  	 *
! 	 * Since we can only issue VACUUM against the parent table, we need to
! 	 * transpose a decision to vacuum a toast table into a decision to vacuum
! 	 * its parent.	There's no point in considering ANALYZE on a toast table,
! 	 * either.	To support this, we keep a list of OIDs of toast tables that
! 	 * need vacuuming alongside the list of regular tables.  Regular tables
! 	 * will be entered into the table list even if they appear not to need
! 	 * vacuuming; we go back and re-mark them after finding all the vacuumable
! 	 * toast tables.
  	 */
! 	relScan = heap_beginscan(classRel, SnapshotNow, 0, NULL);
  
  	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
  	{
  		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
--- 1885,1914 ----
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
  
  	/*
! 	 * Scan pg_class to determine which tables to vacuum.
  	 *
! 	 * We do this in two passes: on the first one we collect the list of
! 	 * plain relations, and on the second one we collect TOAST tables.
! 	 * The reason for doing the second pass is that during it we want to use
! 	 * the main relation's pg_autovacuum entry if the TOAST table does not have
! 	 * any, and we cannot obtain it unless we know beforehand what's the main
! 	 * table OID.
  	 *
! 	 * We need to check TOAST tables separately because in cases with short,
! 	 * wide tables there might be proportionally much more activity in the
! 	 * TOAST table than in its parent.
  	 */
! 	ScanKeyInit(&key,
! 				Anum_pg_class_relkind,
! 				BTEqualStrategyNumber, F_CHAREQ,
! 				CharGetDatum(RELKIND_RELATION));
  
+ 	relScan = heap_beginscan(classRel, SnapshotNow, 1, &key);
+ 
+ 	/*
+ 	 * On the first pass, we collect main tables to vacuum, and also the
+ 	 * main table relid to TOAST relid mapping.
+ 	 */
  	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
  	{
  		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
*************** do_autovacuum(void)
*** 1915,1925 ****
  		bool		wraparound;
  		int			backendID;
  
- 		/* Consider only regular and toast tables. */
- 		if (classForm->relkind != RELKIND_RELATION &&
- 			classForm->relkind != RELKIND_TOASTVALUE)
- 			continue;
- 
  		relid = HeapTupleGetOid(tuple);
  
  		/* Fetch the pg_autovacuum tuple for the relation, if any */
--- 1921,1926 ----
*************** do_autovacuum(void)
*** 1952,1958 ****
  				 * vacuum for wraparound, forcibly drop it.  Otherwise just
  				 * log a complaint.
  				 */
! 				if (wraparound && classForm->relkind == RELKIND_RELATION)
  				{
  					ObjectAddress object;
  
--- 1953,1959 ----
  				 * vacuum for wraparound, forcibly drop it.  Otherwise just
  				 * log a complaint.
  				 */
! 				if (wraparound)
  				{
  					ObjectAddress object;
  
*************** do_autovacuum(void)
*** 1976,1992 ****
  				}
  			}
  		}
! 		else if (classForm->relkind == RELKIND_RELATION)
  		{
  			/* Plain relations that need work are added to table_oids */
  			if (dovacuum || doanalyze)
  				table_oids = lappend_oid(table_oids, relid);
! 			else if (OidIsValid(classForm->reltoastrelid))
  			{
- 				/*
- 				 * If it doesn't appear to need vacuuming, but it has a toast
- 				 * table, remember the association to revisit below.
- 				 */
  				av_relation *rel = palloc(sizeof(av_relation));
  
  				rel->ar_relid = relid;
--- 1977,1998 ----
  				}
  			}
  		}
! 		else
  		{
  			/* Plain relations that need work are added to table_oids */
  			if (dovacuum || doanalyze)
  				table_oids = lappend_oid(table_oids, relid);
! 
! 			/*
! 			 * Remember the association for the second pass.  Note: we must do
! 			 * this even if the table is going to be vacuumed, because we
! 			 * don't automatically vacuum toast tables along the parent table.
! 			 *
! 			 * XXX this is likely to be a performance bottleneck in the case
! 			 * of many tables.  Maybe this should use a hash instead.
! 			 */
! 			if (OidIsValid(classForm->reltoastrelid))
  			{
  				av_relation *rel = palloc(sizeof(av_relation));
  
  				rel->ar_relid = relid;
*************** do_autovacuum(void)
*** 1995,2040 ****
  				table_toast_list = lappend(table_toast_list, rel);
  			}
  		}
- 		else
- 		{
- 			/* TOAST relations that need vacuum are added to toast_oids */
- 			if (dovacuum)
- 				toast_oids = lappend_oid(toast_oids, relid);
- 		}
  
  		if (HeapTupleIsValid(avTup))
  			heap_freetuple(avTup);
  	}
  
  	heap_endscan(relScan);
- 	heap_close(avRel, AccessShareLock);
- 	heap_close(classRel, AccessShareLock);
  
! 	/*
! 	 * Add to the list of tables to vacuum, the OIDs of the tables that
! 	 * correspond to the saved OIDs of toast tables needing vacuum.
! 	 */
! 	foreach(cell, toast_oids)
  	{
! 		Oid			toastoid = lfirst_oid(cell);
! 		ListCell   *cell2;
  
! 		foreach(cell2, table_toast_list)
  		{
! 			av_relation *ar = lfirst(cell2);
  
! 			if (ar->ar_toastrelid == toastoid)
  			{
! 				table_oids = lappend_oid(table_oids, ar->ar_relid);
! 				break;
  			}
  		}
  	}
  
  	list_free_deep(table_toast_list);
  	table_toast_list = NIL;
- 	list_free(toast_oids);
- 	toast_oids = NIL;
  
  	/*
  	 * Create a buffer access strategy object for VACUUM to use.  We want to
--- 2001,2083 ----
  				table_toast_list = lappend(table_toast_list, rel);
  			}
  		}
  
  		if (HeapTupleIsValid(avTup))
  			heap_freetuple(avTup);
  	}
  
  	heap_endscan(relScan);
  
! 	/* second pass: check TOAST tables */
! 	ScanKeyInit(&key,
! 				Anum_pg_class_relkind,
! 				BTEqualStrategyNumber, F_CHAREQ,
! 				CharGetDatum(RELKIND_TOASTVALUE));
! 
! 	relScan = heap_beginscan(classRel, SnapshotNow, 1, &key);
! 	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
  	{
! 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
! 		Form_pg_autovacuum avForm = NULL;
! 		PgStat_StatTabEntry *tabentry;
! 		HeapTuple   avTup;
! 		Oid         relid;
! 		bool		dovacuum;
! 		bool		doanalyze;
! 		bool		wraparound;
! 
! 		/*
! 		 * Skip temp tables (i.e. those in temp namespaces).  We cannot safely
! 		 * process other backends' temp tables.
! 		 */
! 		if (isAnyTempNamespace(classForm->relnamespace))
! 			continue;
  
! 		relid = HeapTupleGetOid(tuple);
! 
! 		/*
! 		 * Fetch the pg_autovacuum tuple for this rel -- either the TOAST
! 		 * table's itself, or failing that, the main rel's.
! 		 */
! 		avTup = get_pg_autovacuum_tuple_relid(avRel, relid);
! 
! 		if (!HeapTupleIsValid(avTup))
  		{
! 			ListCell   *cell;
  
! 			foreach (cell, table_toast_list)
  			{
! 				av_relation    *map = lfirst(cell);
! 
! 				if (map->ar_toastrelid == relid)
! 				{
! 					avTup = get_pg_autovacuum_tuple_relid(avRel, map->ar_relid);
! 					break;
! 				}
  			}
  		}
+ 
+ 		if (HeapTupleIsValid(avTup))
+ 			avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
+ 
+ 		/* Fetch the pgstat entry for this table */
+ 		tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared,
+ 											 shared, dbentry);
+ 
+ 		relation_needs_vacanalyze(relid, avForm, classForm, tabentry,
+ 								  &dovacuum, &doanalyze, &wraparound);
+ 
+ 		/* ignore analyze for toast tables */
+ 		if (dovacuum)
+ 			table_oids = lappend_oid(table_oids, relid);
  	}
  
+ 	heap_endscan(relScan);
+ 	heap_close(avRel, AccessShareLock);
+ 	heap_close(classRel, AccessShareLock);
+ 
  	list_free_deep(table_toast_list);
  	table_toast_list = NIL;
  
  	/*
  	 * Create a buffer access strategy object for VACUUM to use.  We want to
*************** do_autovacuum(void)
*** 2118,2124 ****
  		 * vacuumed in the last 500ms (PGSTAT_STAT_INTERVAL).  This is a bug.
  		 */
  		MemoryContextSwitchTo(AutovacMemCxt);
! 		tab = table_recheck_autovac(relid);
  		if (tab == NULL)
  		{
  			/* someone else vacuumed the table */
--- 2161,2167 ----
  		 * vacuumed in the last 500ms (PGSTAT_STAT_INTERVAL).  This is a bug.
  		 */
  		MemoryContextSwitchTo(AutovacMemCxt);
! 		tab = table_recheck_autovac(relid, table_toast_list);
  		if (tab == NULL)
  		{
  			/* someone else vacuumed the table */
*************** get_pgstat_tabentry_relid(Oid relid, boo
*** 2297,2310 ****
  /*
   * table_recheck_autovac
   *
!  * Recheck whether a plain table still needs vacuum or analyze; be it because
!  * it does directly, or because its TOAST table does.  Return value is a valid
!  * autovac_table pointer if it does, NULL otherwise.
   *
   * Note that the returned autovac_table does not have the name fields set.
   */
  static autovac_table *
! table_recheck_autovac(Oid relid)
  {
  	Form_pg_autovacuum avForm = NULL;
  	Form_pg_class classForm;
--- 2340,2352 ----
  /*
   * table_recheck_autovac
   *
!  * Recheck whether a table still needs vacuum or analyze.  Return value is a
!  * valid autovac_table pointer if it does, NULL otherwise.
   *
   * Note that the returned autovac_table does not have the name fields set.
   */
  static autovac_table *
! table_recheck_autovac(Oid relid, List *table_toast_list)
  {
  	Form_pg_autovacuum avForm = NULL;
  	Form_pg_class classForm;
*************** table_recheck_autovac(Oid relid)
*** 2315,2325 ****
  	bool		doanalyze;
  	autovac_table *tab = NULL;
  	PgStat_StatTabEntry *tabentry;
- 	bool		doit = false;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
! 	bool		wraparound,
! 				toast_wraparound = false;
  
  	/* use fresh stats */
  	autovac_refresh_stats();
--- 2357,2365 ----
  	bool		doanalyze;
  	autovac_table *tab = NULL;
  	PgStat_StatTabEntry *tabentry;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
! 	bool		wraparound;
  
  	/* use fresh stats */
  	autovac_refresh_stats();
*************** table_recheck_autovac(Oid relid)
*** 2335,2343 ****
  		return NULL;
  	classForm = (Form_pg_class) GETSTRUCT(classTup);
  
! 	/* fetch the pg_autovacuum entry, if any */
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
  	avTup = get_pg_autovacuum_tuple_relid(avRel, relid);
  	if (HeapTupleIsValid(avTup))
  		avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
  
--- 2375,2404 ----
  		return NULL;
  	classForm = (Form_pg_class) GETSTRUCT(classTup);
  
! 	/*
! 	 * Fetch the pg_autovacuum entry, if any.  For a toast table, also try the
! 	 * main rel's pg_autovacuum entry if there isn't one for the TOAST table
! 	 * itself.
! 	 */
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
  	avTup = get_pg_autovacuum_tuple_relid(avRel, relid);
+ 	if (!HeapTupleIsValid(avTup) &&
+ 		classForm->relkind == RELKIND_TOASTVALUE)
+ 	{
+ 		ListCell   *cell;
+ 
+ 		foreach (cell, table_toast_list)
+ 		{
+ 			av_relation	   *map = lfirst(cell);
+ 
+ 			if (map->ar_toastrelid == relid)
+ 			{
+ 				avTup = get_pg_autovacuum_tuple_relid(avRel, map->ar_relid);
+ 				break;
+ 			}
+ 		}
+ 	}
+ 
  	if (HeapTupleIsValid(avTup))
  		avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
  
*************** table_recheck_autovac(Oid relid)
*** 2348,2398 ****
  	relation_needs_vacanalyze(relid, avForm, classForm, tabentry,
  							  &dovacuum, &doanalyze, &wraparound);
  
! 	/* OK, it needs vacuum by itself */
! 	if (dovacuum)
! 		doit = true;
! 	/* it doesn't need vacuum, but what about it's TOAST table? */
! 	else if (OidIsValid(classForm->reltoastrelid))
! 	{
! 		Oid			toastrelid = classForm->reltoastrelid;
! 		HeapTuple	toastClassTup;
! 
! 		toastClassTup = SearchSysCacheCopy(RELOID,
! 										   ObjectIdGetDatum(toastrelid),
! 										   0, 0, 0);
! 		if (HeapTupleIsValid(toastClassTup))
! 		{
! 			bool		toast_dovacuum;
! 			bool		toast_doanalyze;
! 			bool		toast_wraparound;
! 			Form_pg_class toastClassForm;
! 			PgStat_StatTabEntry *toasttabentry;
! 
! 			toastClassForm = (Form_pg_class) GETSTRUCT(toastClassTup);
! 			toasttabentry = get_pgstat_tabentry_relid(toastrelid,
! 												 toastClassForm->relisshared,
! 													  shared, dbentry);
! 
! 			/* note we use the pg_autovacuum entry for the main table */
! 			relation_needs_vacanalyze(toastrelid, avForm,
! 									  toastClassForm, toasttabentry,
! 									  &toast_dovacuum, &toast_doanalyze,
! 									  &toast_wraparound);
! 			/* we only consider VACUUM for toast tables */
! 			if (toast_dovacuum)
! 			{
! 				dovacuum = true;
! 				doit = true;
! 			}
! 
! 			heap_freetuple(toastClassTup);
! 		}
! 	}
! 
! 	if (doanalyze)
! 		doit = true;
  
! 	if (doit)
  	{
  		int			freeze_min_age;
  		int			vac_cost_limit;
--- 2409,2420 ----
  	relation_needs_vacanalyze(relid, avForm, classForm, tabentry,
  							  &dovacuum, &doanalyze, &wraparound);
  
! 	/* ignore ANALYZE for toast tables */
! 	if (classForm->relkind == RELKIND_TOASTVALUE)
! 		doanalyze = false;
  
! 	/* OK, it needs something done */
! 	if (doanalyze || dovacuum)
  	{
  		int			freeze_min_age;
  		int			vac_cost_limit;
*************** table_recheck_autovac(Oid relid)
*** 2439,2445 ****
  		tab->at_freeze_min_age = freeze_min_age;
  		tab->at_vacuum_cost_limit = vac_cost_limit;
  		tab->at_vacuum_cost_delay = vac_cost_delay;
! 		tab->at_wraparound = wraparound || toast_wraparound;
  		tab->at_relname = NULL;
  		tab->at_nspname = NULL;
  		tab->at_datname = NULL;
--- 2461,2467 ----
  		tab->at_freeze_min_age = freeze_min_age;
  		tab->at_vacuum_cost_limit = vac_cost_limit;
  		tab->at_vacuum_cost_delay = vac_cost_delay;
! 		tab->at_wraparound = wraparound;
  		tab->at_relname = NULL;
  		tab->at_nspname = NULL;
  		tab->at_datname = NULL;
*************** autovacuum_do_vac_analyze(autovac_table 
*** 2633,2639 ****
  	/* Let pgstat know what we're doing */
  	autovac_report_activity(tab);
  
! 	vacuum(&vacstmt, tab->at_relid, bstrategy, tab->at_wraparound, true);
  }
  
  /*
--- 2655,2661 ----
  	/* Let pgstat know what we're doing */
  	autovac_report_activity(tab);
  
! 	vacuum(&vacstmt, tab->at_relid, false, bstrategy, tab->at_wraparound, true);
  }
  
  /*
Index: src/backend/tcop/utility.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/tcop/utility.c,v
retrieving revision 1.295
diff -c -p -r1.295 utility.c
*** src/backend/tcop/utility.c	18 Jul 2008 20:26:06 -0000	1.295
--- src/backend/tcop/utility.c	8 Aug 2008 15:51:22 -0000
*************** ProcessUtility(Node *parsetree,
*** 836,842 ****
  			break;
  
  		case T_VacuumStmt:
! 			vacuum((VacuumStmt *) parsetree, InvalidOid, NULL, false,
  				   isTopLevel);
  			break;
  
--- 836,842 ----
  			break;
  
  		case T_VacuumStmt:
! 			vacuum((VacuumStmt *) parsetree, InvalidOid, true, NULL, false,
  				   isTopLevel);
  			break;
  
Index: src/include/commands/vacuum.h
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/include/commands/vacuum.h,v
retrieving revision 1.79
diff -c -p -r1.79 vacuum.h
*** src/include/commands/vacuum.h	1 Jul 2008 10:33:09 -0000	1.79
--- src/include/commands/vacuum.h	8 Aug 2008 15:51:22 -0000
*************** extern int	vacuum_freeze_min_age;
*** 125,131 ****
  
  
  /* in commands/vacuum.c */
! extern void vacuum(VacuumStmt *vacstmt, Oid relid,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
  extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
  				 int *nindexes, Relation **Irel);
--- 125,131 ----
  
  
  /* in commands/vacuum.c */
! extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
  extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
  				 int *nindexes, Relation **Irel);
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#1)
Re: autovacuum and TOAST tables

Alvaro Herrera <alvherre@commandprompt.com> writes:

There's two things I'm not happy about in this patch:

1. it uses a List to keep the mapping of heap<->toast Oids. This is
needed to be able to fetch the main rel's pg_autovacuum entry to process
the toast table. This incurs in O(n^2) behavior.

Use a dynahash table instead?

regards, tom lane

#3Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#2)
1 attachment(s)
Re: autovacuum and TOAST tables

Tom Lane wrote:

Alvaro Herrera <alvherre@commandprompt.com> writes:

There's two things I'm not happy about in this patch:

1. it uses a List to keep the mapping of heap<->toast Oids. This is
needed to be able to fetch the main rel's pg_autovacuum entry to process
the toast table. This incurs in O(n^2) behavior.

Use a dynahash table instead?

Right, the attached patch does that.

Note that this patch allows a toast table to be vacuumed by the user:

alvherre=# vacuum pg_toast.pg_toast_39970;
VACUUM

I don't have a problem with that, but if anyone thinks this is not a
good idea, please speak up.

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

autovac-notoast-4.patchtext/x-diff; charset=us-asciiDownload
Index: src/backend/commands/vacuum.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/commands/vacuum.c,v
retrieving revision 1.375
diff -c -p -r1.375 vacuum.c
*** src/backend/commands/vacuum.c	5 Jun 2008 15:47:32 -0000	1.375
--- src/backend/commands/vacuum.c	8 Aug 2008 16:42:02 -0000
*************** static BufferAccessStrategy vac_strategy
*** 213,220 ****
  static List *get_rel_oids(Oid relid, const RangeVar *vacrel,
  			 const char *stmttype);
  static void vac_truncate_clog(TransactionId frozenXID);
! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind,
! 					   bool for_wraparound);
  static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
  static void scan_heap(VRelStats *vacrelstats, Relation onerel,
  		  VacPageList vacuum_pages, VacPageList fraged_pages);
--- 213,220 ----
  static List *get_rel_oids(Oid relid, const RangeVar *vacrel,
  			 const char *stmttype);
  static void vac_truncate_clog(TransactionId frozenXID);
! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast,
! 		   bool for_wraparound);
  static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
  static void scan_heap(VRelStats *vacrelstats, Relation onerel,
  		  VacPageList vacuum_pages, VacPageList fraged_pages);
*************** static Size PageGetFreeSpaceWithFillFact
*** 268,273 ****
--- 268,276 ----
   * OID to be processed, and vacstmt->relation is ignored.  (The non-invalid
   * case is currently only used by autovacuum.)
   *
+  * do_toast is passed as FALSE by autovacuum, because it processes TOAST
+  * tables separately.
+  *
   * for_wraparound is used by autovacuum to let us know when it's forcing
   * a vacuum for wraparound, which should not be auto-cancelled.
   *
*************** static Size PageGetFreeSpaceWithFillFact
*** 281,287 ****
   * at transaction commit.
   */
  void
! vacuum(VacuumStmt *vacstmt, Oid relid,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
  {
  	const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE";
--- 284,290 ----
   * at transaction commit.
   */
  void
! vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
  {
  	const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE";
*************** vacuum(VacuumStmt *vacstmt, Oid relid,
*** 433,439 ****
  			Oid			relid = lfirst_oid(cur);
  
  			if (vacstmt->vacuum)
! 				vacuum_rel(relid, vacstmt, RELKIND_RELATION, for_wraparound);
  
  			if (vacstmt->analyze)
  			{
--- 436,442 ----
  			Oid			relid = lfirst_oid(cur);
  
  			if (vacstmt->vacuum)
! 				vacuum_rel(relid, vacstmt, do_toast, for_wraparound);
  
  			if (vacstmt->analyze)
  			{
*************** vac_truncate_clog(TransactionId frozenXI
*** 975,982 ****
   *		At entry and exit, we are not inside a transaction.
   */
  static void
! vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind,
! 		   bool for_wraparound)
  {
  	LOCKMODE	lmode;
  	Relation	onerel;
--- 978,984 ----
   *		At entry and exit, we are not inside a transaction.
   */
  static void
! vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
  {
  	LOCKMODE	lmode;
  	Relation	onerel;
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1013,1020 ****
  		 * by autovacuum; it's used to avoid cancelling a vacuum that was
  		 * invoked in an emergency.
  		 *
! 		 * Note: this flag remains set until CommitTransaction or
! 		 * AbortTransaction.  We don't want to clear it until we reset
  		 * MyProc->xid/xmin, else OldestXmin might appear to go backwards,
  		 * which is probably Not Good.
  		 */
--- 1015,1022 ----
  		 * by autovacuum; it's used to avoid cancelling a vacuum that was
  		 * invoked in an emergency.
  		 *
! 		 * Note: these flags remain set until CommitTransaction or
! 		 * AbortTransaction.  We don't want to clear them until we reset
  		 * MyProc->xid/xmin, else OldestXmin might appear to go backwards,
  		 * which is probably Not Good.
  		 */
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1087,1096 ****
  	}
  
  	/*
! 	 * Check that it's a plain table; we used to do this in get_rel_oids() but
! 	 * seems safer to check after we've locked the relation.
  	 */
! 	if (onerel->rd_rel->relkind != expected_relkind)
  	{
  		ereport(WARNING,
  				(errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
--- 1089,1099 ----
  	}
  
  	/*
! 	 * Check that it's a vacuumable table; we used to do this in get_rel_oids()
! 	 * but seems safer to check after we've locked the relation.
  	 */
! 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
! 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
  	{
  		ereport(WARNING,
  				(errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1132,1140 ****
  	LockRelationIdForSession(&onerelid, lmode);
  
  	/*
! 	 * Remember the relation's TOAST relation for later
  	 */
! 	toast_relid = onerel->rd_rel->reltoastrelid;
  
  	/*
  	 * Switch to the table owner's userid, so that any index functions are
--- 1135,1147 ----
  	LockRelationIdForSession(&onerelid, lmode);
  
  	/*
! 	 * Remember the relation's TOAST relation for later, if the caller asked
! 	 * us to process it.
  	 */
! 	if (do_toast)
! 		toast_relid = onerel->rd_rel->reltoastrelid;
! 	else
! 		toast_relid = InvalidOid;
  
  	/*
  	 * Switch to the table owner's userid, so that any index functions are
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 1173,1179 ****
  	 * totally unimportant for toast relations.
  	 */
  	if (toast_relid != InvalidOid)
! 		vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE, for_wraparound);
  
  	/*
  	 * Now release the session-level lock on the master table.
--- 1180,1186 ----
  	 * totally unimportant for toast relations.
  	 */
  	if (toast_relid != InvalidOid)
! 		vacuum_rel(toast_relid, vacstmt, false, for_wraparound);
  
  	/*
  	 * Now release the session-level lock on the master table.
Index: src/backend/postmaster/autovacuum.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/postmaster/autovacuum.c,v
retrieving revision 1.83
diff -c -p -r1.83 autovacuum.c
*** src/backend/postmaster/autovacuum.c	23 Jul 2008 20:20:10 -0000	1.83
--- src/backend/postmaster/autovacuum.c	8 Aug 2008 20:33:24 -0000
***************
*** 93,98 ****
--- 93,99 ----
  #include "storage/procarray.h"
  #include "storage/sinvaladt.h"
  #include "tcop/tcopprot.h"
+ #include "utils/dynahash.h"
  #include "utils/flatfiles.h"
  #include "utils/fmgroids.h"
  #include "utils/lsyscache.h"
*************** typedef struct avw_dbase
*** 161,168 ****
  /* struct to keep track of tables to vacuum and/or analyze, in 1st pass */
  typedef struct av_relation
  {
  	Oid			ar_relid;
- 	Oid			ar_toastrelid;
  } av_relation;
  
  /* struct to keep track of tables to vacuum and/or analyze, after rechecking */
--- 162,169 ----
  /* struct to keep track of tables to vacuum and/or analyze, in 1st pass */
  typedef struct av_relation
  {
+ 	Oid			ar_toastrelid; /* must be first -- used as hash key */
  	Oid			ar_relid;
  } av_relation;
  
  /* struct to keep track of tables to vacuum and/or analyze, after rechecking */
*************** static void autovac_balance_cost(void);
*** 279,285 ****
  static void do_autovacuum(void);
  static void FreeWorkerInfo(int code, Datum arg);
  
! static autovac_table *table_recheck_autovac(Oid relid);
  static void relation_needs_vacanalyze(Oid relid, Form_pg_autovacuum avForm,
  						  Form_pg_class classForm,
  						  PgStat_StatTabEntry *tabentry, bool *dovacuum,
--- 280,286 ----
  static void do_autovacuum(void);
  static void FreeWorkerInfo(int code, Datum arg);
  
! static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map);
  static void relation_needs_vacanalyze(Oid relid, Form_pg_autovacuum avForm,
  						  Form_pg_class classForm,
  						  PgStat_StatTabEntry *tabentry, bool *dovacuum,
*************** static void relation_needs_vacanalyze(Oi
*** 287,293 ****
  
  static void autovacuum_do_vac_analyze(autovac_table *tab,
  						  BufferAccessStrategy bstrategy);
! static HeapTuple get_pg_autovacuum_tuple_relid(Relation avRel, Oid relid);
  static PgStat_StatTabEntry *get_pgstat_tabentry_relid(Oid relid, bool isshared,
  						  PgStat_StatDBEntry *shared,
  						  PgStat_StatDBEntry *dbentry);
--- 288,295 ----
  
  static void autovacuum_do_vac_analyze(autovac_table *tab,
  						  BufferAccessStrategy bstrategy);
! static HeapTuple get_pg_autovacuum_tuple_relid(Relation avRel, Oid relid,
! 											   HTAB *table_toast_map);
  static PgStat_StatTabEntry *get_pgstat_tabentry_relid(Oid relid, bool isshared,
  						  PgStat_StatDBEntry *shared,
  						  PgStat_StatDBEntry *dbentry);
*************** do_autovacuum(void)
*** 1821,1832 ****
  	HeapScanDesc relScan;
  	Form_pg_database dbForm;
  	List	   *table_oids = NIL;
! 	List	   *toast_oids = NIL;
! 	List	   *table_toast_list = NIL;
  	ListCell   *volatile cell;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
  	BufferAccessStrategy bstrategy;
  
  	/*
  	 * StartTransactionCommand and CommitTransactionCommand will automatically
--- 1823,1835 ----
  	HeapScanDesc relScan;
  	Form_pg_database dbForm;
  	List	   *table_oids = NIL;
! 	HASHCTL		ctl;
! 	HTAB	   *table_toast_map;
  	ListCell   *volatile cell;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
  	BufferAccessStrategy bstrategy;
+ 	ScanKeyData	key;
  
  	/*
  	 * StartTransactionCommand and CommitTransactionCommand will automatically
*************** do_autovacuum(void)
*** 1884,1908 ****
  	classRel = heap_open(RelationRelationId, AccessShareLock);
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
  
  	/*
! 	 * Scan pg_class and determine which tables to vacuum.
  	 *
! 	 * The stats subsystem collects stats for toast tables independently of
! 	 * the stats for their parent tables.  We need to check those stats since
! 	 * in cases with short, wide tables there might be proportionally much
! 	 * more activity in the toast table than in its parent.
  	 *
! 	 * Since we can only issue VACUUM against the parent table, we need to
! 	 * transpose a decision to vacuum a toast table into a decision to vacuum
! 	 * its parent.	There's no point in considering ANALYZE on a toast table,
! 	 * either.	To support this, we keep a list of OIDs of toast tables that
! 	 * need vacuuming alongside the list of regular tables.  Regular tables
! 	 * will be entered into the table list even if they appear not to need
! 	 * vacuuming; we go back and re-mark them after finding all the vacuumable
! 	 * toast tables.
  	 */
! 	relScan = heap_beginscan(classRel, SnapshotNow, 0, NULL);
  
  	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
  	{
  		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
--- 1887,1928 ----
  	classRel = heap_open(RelationRelationId, AccessShareLock);
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
  
+ 	/* create hash table for toast <-> main relid mapping */
+ 	MemSet(&ctl, 0, sizeof(ctl));
+ 	ctl.keysize = sizeof(Oid);
+ 	ctl.entrysize = sizeof(Oid) * 2;
+ 	ctl.hash = oid_hash;
+ 
+ 	table_toast_map = hash_create("TOAST to main relid map",
+ 								  100,
+ 								  &ctl,
+ 								  HASH_ELEM | HASH_FUNCTION);
+ 
  	/*
! 	 * Scan pg_class to determine which tables to vacuum.
  	 *
! 	 * We do this in two passes: on the first one we collect the list of
! 	 * plain relations, and on the second one we collect TOAST tables.
! 	 * The reason for doing the second pass is that during it we want to use
! 	 * the main relation's pg_autovacuum entry if the TOAST table does not have
! 	 * any, and we cannot obtain it unless we know beforehand what's the main
! 	 * table OID.
  	 *
! 	 * We need to check TOAST tables separately because in cases with short,
! 	 * wide tables there might be proportionally much more activity in the
! 	 * TOAST table than in its parent.
  	 */
! 	ScanKeyInit(&key,
! 				Anum_pg_class_relkind,
! 				BTEqualStrategyNumber, F_CHAREQ,
! 				CharGetDatum(RELKIND_RELATION));
! 
! 	relScan = heap_beginscan(classRel, SnapshotNow, 1, &key);
  
+ 	/*
+ 	 * On the first pass, we collect main tables to vacuum, and also the
+ 	 * main table relid to TOAST relid mapping.
+ 	 */
  	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
  	{
  		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
*************** do_autovacuum(void)
*** 1915,1929 ****
  		bool		wraparound;
  		int			backendID;
  
- 		/* Consider only regular and toast tables. */
- 		if (classForm->relkind != RELKIND_RELATION &&
- 			classForm->relkind != RELKIND_TOASTVALUE)
- 			continue;
- 
  		relid = HeapTupleGetOid(tuple);
  
  		/* Fetch the pg_autovacuum tuple for the relation, if any */
! 		avTup = get_pg_autovacuum_tuple_relid(avRel, relid);
  		if (HeapTupleIsValid(avTup))
  			avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
  
--- 1935,1944 ----
  		bool		wraparound;
  		int			backendID;
  
  		relid = HeapTupleGetOid(tuple);
  
  		/* Fetch the pg_autovacuum tuple for the relation, if any */
! 		avTup = get_pg_autovacuum_tuple_relid(avRel, relid, NULL);
  		if (HeapTupleIsValid(avTup))
  			avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
  
*************** do_autovacuum(void)
*** 1952,1958 ****
  				 * vacuum for wraparound, forcibly drop it.  Otherwise just
  				 * log a complaint.
  				 */
! 				if (wraparound && classForm->relkind == RELKIND_RELATION)
  				{
  					ObjectAddress object;
  
--- 1967,1973 ----
  				 * vacuum for wraparound, forcibly drop it.  Otherwise just
  				 * log a complaint.
  				 */
! 				if (wraparound)
  				{
  					ObjectAddress object;
  
*************** do_autovacuum(void)
*** 1976,2040 ****
  				}
  			}
  		}
! 		else if (classForm->relkind == RELKIND_RELATION)
  		{
  			/* Plain relations that need work are added to table_oids */
  			if (dovacuum || doanalyze)
  				table_oids = lappend_oid(table_oids, relid);
! 			else if (OidIsValid(classForm->reltoastrelid))
  			{
! 				/*
! 				 * If it doesn't appear to need vacuuming, but it has a toast
! 				 * table, remember the association to revisit below.
! 				 */
! 				av_relation *rel = palloc(sizeof(av_relation));
  
! 				rel->ar_relid = relid;
! 				rel->ar_toastrelid = classForm->reltoastrelid;
  
! 				table_toast_list = lappend(table_toast_list, rel);
  			}
  		}
- 		else
- 		{
- 			/* TOAST relations that need vacuum are added to toast_oids */
- 			if (dovacuum)
- 				toast_oids = lappend_oid(toast_oids, relid);
- 		}
  
  		if (HeapTupleIsValid(avTup))
  			heap_freetuple(avTup);
  	}
  
  	heap_endscan(relScan);
- 	heap_close(avRel, AccessShareLock);
- 	heap_close(classRel, AccessShareLock);
  
! 	/*
! 	 * Add to the list of tables to vacuum, the OIDs of the tables that
! 	 * correspond to the saved OIDs of toast tables needing vacuum.
! 	 */
! 	foreach(cell, toast_oids)
  	{
! 		Oid			toastoid = lfirst_oid(cell);
! 		ListCell   *cell2;
  
! 		foreach(cell2, table_toast_list)
! 		{
! 			av_relation *ar = lfirst(cell2);
  
! 			if (ar->ar_toastrelid == toastoid)
! 			{
! 				table_oids = lappend_oid(table_oids, ar->ar_relid);
! 				break;
! 			}
! 		}
  	}
  
! 	list_free_deep(table_toast_list);
! 	table_toast_list = NIL;
! 	list_free(toast_oids);
! 	toast_oids = NIL;
  
  	/*
  	 * Create a buffer access strategy object for VACUUM to use.  We want to
--- 1991,2078 ----
  				}
  			}
  		}
! 		else
  		{
  			/* Plain relations that need work are added to table_oids */
  			if (dovacuum || doanalyze)
  				table_oids = lappend_oid(table_oids, relid);
! 
! 			/*
! 			 * Remember the association for the second pass.  Note: we must do
! 			 * this even if the table is going to be vacuumed, because we
! 			 * don't automatically vacuum toast tables along the parent table.
! 			 */
! 			if (OidIsValid(classForm->reltoastrelid))
  			{
! 				av_relation *hentry;
! 				bool		found;
  
! 				hentry = hash_search(table_toast_map,
! 									 (void *) &classForm->reltoastrelid,
! 									 HASH_ENTER, &found);
  
! 				if (!found)
! 				{
! 					/* hash_search already filled in the key */
! 					hentry->ar_relid = relid;
! 				}
  			}
  		}
  
  		if (HeapTupleIsValid(avTup))
  			heap_freetuple(avTup);
  	}
  
  	heap_endscan(relScan);
  
! 	/* second pass: check TOAST tables */
! 	ScanKeyInit(&key,
! 				Anum_pg_class_relkind,
! 				BTEqualStrategyNumber, F_CHAREQ,
! 				CharGetDatum(RELKIND_TOASTVALUE));
! 
! 	relScan = heap_beginscan(classRel, SnapshotNow, 1, &key);
! 	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
  	{
! 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
! 		Form_pg_autovacuum avForm = NULL;
! 		PgStat_StatTabEntry *tabentry;
! 		HeapTuple   avTup;
! 		Oid         relid;
! 		bool		dovacuum;
! 		bool		doanalyze;
! 		bool		wraparound;
  
! 		/*
! 		 * Skip temp tables (i.e. those in temp namespaces).  We cannot safely
! 		 * process other backends' temp tables.
! 		 */
! 		if (isAnyTempNamespace(classForm->relnamespace))
! 			continue;
  
! 		relid = HeapTupleGetOid(tuple);
! 
! 		/* Fetch the pg_autovacuum tuple for this rel */
! 		avTup = get_pg_autovacuum_tuple_relid(avRel, relid, table_toast_map);
! 
! 		if (HeapTupleIsValid(avTup))
! 			avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
! 
! 		/* Fetch the pgstat entry for this table */
! 		tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared,
! 											 shared, dbentry);
! 
! 		relation_needs_vacanalyze(relid, avForm, classForm, tabentry,
! 								  &dovacuum, &doanalyze, &wraparound);
! 
! 		/* ignore analyze for toast tables */
! 		if (dovacuum)
! 			table_oids = lappend_oid(table_oids, relid);
  	}
  
! 	heap_endscan(relScan);
! 	heap_close(avRel, AccessShareLock);
! 	heap_close(classRel, AccessShareLock);
  
  	/*
  	 * Create a buffer access strategy object for VACUUM to use.  We want to
*************** do_autovacuum(void)
*** 2118,2124 ****
  		 * vacuumed in the last 500ms (PGSTAT_STAT_INTERVAL).  This is a bug.
  		 */
  		MemoryContextSwitchTo(AutovacMemCxt);
! 		tab = table_recheck_autovac(relid);
  		if (tab == NULL)
  		{
  			/* someone else vacuumed the table */
--- 2156,2162 ----
  		 * vacuumed in the last 500ms (PGSTAT_STAT_INTERVAL).  This is a bug.
  		 */
  		MemoryContextSwitchTo(AutovacMemCxt);
! 		tab = table_recheck_autovac(relid, table_toast_map);
  		if (tab == NULL)
  		{
  			/* someone else vacuumed the table */
*************** deleted:
*** 2244,2252 ****
  /*
   * Returns a copy of the pg_autovacuum tuple for the given relid, or NULL if
   * there isn't any.  avRel is pg_autovacuum, already open and suitably locked.
   */
  static HeapTuple
! get_pg_autovacuum_tuple_relid(Relation avRel, Oid relid)
  {
  	ScanKeyData entry[1];
  	SysScanDesc avScan;
--- 2282,2295 ----
  /*
   * Returns a copy of the pg_autovacuum tuple for the given relid, or NULL if
   * there isn't any.  avRel is pg_autovacuum, already open and suitably locked.
+  *
+  * If table_toast_map is not null, use it to find an alternative OID with which
+  * to search a pg_autovacuum entry, if the passed relid does not yield one
+  * directly.
   */
  static HeapTuple
! get_pg_autovacuum_tuple_relid(Relation avRel, Oid relid,
! 							  HTAB *table_toast_map)
  {
  	ScanKeyData entry[1];
  	SysScanDesc avScan;
*************** get_pg_autovacuum_tuple_relid(Relation a
*** 2267,2272 ****
--- 2310,2328 ----
  
  	systable_endscan(avScan);
  
+ 	if (!HeapTupleIsValid(avTup) && table_toast_map != NULL)
+ 	{
+ 		av_relation		*hentry;
+ 		bool		found;
+ 
+ 		hentry = hash_search(table_toast_map, (void *) &relid,
+ 							 HASH_FIND, &found);
+ 		if (found)
+ 			/* avoid second recursion */
+ 			avTup = get_pg_autovacuum_tuple_relid(avRel, hentry->ar_relid,
+ 												  NULL);
+ 	}
+ 
  	return avTup;
  }
  
*************** get_pgstat_tabentry_relid(Oid relid, boo
*** 2297,2310 ****
  /*
   * table_recheck_autovac
   *
!  * Recheck whether a plain table still needs vacuum or analyze; be it because
!  * it does directly, or because its TOAST table does.  Return value is a valid
!  * autovac_table pointer if it does, NULL otherwise.
   *
   * Note that the returned autovac_table does not have the name fields set.
   */
  static autovac_table *
! table_recheck_autovac(Oid relid)
  {
  	Form_pg_autovacuum avForm = NULL;
  	Form_pg_class classForm;
--- 2353,2365 ----
  /*
   * table_recheck_autovac
   *
!  * Recheck whether a table still needs vacuum or analyze.  Return value is a
!  * valid autovac_table pointer if it does, NULL otherwise.
   *
   * Note that the returned autovac_table does not have the name fields set.
   */
  static autovac_table *
! table_recheck_autovac(Oid relid, HTAB *table_toast_map)
  {
  	Form_pg_autovacuum avForm = NULL;
  	Form_pg_class classForm;
*************** table_recheck_autovac(Oid relid)
*** 2315,2325 ****
  	bool		doanalyze;
  	autovac_table *tab = NULL;
  	PgStat_StatTabEntry *tabentry;
- 	bool		doit = false;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
! 	bool		wraparound,
! 				toast_wraparound = false;
  
  	/* use fresh stats */
  	autovac_refresh_stats();
--- 2370,2378 ----
  	bool		doanalyze;
  	autovac_table *tab = NULL;
  	PgStat_StatTabEntry *tabentry;
  	PgStat_StatDBEntry *shared;
  	PgStat_StatDBEntry *dbentry;
! 	bool		wraparound;
  
  	/* use fresh stats */
  	autovac_refresh_stats();
*************** table_recheck_autovac(Oid relid)
*** 2335,2343 ****
  		return NULL;
  	classForm = (Form_pg_class) GETSTRUCT(classTup);
  
! 	/* fetch the pg_autovacuum entry, if any */
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
! 	avTup = get_pg_autovacuum_tuple_relid(avRel, relid);
  	if (HeapTupleIsValid(avTup))
  		avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
  
--- 2388,2402 ----
  		return NULL;
  	classForm = (Form_pg_class) GETSTRUCT(classTup);
  
! 	/*
! 	 * Fetch the pg_autovacuum entry, if any.  For a toast table, also try the
! 	 * main rel's pg_autovacuum entry if there isn't one for the TOAST table
! 	 * itself.
! 	 */
  	avRel = heap_open(AutovacuumRelationId, AccessShareLock);
! 	avTup = get_pg_autovacuum_tuple_relid(avRel, relid,
! 			classForm->relkind == RELKIND_TOASTVALUE ? table_toast_map : NULL);
! 
  	if (HeapTupleIsValid(avTup))
  		avForm = (Form_pg_autovacuum) GETSTRUCT(avTup);
  
*************** table_recheck_autovac(Oid relid)
*** 2348,2398 ****
  	relation_needs_vacanalyze(relid, avForm, classForm, tabentry,
  							  &dovacuum, &doanalyze, &wraparound);
  
! 	/* OK, it needs vacuum by itself */
! 	if (dovacuum)
! 		doit = true;
! 	/* it doesn't need vacuum, but what about it's TOAST table? */
! 	else if (OidIsValid(classForm->reltoastrelid))
! 	{
! 		Oid			toastrelid = classForm->reltoastrelid;
! 		HeapTuple	toastClassTup;
! 
! 		toastClassTup = SearchSysCacheCopy(RELOID,
! 										   ObjectIdGetDatum(toastrelid),
! 										   0, 0, 0);
! 		if (HeapTupleIsValid(toastClassTup))
! 		{
! 			bool		toast_dovacuum;
! 			bool		toast_doanalyze;
! 			bool		toast_wraparound;
! 			Form_pg_class toastClassForm;
! 			PgStat_StatTabEntry *toasttabentry;
! 
! 			toastClassForm = (Form_pg_class) GETSTRUCT(toastClassTup);
! 			toasttabentry = get_pgstat_tabentry_relid(toastrelid,
! 												 toastClassForm->relisshared,
! 													  shared, dbentry);
! 
! 			/* note we use the pg_autovacuum entry for the main table */
! 			relation_needs_vacanalyze(toastrelid, avForm,
! 									  toastClassForm, toasttabentry,
! 									  &toast_dovacuum, &toast_doanalyze,
! 									  &toast_wraparound);
! 			/* we only consider VACUUM for toast tables */
! 			if (toast_dovacuum)
! 			{
! 				dovacuum = true;
! 				doit = true;
! 			}
! 
! 			heap_freetuple(toastClassTup);
! 		}
! 	}
! 
! 	if (doanalyze)
! 		doit = true;
  
! 	if (doit)
  	{
  		int			freeze_min_age;
  		int			vac_cost_limit;
--- 2407,2418 ----
  	relation_needs_vacanalyze(relid, avForm, classForm, tabentry,
  							  &dovacuum, &doanalyze, &wraparound);
  
! 	/* ignore ANALYZE for toast tables */
! 	if (classForm->relkind == RELKIND_TOASTVALUE)
! 		doanalyze = false;
  
! 	/* OK, it needs something done */
! 	if (doanalyze || dovacuum)
  	{
  		int			freeze_min_age;
  		int			vac_cost_limit;
*************** table_recheck_autovac(Oid relid)
*** 2439,2445 ****
  		tab->at_freeze_min_age = freeze_min_age;
  		tab->at_vacuum_cost_limit = vac_cost_limit;
  		tab->at_vacuum_cost_delay = vac_cost_delay;
! 		tab->at_wraparound = wraparound || toast_wraparound;
  		tab->at_relname = NULL;
  		tab->at_nspname = NULL;
  		tab->at_datname = NULL;
--- 2459,2465 ----
  		tab->at_freeze_min_age = freeze_min_age;
  		tab->at_vacuum_cost_limit = vac_cost_limit;
  		tab->at_vacuum_cost_delay = vac_cost_delay;
! 		tab->at_wraparound = wraparound;
  		tab->at_relname = NULL;
  		tab->at_nspname = NULL;
  		tab->at_datname = NULL;
*************** autovacuum_do_vac_analyze(autovac_table 
*** 2633,2639 ****
  	/* Let pgstat know what we're doing */
  	autovac_report_activity(tab);
  
! 	vacuum(&vacstmt, tab->at_relid, bstrategy, tab->at_wraparound, true);
  }
  
  /*
--- 2653,2659 ----
  	/* Let pgstat know what we're doing */
  	autovac_report_activity(tab);
  
! 	vacuum(&vacstmt, tab->at_relid, false, bstrategy, tab->at_wraparound, true);
  }
  
  /*
Index: src/backend/tcop/utility.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/tcop/utility.c,v
retrieving revision 1.295
diff -c -p -r1.295 utility.c
*** src/backend/tcop/utility.c	18 Jul 2008 20:26:06 -0000	1.295
--- src/backend/tcop/utility.c	8 Aug 2008 15:51:22 -0000
*************** ProcessUtility(Node *parsetree,
*** 836,842 ****
  			break;
  
  		case T_VacuumStmt:
! 			vacuum((VacuumStmt *) parsetree, InvalidOid, NULL, false,
  				   isTopLevel);
  			break;
  
--- 836,842 ----
  			break;
  
  		case T_VacuumStmt:
! 			vacuum((VacuumStmt *) parsetree, InvalidOid, true, NULL, false,
  				   isTopLevel);
  			break;
  
Index: src/include/commands/vacuum.h
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/include/commands/vacuum.h,v
retrieving revision 1.79
diff -c -p -r1.79 vacuum.h
*** src/include/commands/vacuum.h	1 Jul 2008 10:33:09 -0000	1.79
--- src/include/commands/vacuum.h	8 Aug 2008 15:51:22 -0000
*************** extern int	vacuum_freeze_min_age;
*** 125,131 ****
  
  
  /* in commands/vacuum.c */
! extern void vacuum(VacuumStmt *vacstmt, Oid relid,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
  extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
  				 int *nindexes, Relation **Irel);
--- 125,131 ----
  
  
  /* in commands/vacuum.c */
! extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
  extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
  				 int *nindexes, Relation **Irel);
#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#3)
Re: autovacuum and TOAST tables

Alvaro Herrera <alvherre@commandprompt.com> writes:

Note that this patch allows a toast table to be vacuumed by the user:
I don't have a problem with that, but if anyone thinks this is not a
good idea, please speak up.

The permissions on pg_toast will prevent anyone but a superuser from
doing that anyway, so it's no big deal.

Possibly more interesting is what happens if someone drops the parent
table while VACUUM is working independently on the toast table. Does
DROP take exclusive lock on a toast table? Probably, but it needs
to be checked. I think preventing that scenario was one reason why
the vacuuming was tied together way back when.

(The same goes for any other parent-table DDL action that would affect
the toast table; CLUSTER or TRUNCATE for instance.)

regards, tom lane

#5Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#4)
Re: autovacuum and TOAST tables

Tom Lane wrote:

Alvaro Herrera <alvherre@commandprompt.com> writes:

Note that this patch allows a toast table to be vacuumed by the user:
I don't have a problem with that, but if anyone thinks this is not a
good idea, please speak up.

The permissions on pg_toast will prevent anyone but a superuser from
doing that anyway, so it's no big deal.

Possibly more interesting is what happens if someone drops the parent
table while VACUUM is working independently on the toast table. Does
DROP take exclusive lock on a toast table? Probably, but it needs
to be checked.

Yes, it does. So the autovacuum process working on the TOAST table
would get cancelled by the DROP TABLE, TRUNCATE, CLUSTER. The one ALTER
TABLE variant that I think needs to handle the TOAST table is ALTER
TYPE, but I think it should work that it is being vacuumed concurrently.
REINDEX TABLE should perhaps also be concerned because it does reindex
the toast table, but it grabs the lock before actually doing the
reindexing so I don't think there's a problem here.

BTW only now I notice that CLUSTER leaves the toast table name in bad
shape: if you create a table with OID X its TOAST table is named
pg_toast_X. If you then cluster this table, a new transient table gets
created with OID Y; the TOAST table for Y is named pg_toast_Y, and then
this new TOAST table is used as the new TOAST table for the original
table X. So you end up with table OID X having TOAST table pg_toast_Y.

This is not a concern from the system standpoint because it doesn't use
this name for anything, but people looking at the catalogs manually may
be taken by surprise.

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#5)
Re: autovacuum and TOAST tables

Alvaro Herrera <alvherre@commandprompt.com> writes:

BTW only now I notice that CLUSTER leaves the toast table name in bad
shape: if you create a table with OID X its TOAST table is named
pg_toast_X. If you then cluster this table, a new transient table gets
created with OID Y; the TOAST table for Y is named pg_toast_Y, and then
this new TOAST table is used as the new TOAST table for the original
table X. So you end up with table OID X having TOAST table pg_toast_Y.

Hmm, we could probably fix that if we made the cluster operation swap
the physical storage of the two toast tables, rather than swapping the
tables altogether. I agree it's not critical but it could be confusing.

regards, tom lane

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#6)
Re: autovacuum and TOAST tables

I wrote:

Hmm, we could probably fix that if we made the cluster operation swap
the physical storage of the two toast tables, rather than swapping the
tables altogether. I agree it's not critical but it could be confusing.

On second thought, I think it *could* lead to a visible failure.
Suppose the OID counter wraps around and the OID that had been used for
the temporary CLUSTER table gets assigned to a new table. If that table
needs a toast table, it'll try to create one using the name that is
already in use. We have defenses against picking an OID that's in use,
but none for toast table names. So I think it's indeed worth fixing.

regards, tom lane

#8Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#7)
Re: autovacuum and TOAST tables

Tom Lane wrote:

On second thought, I think it *could* lead to a visible failure.
Suppose the OID counter wraps around and the OID that had been used for
the temporary CLUSTER table gets assigned to a new table. If that table
needs a toast table, it'll try to create one using the name that is
already in use. We have defenses against picking an OID that's in use,
but none for toast table names. So I think it's indeed worth fixing.

Okay, I'll see to it after committing this autovacuum stuff.

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#9Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#7)
Re: autovacuum and TOAST tables

Tom Lane wrote:

I wrote:

Hmm, we could probably fix that if we made the cluster operation swap
the physical storage of the two toast tables, rather than swapping the
tables altogether. I agree it's not critical but it could be confusing.

On second thought, I think it *could* lead to a visible failure.
Suppose the OID counter wraps around and the OID that had been used for
the temporary CLUSTER table gets assigned to a new table. If that table
needs a toast table, it'll try to create one using the name that is
already in use. We have defenses against picking an OID that's in use,
but none for toast table names. So I think it's indeed worth fixing.

My first attempt at a fix, which was simply swapping relfilenode for the
TOAST tables (and its indexes) after the data has been copied, does not
work, apparently because the TOAST pointers have the toast table ID
embedded. Since we're intending to keep the pg_class entry for the old
TOAST table, the OID in the toast links is no longer valid.

I'm not sure what can be done about this ... Obviously we cannot just
swap the toast table before starting to move the data, because then we
cannot read the original data.

I wonder if we can get away with simply renaming the new toast table and
index after the data has been copied.

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#9)
Re: autovacuum and TOAST tables

Alvaro Herrera <alvherre@commandprompt.com> writes:

My first attempt at a fix, which was simply swapping relfilenode for the
TOAST tables (and its indexes) after the data has been copied, does not
work, apparently because the TOAST pointers have the toast table ID
embedded.

Ouch. Right.

I wonder if we can get away with simply renaming the new toast table and
index after the data has been copied.

Yeah, that seems like the best answer.

regards, tom lane

#11Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#10)
1 attachment(s)
Re: autovacuum and TOAST tables

Tom Lane wrote:

Alvaro Herrera <alvherre@commandprompt.com> writes:

I wonder if we can get away with simply renaming the new toast table and
index after the data has been copied.

Yeah, that seems like the best answer.

Seems like this patch fixes it.

How far back should be backpatched? Given the lack of field complaints
I'd say "just to HEAD" but maybe others have different opinions. FWIW
the patch applies cleanly (modulo header changes) back to 8.0.

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Attachments:

cluster-toastname.patchtext/x-diff; charset=us-asciiDownload
Index: src/backend/commands/cluster.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/backend/commands/cluster.c,v
retrieving revision 1.177
diff -c -p -r1.177 cluster.c
*** src/backend/commands/cluster.c	19 Jun 2008 00:46:04 -0000	1.177
--- src/backend/commands/cluster.c	9 Oct 2008 21:15:55 -0000
***************
*** 29,34 ****
--- 29,35 ----
  #include "catalog/index.h"
  #include "catalog/indexing.h"
  #include "catalog/namespace.h"
+ #include "catalog/pg_namespace.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
  #include "commands/tablecmds.h"
*************** rebuild_relation(Relation OldHeap, Oid i
*** 568,573 ****
--- 569,576 ----
  	char		NewHeapName[NAMEDATALEN];
  	TransactionId frozenXid;
  	ObjectAddress object;
+ 	char		NewToastName[NAMEDATALEN];
+ 	Relation	newrel;
  
  	/* Mark the correct index as clustered */
  	mark_index_clustered(OldHeap, indexOid);
*************** rebuild_relation(Relation OldHeap, Oid i
*** 622,627 ****
--- 625,645 ----
  	 * because reindex_relation does it.
  	 */
  	reindex_relation(tableOid, false);
+ 
+ 	/*
+ 	 * At this point, everything is kosher except that the toast table's name
+ 	 * corresponds to the temporary table.  The name is irrelevant to
+ 	 * the backend because it's referenced by OID, but users looking at the
+ 	 * catalogs could be confused.  Rename it to prevent this problem.
+ 	 *
+ 	 * Note no lock required on the relation, because we already hold an
+ 	 * exclusive lock on it.
+ 	 */
+ 	newrel = relation_open(tableOid, NoLock);
+ 	snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", tableOid);
+ 	RenameRelationInternal(newrel->rd_rel->reltoastrelid, NewToastName,
+ 						   PG_TOAST_NAMESPACE);
+ 	relation_close(newrel, NoLock);
  }
  
  /*
#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#11)
Re: autovacuum and TOAST tables

Alvaro Herrera <alvherre@commandprompt.com> writes:

Yeah, that seems like the best answer.

Seems like this patch fixes it.

Um, not for tables that don't have toast tables ...

How far back should be backpatched?

Not at all; it's not a bug fix.

regards, tom lane

#13Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#12)
1 attachment(s)
Re: autovacuum and TOAST tables

Tom Lane wrote:

Alvaro Herrera <alvherre@commandprompt.com> writes:

Yeah, that seems like the best answer.

Seems like this patch fixes it.

Um, not for tables that don't have toast tables ...

Right, this seems better.

Note that it needs to open the toast table and grab AccessShare to get
the toast index OID. I don't think it needs a stronger lock.

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

cluster-toastname-2.patchtext/x-diff; charset=us-asciiDownload
Index: src/backend/commands/cluster.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/commands/cluster.c,v
retrieving revision 1.177
diff -c -p -r1.177 cluster.c
*** src/backend/commands/cluster.c	19 Jun 2008 00:46:04 -0000	1.177
--- src/backend/commands/cluster.c	10 Oct 2008 19:46:56 -0000
***************
*** 29,34 ****
--- 29,35 ----
  #include "catalog/index.h"
  #include "catalog/indexing.h"
  #include "catalog/namespace.h"
+ #include "catalog/pg_namespace.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
  #include "commands/tablecmds.h"
*************** rebuild_relation(Relation OldHeap, Oid i
*** 568,573 ****
--- 569,575 ----
  	char		NewHeapName[NAMEDATALEN];
  	TransactionId frozenXid;
  	ObjectAddress object;
+ 	Relation	newrel;
  
  	/* Mark the correct index as clustered */
  	mark_index_clustered(OldHeap, indexOid);
*************** rebuild_relation(Relation OldHeap, Oid i
*** 622,627 ****
--- 624,658 ----
  	 * because reindex_relation does it.
  	 */
  	reindex_relation(tableOid, false);
+ 
+ 	/*
+ 	 * At this point, everything is kosher except that the toast table's name
+ 	 * corresponds to the temporary table.  The name is irrelevant to
+ 	 * the backend because it's referenced by OID, but users looking at the
+ 	 * catalogs could be confused.  Rename it to prevent this problem.
+ 	 *
+ 	 * Note no lock required on the relation, because we already hold an
+ 	 * exclusive lock on it.
+ 	 */
+ 	newrel = heap_open(tableOid, NoLock);
+ 	if (OidIsValid(newrel->rd_rel->reltoastrelid))
+ 	{
+ 		char		NewToastName[NAMEDATALEN];
+ 		Relation	toastrel;
+ 
+ 		/* rename the toast table ... */
+ 		snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", tableOid);
+ 		RenameRelationInternal(newrel->rd_rel->reltoastrelid, NewToastName,
+ 							   PG_TOAST_NAMESPACE);
+ 
+ 		/* ... and its index too */
+ 		toastrel = relation_open(newrel->rd_rel->reltoastrelid, AccessShareLock);
+ 		snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index", tableOid);
+ 		RenameRelationInternal(toastrel->rd_rel->reltoastidxid, NewToastName,
+ 							   PG_TOAST_NAMESPACE);
+ 		relation_close(toastrel, AccessShareLock);
+ 	}
+ 	relation_close(newrel, NoLock);
  }
  
  /*