diff -rc -X diff-ignore 11fixclass/doc/src/sgml/catalogs.sgml 12ntrelminxid/doc/src/sgml/catalogs.sgml *** 11fixclass/doc/src/sgml/catalogs.sgml 2006-06-09 18:52:50.000000000 -0400 --- 12ntrelminxid/doc/src/sgml/catalogs.sgml 2006-06-10 19:38:04.000000000 -0400 *************** *** 1619,1624 **** --- 1619,1648 ---- + relminxid + xid + + + The minimum transaction ID present in all rows in this table. This + value is used to determine the database-global + pg_database.datminxid value. + + + + + relvacuumxid + xid + + + The transaction ID that was used as cleaning point as of the last vacuum + operation. All rows inserted, updated or deleted in this table by + transactions whose IDs are below this one have been marked as known good + or deleted. This is used to determine the database-global + pg_database.datvacuumxid value. + + + + relacl aclitem[] *************** *** 2048,2068 **** xid ! All rows inserted or deleted by transaction IDs before this one ! have been marked as known committed or known aborted in this database. ! This is used to determine when commit-log space can be recycled. ! datfrozenxid xid All rows inserted by transaction IDs before this one have been relabeled with a permanent (frozen) transaction ID in this ! database. This is useful to check whether a database must be vacuumed ! soon to avoid transaction ID wrap-around problems. --- 2072,2098 ---- xid ! The transaction ID that was used as cleaning point as of the last vacuum ! operation. All rows inserted or deleted by transaction IDs before this one ! have been marked as known good or deleted. This ! is used to determine when commit-log space can be recycled. ! If InvalidTransactionId, then the minimum is unknown and can be ! determined by scanning pg_class.relvacuumxid. ! datminxid xid + The minimum transaction ID present in all tables in this database. All rows inserted by transaction IDs before this one have been relabeled with a permanent (frozen) transaction ID in this ! database. This is useful to check whether a database must be ! vacuumed soon to avoid transaction ID wrap-around problems. ! If InvalidTransactionId, then the minimum is unknown and can be ! determined by scanning pg_class.relminxid. diff -rc -X diff-ignore 11fixclass/src/backend/access/heap/heapam.c 12ntrelminxid/src/backend/access/heap/heapam.c *** 11fixclass/src/backend/access/heap/heapam.c 2006-06-10 19:17:47.000000000 -0400 --- 12ntrelminxid/src/backend/access/heap/heapam.c 2006-06-11 02:41:15.000000000 -0400 *************** *** 45,61 **** --- 45,74 ---- #include "access/valid.h" #include "access/xlogutils.h" #include "catalog/catalog.h" + #include "catalog/heap.h" #include "catalog/namespace.h" + #include "catalog/pg_ntclass.h" #include "miscadmin.h" #include "pgstat.h" #include "storage/procarray.h" #include "utils/inval.h" #include "utils/relcache.h" + #include "utils/syscache.h" static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, ItemPointerData from, Buffer newbuf, HeapTuple newtup, bool move); + /* + * There are some situations in which we want to acquire strong locks on + * relations, but we know there is no need to unfreeze them; for example, + * during VACUUM. Any such caller sets this variable to true, which turns + * heap_unfreeze into a no-op. + * + * It's advisable to do this in a PG_TRY block so that it won't "forget" to + * reset the variable in case of error. + */ + bool disable_heap_unfreeze = false; /* ---------------------------------------------------------------- * heap support routines *************** *** 2669,2674 **** --- 2682,2772 ---- return HeapTupleMayBeUpdated; } + /* + * heap_unfreeze + * Mark a table as no longer frozen in pg_ntclass. + * + * This routine updates the pg_ntclass.relminxid and relvacuumxid columns so + * that they no longer appear as frozen. + */ + void + heap_unfreeze(Relation rel) + { + Form_pg_ntclass ntclassForm; + HeapTuple tuple; + bool dirty = false; + + /* early exit if somebody decided to disable us */ + if (disable_heap_unfreeze) + return; + + /* Exit early in bootstrap mode */ + if (IsBootstrapProcessingMode()) + return; + + /* + * We process only normal heaps, toast tables, and non-transactional + * heaps. Other kinds of heaps don't store Xids in them and thus they + * don't need unfreezing. + */ + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_INDEX && + rel->rd_rel->relkind != RELKIND_NON_TRANSACTIONAL) + return; + + /* + * Under normal conditions, we should have a snapshot already, but in some + * cases there may not be one. Getting a snapshot guarantees we will have + * a valid RecentXmin to use. + */ + if (!TransactionIdIsValid(RecentXmin)) + ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); + Assert(TransactionIdIsValid(RecentXmin)); + + PG_TRY(); + { + Relation pg_ntclass; + + /* Disable ourselves so that we don't recurse on unfreezing pg_ntclass */ + disable_heap_unfreeze = true; + + pg_ntclass = heap_open(NtClassRelationId, RowExclusiveLock); + + /* Fetch a copy to scribble on */ + tuple = get_ntclass_entry(pg_ntclass, rel); + + ntclassForm = (Form_pg_ntclass) GETSTRUCT(tuple); + + /* Apply all necessary updates. */ + if (TransactionIdEquals(ntclassForm->relminxid, FrozenTransactionId)) + { + ntclassForm->relminxid = RecentXmin; + dirty = true; + } + if (TransactionIdEquals(ntclassForm->relvacuumxid, FrozenTransactionId)) + { + ntclassForm->relvacuumxid = RecentXmin; + dirty = true; + } + /* Update tuple if necessary. */ + if (dirty) + update_ntclass_entry(pg_ntclass, tuple); + + /* All done, cleanup and return */ + heap_freetuple(tuple); + heap_close(pg_ntclass, RowExclusiveLock); + + /* reenable heap unfreezing */ + disable_heap_unfreeze = false; + } + PG_CATCH(); + { + /* make sure to reenable heap unfreezing in case of error */ + disable_heap_unfreeze = false; + PG_RE_THROW(); + } + PG_END_TRY(); + } /* * heap_inplace_update - update a tuple "in place" (ie, overwrite it) diff -rc -X diff-ignore 11fixclass/src/backend/access/transam/varsup.c 12ntrelminxid/src/backend/access/transam/varsup.c *** 11fixclass/src/backend/access/transam/varsup.c 2006-03-08 16:23:24.000000000 -0300 --- 12ntrelminxid/src/backend/access/transam/varsup.c 2006-06-10 19:38:04.000000000 -0400 *************** *** 168,178 **** /* * Determine the last safe XID to allocate given the currently oldest ! * datfrozenxid (ie, the oldest XID that might exist in any database * of our cluster). */ void ! SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Name oldest_datname) { TransactionId xidWarnLimit; --- 168,178 ---- /* * Determine the last safe XID to allocate given the currently oldest ! * datminxid (ie, the oldest XID that might exist in any database * of our cluster). */ void ! SetTransactionIdLimit(TransactionId oldest_datminxid, Name oldest_datname) { TransactionId xidWarnLimit; *************** *** 180,195 **** TransactionId xidWrapLimit; TransactionId curXid; ! Assert(TransactionIdIsValid(oldest_datfrozenxid)); /* * The place where we actually get into deep trouble is halfway around ! * from the oldest potentially-existing XID. (This calculation is ! * probably off by one or two counts, because the special XIDs reduce the ! * size of the loop a little bit. But we throw in plenty of slop below, ! * so it doesn't matter.) */ ! xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1); if (xidWrapLimit < FirstNormalTransactionId) xidWrapLimit += FirstNormalTransactionId; --- 180,195 ---- TransactionId xidWrapLimit; TransactionId curXid; ! Assert(TransactionIdIsValid(oldest_datminxid)); /* * The place where we actually get into deep trouble is halfway around ! * from the oldest existing XID. (This calculation is probably off by one ! * or two counts, because the special XIDs reduce the size of the loop a ! * little bit. But we throw in plenty of slop below, so it doesn't ! * matter.) */ ! xidWrapLimit = oldest_datminxid + (MaxTransactionId >> 1); if (xidWrapLimit < FirstNormalTransactionId) xidWrapLimit += FirstNormalTransactionId; diff -rc -X diff-ignore 11fixclass/src/backend/catalog/heap.c 12ntrelminxid/src/backend/catalog/heap.c *** 11fixclass/src/backend/catalog/heap.c 2006-06-11 02:46:03.000000000 -0400 --- 12ntrelminxid/src/backend/catalog/heap.c 2006-06-11 01:03:39.000000000 -0400 *************** *** 637,648 **** switch (relkind) { case RELKIND_RELATION: - case RELKIND_INDEX: case RELKIND_TOASTVALUE: case RELKIND_NON_TRANSACTIONAL: /* The relation is real, but as yet empty */ new_rel_nttup->relpages = 0; new_rel_nttup->reltuples = 0; break; default: elog(ERROR, "can't create pg_ntclass entries for relkind '%c'", --- 637,665 ---- switch (relkind) { case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_NON_TRANSACTIONAL: /* The relation is real, but as yet empty */ new_rel_nttup->relpages = 0; new_rel_nttup->reltuples = 0; + /* + * Real tables have Xids stored in them, so initialize our + * known value to the minimum Xid that could put tuples in the + * new table. + */ + new_rel_nttup->relminxid = RecentXmin; + new_rel_nttup->relvacuumxid = RecentXmin; + break; + case RELKIND_INDEX: + /* The relation is real, but as yet empty */ + new_rel_nttup->relpages = 0; + new_rel_nttup->reltuples = 0; + /* + * Index relations don't have Xids stored in them, so set the + * transaction Id values to InvalidTransactionId. + */ + new_rel_nttup->relminxid = InvalidTransactionId; + new_rel_nttup->relvacuumxid = InvalidTransactionId; break; default: elog(ERROR, "can't create pg_ntclass entries for relkind '%c'", diff -rc -X diff-ignore 11fixclass/src/backend/commands/analyze.c 12ntrelminxid/src/backend/commands/analyze.c *** 11fixclass/src/backend/commands/analyze.c 2006-06-10 19:17:47.000000000 -0400 --- 12ntrelminxid/src/backend/commands/analyze.c 2006-06-11 01:26:00.000000000 -0400 *************** *** 420,426 **** /* * If we are running a standalone ANALYZE, update pages/tuples stats in ! * pg_class. We know the accurate page count from the smgr, but only an * approximate number of tuples; therefore, if we are part of VACUUM * ANALYZE do *not* overwrite the accurate count already inserted by * VACUUM. The same consideration applies to indexes. --- 420,426 ---- /* * If we are running a standalone ANALYZE, update pages/tuples stats in ! * pg_ntclass. We know the accurate page count from the smgr, but only an * approximate number of tuples; therefore, if we are part of VACUUM * ANALYZE do *not* overwrite the accurate count already inserted by * VACUUM. The same consideration applies to indexes. *************** *** 429,436 **** { vac_update_relstats(onerel, RelationGetNumberOfBlocks(onerel), ! totalrows, ! hasindex); for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; --- 429,437 ---- { vac_update_relstats(onerel, RelationGetNumberOfBlocks(onerel), ! totalrows, hasindex, ! InvalidTransactionId, InvalidTransactionId); ! for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; *************** *** 439,446 **** totalindexrows = ceil(thisdata->tupleFract * totalrows); vac_update_relstats(Irel[ind], RelationGetNumberOfBlocks(Irel[ind]), ! totalindexrows, ! false); } /* report results to the stats collector, too */ --- 440,447 ---- totalindexrows = ceil(thisdata->tupleFract * totalrows); vac_update_relstats(Irel[ind], RelationGetNumberOfBlocks(Irel[ind]), ! totalindexrows, false, ! InvalidTransactionId, InvalidTransactionId); } /* report results to the stats collector, too */ diff -rc -X diff-ignore 11fixclass/src/backend/commands/dbcommands.c 12ntrelminxid/src/backend/commands/dbcommands.c *** 11fixclass/src/backend/commands/dbcommands.c 2006-05-04 21:36:10.000000000 -0400 --- 12ntrelminxid/src/backend/commands/dbcommands.c 2006-06-10 19:38:04.000000000 -0400 *************** *** 46,51 **** --- 46,52 ---- #include "utils/flatfiles.h" #include "utils/fmgroids.h" #include "utils/guc.h" + #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" *************** *** 55,61 **** Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, ! TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP, Oid *dbTablespace); static bool have_createdb_privilege(void); static void remove_dbtablespaces(Oid db_id); --- 56,62 ---- Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, ! TransactionId *dbVacuumXidP, TransactionId *dbMinXidP, Oid *dbTablespace); static bool have_createdb_privilege(void); static void remove_dbtablespaces(Oid db_id); *************** *** 76,82 **** bool src_allowconn; Oid src_lastsysoid; TransactionId src_vacuumxid; ! TransactionId src_frozenxid; Oid src_deftablespace; volatile Oid dst_deftablespace; Relation pg_database_rel; --- 77,83 ---- bool src_allowconn; Oid src_lastsysoid; TransactionId src_vacuumxid; ! TransactionId src_minxid; Oid src_deftablespace; volatile Oid dst_deftablespace; Relation pg_database_rel; *************** *** 228,234 **** if (!get_db_info(dbtemplate, ShareLock, &src_dboid, &src_owner, &src_encoding, &src_istemplate, &src_allowconn, &src_lastsysoid, ! &src_vacuumxid, &src_frozenxid, &src_deftablespace)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("template database \"%s\" does not exist", --- 229,235 ---- if (!get_db_info(dbtemplate, ShareLock, &src_dboid, &src_owner, &src_encoding, &src_istemplate, &src_allowconn, &src_lastsysoid, ! &src_vacuumxid, &src_minxid, &src_deftablespace)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("template database \"%s\" does not exist", *************** *** 327,340 **** } /* ! * Normally we mark the new database with the same datvacuumxid and ! * datfrozenxid as the source. However, if the source is not allowing ! * connections then we assume it is fully frozen, and we can set the ! * current transaction ID as the xid limits. This avoids immediately ! * starting to generate warnings after cloning template0. ! */ ! if (!src_allowconn) ! src_vacuumxid = src_frozenxid = GetCurrentTransactionId(); /* * Check for db name conflict. This is just to give a more friendly --- 328,342 ---- } /* ! * Normally we mark the new database with the same datminxid as the source. ! * However, if the source is fully frozen, we must not mark the new ! * database as frozen because of the new pg_database tuple, which will be ! * marked with our transaction ID. ! */ ! if (TransactionIdEquals(src_minxid, FrozenTransactionId)) ! src_minxid = GetCurrentTransactionId(); ! if (TransactionIdEquals(src_vacuumxid, FrozenTransactionId)) ! src_vacuumxid = GetCurrentTransactionId(); /* * Check for db name conflict. This is just to give a more friendly *************** *** 367,373 **** new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid); new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid); ! new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace); /* --- 369,375 ---- new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid); new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid); ! new_record[Anum_pg_database_datminxid - 1] = TransactionIdGetDatum(src_minxid); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace); /* *************** *** 1050,1055 **** --- 1052,1119 ---- */ } + /* + * UnfreezeDatabase + * Unfreezes a database + * + * Unfreezing a database is to set datminxid and datvacuumxid to a current + * normal Xid. Currently, we do this anytime somebody connects to a database + * that is currently marked as frozen (datminxid = FrozenTransactionId). + * + * NB --- this is called early during backend initialization. + */ + void + UnfreezeDatabase(Oid dbid, TransactionId unfreezeXid) + { + Relation dbRel; + HeapTuple tuple; + Form_pg_database dbForm; + bool dirty = false; + + PG_TRY(); + { + disable_heap_unfreeze = true; + + dbRel = heap_open(DatabaseRelationId, RowExclusiveLock); + + /* fetch a copy of the tuple to scribble on */ + tuple = SearchSysCacheCopy(DATABASEOID, + dbid, + 0, 0, 0); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, + "cache lookup failed for database %u", dbid); + + dbForm = (Form_pg_database) GETSTRUCT(tuple); + + /* make sure no one did it while we weren't looking */ + if (TransactionIdEquals(dbForm->datminxid, FrozenTransactionId)) + { + dbForm->datminxid = unfreezeXid; + dirty = true; + } + if (TransactionIdEquals(dbForm->datvacuumxid, FrozenTransactionId)) + { + dbForm->datvacuumxid = unfreezeXid; + dirty = true; + } + + if (dirty) + heap_inplace_update(dbRel, tuple); + + heap_close(dbRel, RowExclusiveLock); + } + PG_CATCH(); + { + disable_heap_unfreeze = false; + PG_RE_THROW(); + }; + PG_END_TRY(); + + /* reenable heap unfreezing */ + disable_heap_unfreeze = false; + } /* * Helper functions *************** *** 1066,1072 **** Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, ! TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP, Oid *dbTablespace) { bool result = false; --- 1130,1136 ---- Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, ! TransactionId *dbVacuumXidP, TransactionId *dbMinXidP, Oid *dbTablespace) { bool result = false; *************** *** 1155,1163 **** /* limit of vacuumed XIDs */ if (dbVacuumXidP) *dbVacuumXidP = dbform->datvacuumxid; ! /* limit of frozen XIDs */ ! if (dbFrozenXidP) ! *dbFrozenXidP = dbform->datfrozenxid; /* default tablespace for this database */ if (dbTablespace) *dbTablespace = dbform->dattablespace; --- 1219,1227 ---- /* limit of vacuumed XIDs */ if (dbVacuumXidP) *dbVacuumXidP = dbform->datvacuumxid; ! /* limit of min XIDs */ ! if (dbMinXidP) ! *dbMinXidP = dbform->datminxid; /* default tablespace for this database */ if (dbTablespace) *dbTablespace = dbform->dattablespace; diff -rc -X diff-ignore 11fixclass/src/backend/commands/vacuum.c 12ntrelminxid/src/backend/commands/vacuum.c *** 11fixclass/src/backend/commands/vacuum.c 2006-06-10 19:17:47.000000000 -0400 --- 12ntrelminxid/src/backend/commands/vacuum.c 2006-06-11 02:37:38.000000000 -0400 *************** *** 129,134 **** --- 129,135 ---- Size min_tlen; Size max_tlen; bool hasindex; + TransactionId minxid; /* Minimum Xid present anywhere on table */ /* vtlinks array for tuple chain following - sorted by new_tid */ int num_vtlinks; VTupleLink vtlinks; *************** *** 196,220 **** static int elevel = -1; - static TransactionId OldestXmin; - static TransactionId FreezeLimit; - /* non-export function prototypes */ static List *get_rel_oids(List *relids, const RangeVar *vacrel, const char *stmttype, bool full); ! static void vac_update_dbstats(Oid dbid, ! TransactionId vacuumXID, ! TransactionId frozenXID); ! static void vac_truncate_clog(TransactionId vacuumXID, ! TransactionId frozenXID); ! static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool allow_toast); static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); static void scan_heap(VRelStats *vacrelstats, Relation onerel, ! VacPageList vacuum_pages, VacPageList fraged_pages); static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, ! int nindexes, Relation *Irel); static void move_chain_tuple(Relation rel, Buffer old_buf, Page old_page, HeapTuple old_tup, Buffer dst_buf, Page dst_page, VacPage dst_vacpage, --- 197,216 ---- static int elevel = -1; /* non-export function prototypes */ static List *get_rel_oids(List *relids, const RangeVar *vacrel, const char *stmttype, bool full); ! static void vac_update_dbminxid(Oid dbid); ! static void vac_truncate_clog(void); ! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool allow_toast); static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); static void scan_heap(VRelStats *vacrelstats, Relation onerel, ! VacPageList vacuum_pages, VacPageList fraged_pages, ! TransactionId FreezeLimit, TransactionId OldestXmin); static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, ! int nindexes, Relation *Irel, TransactionId OldestXmin); static void move_chain_tuple(Relation rel, Buffer old_buf, Page old_page, HeapTuple old_tup, Buffer dst_buf, Page dst_page, VacPage dst_vacpage, *************** *** 270,277 **** vacuum(VacuumStmt *vacstmt, List *relids) { const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE"; - TransactionId initialOldestXmin = InvalidTransactionId; - TransactionId initialFreezeLimit = InvalidTransactionId; volatile MemoryContext anl_context = NULL; volatile bool all_rels, in_outer_xact, --- 266,271 ---- *************** *** 355,386 **** relations = get_rel_oids(relids, vacstmt->relation, stmttype, vacstmt->full); - if (vacstmt->vacuum && all_rels) - { - /* - * It's a database-wide VACUUM. - * - * Compute the initially applicable OldestXmin and FreezeLimit XIDs, - * so that we can record these values at the end of the VACUUM. Note - * that individual tables may well be processed with newer values, but - * we can guarantee that no (non-shared) relations are processed with - * older ones. - * - * It is okay to record non-shared values in pg_database, even though - * we may vacuum shared relations with older cutoffs, because only the - * minimum of the values present in pg_database matters. We can be - * sure that shared relations have at some time been vacuumed with - * cutoffs no worse than the global minimum; for, if there is a - * backend in some other DB with xmin = OLDXMIN that's determining the - * cutoff with which we vacuum shared relations, it is not possible - * for that database to have a cutoff newer than OLDXMIN recorded in - * pg_database. - */ - vacuum_set_xid_limits(vacstmt, false, - &initialOldestXmin, - &initialFreezeLimit); - } - /* * Decide whether we need to start/commit our own transactions. * --- 349,354 ---- *************** *** 441,446 **** --- 409,424 ---- VacuumCostBalance = 0; /* + * During vacuum, we are going to lock the relation with a writer's + * lock, but if it's already frozen, we won't do any writing on it, so + * disable the unfreezing for the duration of this VACUUM operation. + * However, if this a FULL vacuum, we may mark some tuples with our + * Xid even if the table is frozen, so skip this step. + */ + if (!vacstmt->full) + disable_heap_unfreeze = true; + + /* * Loop to process each selected relation. */ foreach(cur, relations) *************** *** 448,457 **** Oid relid = lfirst_oid(cur); if (vacstmt->vacuum) ! { ! if (!vacuum_rel(relid, vacstmt, false)) ! all_rels = false; /* forget about updating dbstats */ ! } if (vacstmt->analyze) { MemoryContext old_context = NULL; --- 426,433 ---- Oid relid = lfirst_oid(cur); if (vacstmt->vacuum) ! vacuum_rel(relid, vacstmt, false); ! if (vacstmt->analyze) { MemoryContext old_context = NULL; *************** *** 496,507 **** --- 472,487 ---- { /* Make sure cost accounting is turned off after error */ VacuumCostActive = false; + /* reenable heap unfreezing too */ + disable_heap_unfreeze = false; PG_RE_THROW(); } PG_END_TRY(); /* Turn off vacuum cost accounting */ VacuumCostActive = false; + /* reenable heap unfreezing too */ + disable_heap_unfreeze = false; /* * Finish up processing. *************** *** 534,550 **** if (all_rels) PrintFreeSpaceMapStatistics(elevel); ! /* ! * If we completed a database-wide VACUUM without skipping any ! * relations, update the database's pg_database row with info about ! * the transaction IDs used, and try to truncate pg_clog. ! */ ! if (all_rels) ! { ! vac_update_dbstats(MyDatabaseId, ! initialOldestXmin, initialFreezeLimit); ! vac_truncate_clog(initialOldestXmin, initialFreezeLimit); ! } } /* --- 514,524 ---- if (all_rels) PrintFreeSpaceMapStatistics(elevel); ! /* Update pg_database.datminxid and datvacuumxid */ ! vac_update_dbminxid(MyDatabaseId); ! ! /* Try to truncate pg_clog. */ ! vac_truncate_clog(); } /* *************** *** 701,707 **** */ void vac_update_relstats(Relation rel, BlockNumber num_pages, double num_tuples, ! bool hasindex) { Relation rd; HeapTuple ctup; --- 675,682 ---- */ void vac_update_relstats(Relation rel, BlockNumber num_pages, double num_tuples, ! bool hasindex, TransactionId minxid, ! TransactionId vacuumxid) { Relation rd; HeapTuple ctup; *************** *** 709,832 **** Form_pg_class pgcform; bool dirty; ! /* Process pg_ntclass first */ ! rd = heap_open(NtClassRelationId, RowExclusiveLock); ! /* Fetch a copy of the pg_ntclass tuple to scribble on */ ! ctup = get_ntclass_entry(rd, rel); ! pgntform = (Form_pg_ntclass) GETSTRUCT(ctup); ! /* Apply required updates, if any, to copied tuple */ ! dirty = false; ! if (pgntform->relpages != (int32) num_pages) ! { ! pgntform->relpages = (int32) num_pages; ! dirty = true; ! } ! if (pgntform->reltuples != (float4) num_tuples) ! { ! pgntform->reltuples = (float4) num_tuples; ! dirty = true; ! } ! /* If anything changed, write out the tuple */ ! if (dirty) ! update_ntclass_entry(rd, ctup); ! heap_freetuple(ctup); ! heap_close(rd, RowExclusiveLock); ! /* Now go for the pg_class tuple */ ! rd = heap_open(RelationRelationId, RowExclusiveLock); ! /* Fetch a copy of the tuple to scribble on */ ! ctup = SearchSysCacheCopy(RELOID, ! ObjectIdGetDatum(RelationGetRelid(rel)), ! 0, 0, 0); ! if (!HeapTupleIsValid(ctup)) ! elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", ! RelationGetRelid(rel)); ! pgcform = (Form_pg_class) GETSTRUCT(ctup); ! /* Apply required updates, if any, to copied tuple */ ! dirty = false; ! if (pgcform->relhasindex != hasindex) ! { ! pgcform->relhasindex = hasindex; ! dirty = true; } ! /* ! * If we have discovered that there are no indexes, then there's no ! * primary key either. This could be done more thoroughly... ! */ ! if (!hasindex) { ! if (pgcform->relhaspkey) ! { ! pgcform->relhaspkey = false; ! dirty = true; ! } } ! /* ! * If anything changed, write out the tuple ! */ ! if (dirty) ! heap_inplace_update(rd, ctup); ! ! heap_close(rd, RowExclusiveLock); } - /* ! * vac_update_dbstats() -- update statistics for one database * ! * Update the whole-database statistics that are kept in its pg_database ! * row, and the flat-file copy of pg_database. * * We violate transaction semantics here by overwriting the database's ! * existing pg_database tuple with the new values. This is reasonably ! * safe since the new values are correct whether or not this transaction * commits. As with vac_update_relstats, this avoids leaving dead tuples * behind after a VACUUM. * ! * This routine is shared by full and lazy VACUUM. Note that it is only ! * applied after a database-wide VACUUM operation. */ static void ! vac_update_dbstats(Oid dbid, ! TransactionId vacuumXID, ! TransactionId frozenXID) { - Relation relation; HeapTuple tuple; Form_pg_database dbform; ! relation = heap_open(DatabaseRelationId, RowExclusiveLock); ! /* Fetch a copy of the tuple to scribble on */ ! tuple = SearchSysCacheCopy(DATABASEOID, ! ObjectIdGetDatum(dbid), ! 0, 0, 0); ! if (!HeapTupleIsValid(tuple)) ! elog(ERROR, "could not find tuple for database %u", dbid); ! dbform = (Form_pg_database) GETSTRUCT(tuple); ! ! /* overwrite the existing statistics in the tuple */ ! dbform->datvacuumxid = vacuumXID; ! dbform->datfrozenxid = frozenXID; ! heap_inplace_update(relation, tuple); ! heap_close(relation, RowExclusiveLock); ! /* Mark the flat-file copy of pg_database for update at commit */ ! database_file_update_needed(); ! } /* * vac_truncate_clog() -- attempt to truncate the commit log --- 684,944 ---- Form_pg_class pgcform; bool dirty; ! PG_TRY(); ! { ! /* Don't unfreeze pg_ntclass nor pg_class by doing this */ ! disable_heap_unfreeze = true; ! /* Process pg_ntclass first */ ! rd = heap_open(NtClassRelationId, RowExclusiveLock); ! /* Fetch a copy of the pg_ntclass tuple to scribble on */ ! ctup = get_ntclass_entry(rd, rel); ! pgntform = (Form_pg_ntclass) GETSTRUCT(ctup); ! /* Apply required updates, if any, to copied tuple */ ! dirty = false; ! if (pgntform->relpages != (int32) num_pages) ! { ! pgntform->relpages = (int32) num_pages; ! dirty = true; ! } ! if (pgntform->reltuples != (float4) num_tuples) ! { ! pgntform->reltuples = (float4) num_tuples; ! dirty = true; ! } ! if (pgntform->relminxid != minxid) ! { ! pgntform->relminxid = minxid; ! dirty = true; ! } ! /* ! * If relminxid is Frozen (i.e., the table is truly frozen), it's more ! * useful to mark it as having vacuumxid frozen as well. This means ! * tht this table does not impose any particular limit to pg_clog ! * truncation. ! * ! * It seems a bit of a hack to be doing this here, but it would be even ! * uglier to have all the callers do it. ! */ ! if (TransactionIdEquals(minxid, FrozenTransactionId)) ! vacuumxid = FrozenTransactionId; ! if (pgntform->relvacuumxid != vacuumxid) ! { ! pgntform->relvacuumxid = vacuumxid; ! dirty = true; ! } ! /* If anything changed, write out the tuple */ ! if (dirty) ! update_ntclass_entry(rd, ctup); ! heap_freetuple(ctup); ! ! heap_close(rd, RowExclusiveLock); ! ! /* Now go for the pg_class tuple */ ! rd = heap_open(RelationRelationId, RowExclusiveLock); ! ! /* Fetch a copy of the tuple to scribble on */ ! ctup = SearchSysCacheCopy(RELOID, ! ObjectIdGetDatum(RelationGetRelid(rel)), ! 0, 0, 0); ! if (!HeapTupleIsValid(ctup)) ! elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", ! RelationGetRelid(rel)); ! pgcform = (Form_pg_class) GETSTRUCT(ctup); ! /* Apply required updates, if any, to copied tuple */ ! dirty = false; ! if (pgcform->relhasindex != hasindex) ! { ! pgcform->relhasindex = hasindex; ! dirty = true; ! } ! /* ! * If we have discovered that there are no indexes, then there's no ! * primary key either. This could be done more thoroughly... ! */ ! if (!hasindex) ! { ! if (pgcform->relhaspkey) ! { ! pgcform->relhaspkey = false; ! dirty = true; ! } ! } ! /* ! * If anything changed, write out the tuple ! */ ! if (dirty) ! heap_inplace_update(rd, ctup); ! heap_close(rd, RowExclusiveLock); ! heap_freetuple(ctup); } ! PG_CATCH(); { ! disable_heap_unfreeze = false; ! PG_RE_THROW(); } + PG_END_TRY(); ! disable_heap_unfreeze = false; } /* ! * vac_update_dbminxid() -- update the minimum Xid present in one database * ! * Update pg_database's datminxid and datvacuumxid, and the flat-file copy ! * of it. datminxid is updated to the minimum of all relminxid found in ! * pg_ntclass. * * We violate transaction semantics here by overwriting the database's ! * existing pg_database tuple with the new value. This is reasonably ! * safe since the new value is correct whether or not this transaction * commits. As with vac_update_relstats, this avoids leaving dead tuples * behind after a VACUUM. * ! * This routine is shared by full and lazy VACUUM. */ static void ! vac_update_dbminxid(Oid dbid) { HeapTuple tuple; Form_pg_database dbform; + Relation relation; + SysScanDesc scan; + HeapTuple ntclassTup; + TransactionId newMinXid = InvalidTransactionId; + TransactionId newVacXid = InvalidTransactionId; + bool dirty = false; ! PG_TRY(); ! { ! /* ! * Disable heap unfreezing of pg_database, since we are going ! * to update the tuple in-place and won't be writing our Xid on it. ! */ ! disable_heap_unfreeze = true; ! /* We must seqscan pg_ntclass to find the minimum Xid */ ! relation = heap_open(NtClassRelationId, AccessShareLock); ! scan = systable_beginscan(relation, InvalidOid, false, ! SnapshotNow, 0, NULL); ! while ((ntclassTup = systable_getnext(scan)) != NULL) ! { ! Form_pg_ntclass ntclassForm; ! ntclassForm = (Form_pg_ntclass) GETSTRUCT(ntclassTup); ! ! /* ! * Compute the minimum relminxid in all the tables in the database. ! * We consider only normal Xids --- this means in particular we ! * avoid setting the minimum to FrozenTransactionId here. If all ! * tables turn out to be frozen, we will exit the loop with the ! * value set to InvalidTransactionId. We cannot allow newMinXid to ! * be set to FrozenTransactionId --- that messes us up because of ! * the semantics of TransactionIdPrecedes. ! * ! * Other values we are ignoring here are InvalidTransactionId ! * (which is set for some bootstrap tables) and ! * BootstrapTransactionId. This doesn't cause any problems because ! * for all practical purposes they behave exactly like ! * FrozenTransactionId. ! */ ! if (TransactionIdIsValid(ntclassForm->relminxid) && ! TransactionIdIsNormal(ntclassForm->relminxid) && ! (!TransactionIdIsValid(newMinXid) || ! TransactionIdPrecedes(ntclassForm->relminxid, newMinXid))) ! newMinXid = ntclassForm->relminxid; ! ! /* ditto, for relvacuumxid */ ! if (TransactionIdIsValid(ntclassForm->relvacuumxid) && ! TransactionIdIsNormal(ntclassForm->relvacuumxid) && ! (!TransactionIdIsValid(newVacXid) || ! TransactionIdPrecedes(ntclassForm->relvacuumxid, newVacXid))) ! newVacXid = ntclassForm->relvacuumxid; ! } ! ! /* we're done with pg_ntclass */ ! systable_endscan(scan); ! heap_close(relation, AccessShareLock); + /* + * If we got InvalidTransactionId, then all tables must be frozen. As + * a special protection, we only allow a database to be wholly marked + * as "frozen" if this is a standalone backend. Otherwise, some other + * backend may be modifying a table behind our back; we can't safely + * assume that the database is truly frozen. So if we detect that all + * tables are frozen but we're running on a regular backend, fall back + * to storing RecentXmin in datminxid (the minimum Xid which could be + * unfreezing a table simultaneously.) + */ + if (!TransactionIdIsValid(newMinXid)) + newMinXid = IsPostmasterEnvironment ? RecentXmin : + FrozenTransactionId; + + /* + * In datvacuumxid, if we got InvalidTransactionId we use + * FrozenTransactionId in pg_database. This case is not valid as + * truncation point for pg_clog, but it's handled specially in + * vac_truncate_clog() because it's useful as a permanent sign + * that this database doesn't have a true lower limit on truncation. + */ + if (!TransactionIdIsValid(newVacXid)) + newVacXid = FrozenTransactionId; + + /* Now fetch the pg_database tuple we need to update. */ + relation = heap_open(DatabaseRelationId, RowExclusiveLock); + + /* Fetch a copy of the tuple to scribble on */ + tuple = SearchSysCacheCopy(DATABASEOID, + ObjectIdGetDatum(dbid), + 0, 0, 0); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for database %u", dbid); + + dbform = (Form_pg_database) GETSTRUCT(tuple); + + if (TransactionIdPrecedes(dbform->datminxid, newMinXid) || + TransactionIdEquals(newMinXid, FrozenTransactionId)) + { + dbform->datminxid = newMinXid; + dirty = true; + } + if (TransactionIdPrecedes(dbform->datvacuumxid, newVacXid) || + TransactionIdEquals(newVacXid, FrozenTransactionId)) + { + dbform->datvacuumxid = newVacXid; + dirty = true; + } + + if (dirty) + heap_inplace_update(relation, tuple); + + heap_freetuple(tuple); + + heap_close(relation, RowExclusiveLock); + } + PG_CATCH(); + { + /* Make sure to reenable heap unfreezing in case of error */ + disable_heap_unfreeze = false; + PG_RE_THROW(); + } + PG_END_TRY(); + + /* reenable heap unfreezing */ + disable_heap_unfreeze = false; + } /* * vac_truncate_clog() -- attempt to truncate the commit log *************** *** 841,868 **** * will generate more-annoying warnings, and ultimately refuse to issue * any more new XIDs. * - * The passed XIDs are simply the ones I just wrote into my pg_database - * entry. They're used to initialize the "min" calculations. - * * This routine is shared by full and lazy VACUUM. Note that it is only * applied after a database-wide VACUUM operation. */ static void ! vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) { TransactionId myXID = GetCurrentTransactionId(); Relation relation; HeapScanDesc scan; HeapTuple tuple; int32 age; NameData oldest_datname; bool vacuumAlreadyWrapped = false; ! bool frozenAlreadyWrapped = false; ! /* init oldest_datname to sync with my frozenXID */ namestrcpy(&oldest_datname, get_database_name(MyDatabaseId)); /* * Note: the "already wrapped" cases should now be impossible due to the * defenses in GetNewTransactionId, but we keep them anyway. */ --- 953,988 ---- * will generate more-annoying warnings, and ultimately refuse to issue * any more new XIDs. * * This routine is shared by full and lazy VACUUM. Note that it is only * applied after a database-wide VACUUM operation. */ static void ! vac_truncate_clog(void) { TransactionId myXID = GetCurrentTransactionId(); + TransactionId minXID; + TransactionId vacuumXID; Relation relation; HeapScanDesc scan; HeapTuple tuple; int32 age; NameData oldest_datname; bool vacuumAlreadyWrapped = false; ! bool minAlreadyWrapped = false; ! /* ! * Initialize the minimum values to a recent value. ! */ ! minXID = vacuumXID = RecentXmin; namestrcpy(&oldest_datname, get_database_name(MyDatabaseId)); /* + * Note we don't initialize the oldest database name here. This is because + * the name will only be used if myXID - minXID is some positive quantity, + * and if that happens, we will also initialize the name in the loop below. + */ + + /* * Note: the "already wrapped" cases should now be impossible due to the * defenses in GetNewTransactionId, but we keep them anyway. */ *************** *** 874,884 **** { Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); ! /* Ignore non-connectable databases (eg, template0) */ ! /* It's assumed that these have been frozen correctly */ ! if (!dbform->datallowconn) ! continue; ! if (TransactionIdIsNormal(dbform->datvacuumxid)) { if (TransactionIdPrecedes(myXID, dbform->datvacuumxid)) --- 994,1009 ---- { Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); ! /* ! * Note we ignore FrozenTransactionId here for both values. If all ! * databases turn out to be frozen, the values will end up as the ! * current XID, which is the correct truncation point for pg_clog and ! * also the correct value for the varsup.c limit. ! * ! * Also note that the all-databases-are-frozen case is pretty rare. ! * It can only happen if the user VACUUM FREEZEs all databases using ! * standalone backends. ! */ if (TransactionIdIsNormal(dbform->datvacuumxid)) { if (TransactionIdPrecedes(myXID, dbform->datvacuumxid)) *************** *** 886,898 **** else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID)) vacuumXID = dbform->datvacuumxid; } ! if (TransactionIdIsNormal(dbform->datfrozenxid)) { ! if (TransactionIdPrecedes(myXID, dbform->datfrozenxid)) ! frozenAlreadyWrapped = true; ! else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID)) { ! frozenXID = dbform->datfrozenxid; namecpy(&oldest_datname, &dbform->datname); } } --- 1011,1023 ---- else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID)) vacuumXID = dbform->datvacuumxid; } ! if (TransactionIdIsNormal(dbform->datminxid)) { ! if (TransactionIdPrecedes(myXID, dbform->datminxid)) ! minAlreadyWrapped = true; ! else if (TransactionIdPrecedes(dbform->datminxid, minXID)) { ! minXID = dbform->datminxid; namecpy(&oldest_datname, &dbform->datname); } } *************** *** 921,927 **** * Do not update varsup.c if we seem to have suffered wraparound already; * the computed XID might be bogus. */ ! if (frozenAlreadyWrapped) { ereport(WARNING, (errmsg("some databases have not been vacuumed in over 1 billion transactions"), --- 1046,1052 ---- * Do not update varsup.c if we seem to have suffered wraparound already; * the computed XID might be bogus. */ ! if (minAlreadyWrapped) { ereport(WARNING, (errmsg("some databases have not been vacuumed in over 1 billion transactions"), *************** *** 930,939 **** } /* Update the wrap limit for GetNewTransactionId */ ! SetTransactionIdLimit(frozenXID, &oldest_datname); /* Give warning about impending wraparound problems */ ! age = (int32) (myXID - frozenXID); if (age > (int32) ((MaxTransactionId >> 3) * 3)) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", --- 1055,1064 ---- } /* Update the wrap limit for GetNewTransactionId */ ! SetTransactionIdLimit(minXID, &oldest_datname); /* Give warning about impending wraparound problems */ ! age = (int32) (myXID - minXID); if (age > (int32) ((MaxTransactionId >> 3) * 3)) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", *************** *** 955,965 **** /* * vacuum_rel() -- vacuum one heap relation * - * Returns TRUE if we actually processed the relation (or can ignore it - * for some reason), FALSE if we failed to process it due to permissions - * or other reasons. (A FALSE result really means that some data - * may have been left unvacuumed, so we can't update XID stats.) - * * Doing one heap at a time incurs extra overhead, since we need to * check that the heap exists again just before we vacuum it. The * reason that we do this is so that vacuuming can be spread across --- 1080,1085 ---- *************** *** 968,981 **** * * At entry and exit, we are not inside a transaction. */ ! static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool allow_toast) { LOCKMODE lmode; Relation onerel; LockRelId onerelid; Oid toast_relid; - bool result; /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); --- 1088,1100 ---- * * At entry and exit, we are not inside a transaction. */ ! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool allow_toast) { LOCKMODE lmode; Relation onerel; LockRelId onerelid; Oid toast_relid; /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); *************** *** 1004,1021 **** { StrategyHintVacuum(false); CommitTransactionCommand(); ! return true; /* okay 'cause no data there */ } /* * Determine the type of lock we want --- hard exclusive lock for a FULL ! * vacuum, but just ShareUpdateExclusiveLock for concurrent vacuum. Either ! * way, we can be sure that no other backend is vacuuming the same table. */ ! lmode = vacstmt->full ? AccessExclusiveLock : ShareUpdateExclusiveLock; /* ! * Open the class, get an appropriate lock on it, and check permissions. * * We allow the user to vacuum a table if he is superuser, the table * owner, or the database owner (but in the latter case, only if it's not --- 1123,1164 ---- { StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* * Determine the type of lock we want --- hard exclusive lock for a FULL ! * vacuum, ExclusiveLock for VACUUM FREEZE, but just ! * ShareUpdateExclusiveLock for concurrent vacuum. Either way, we can be ! * sure that no other backend is vacuuming the same table. ! */ ! lmode = vacstmt->full ? AccessExclusiveLock : ! vacstmt->freeze ? ExclusiveLock : ShareUpdateExclusiveLock; ! ! /* ! * Open the class and get an appropriate lock on it. */ ! PG_TRY(); ! { ! /* ! * Note that we can skip unfreezing the table at this moment, because ! * we will update the correct relminxid later, if needed. Furthermore, ! * doing this allows us to skip vacuuming the table at all if it has ! * been frozen and not modified since. ! */ ! disable_heap_unfreeze = true; ! onerel = relation_open(relid, lmode); ! } ! PG_CATCH(); ! { ! disable_heap_unfreeze = false; ! PG_RE_THROW(); ! } ! PG_END_TRY(); ! disable_heap_unfreeze = false; /* ! * Check permissions on the table. * * We allow the user to vacuum a table if he is superuser, the table * owner, or the database owner (but in the latter case, only if it's not *************** *** 1024,1031 **** * Note we choose to treat permissions failure as a WARNING and keep * trying to vacuum the rest of the DB --- is this appropriate? */ - onerel = relation_open(relid, lmode); - if (!(pg_class_ownercheck(RelationGetRelid(onerel), GetUserId()) || (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !onerel->rd_rel->relisshared))) { --- 1167,1172 ---- *************** *** 1035,1041 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return false; } /* --- 1176,1182 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* *************** *** 1053,1059 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return false; } /* --- 1194,1200 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* *************** *** 1069,1075 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return false; } /* --- 1210,1216 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* *************** *** 1084,1090 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return true; /* assume no long-lived data in temp tables */ } /* --- 1225,1231 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; /* assume no long-lived data in temp tables */ } /* *************** *** 1113,1120 **** else lazy_vacuum_rel(onerel, vacstmt); - result = true; /* did the vacuum */ - /* all done with this class, but hold lock until commit */ relation_close(onerel, NoLock); --- 1254,1259 ---- *************** *** 1132,1148 **** * totally unimportant for toast relations. */ if (toast_relid != InvalidOid) ! { ! if (!vacuum_rel(toast_relid, vacstmt, true)) ! result = false; /* failed to vacuum the TOAST table? */ ! } /* * Now release the session-level lock on the master table. */ UnlockRelationForSession(&onerelid, lmode); - - return result; } --- 1271,1282 ---- * totally unimportant for toast relations. */ if (toast_relid != InvalidOid) ! vacuum_rel(toast_relid, vacstmt, true); /* * Now release the session-level lock on the master table. */ UnlockRelationForSession(&onerelid, lmode); } *************** *** 1174,1179 **** --- 1308,1315 ---- int nindexes, i; VRelStats *vacrelstats; + TransactionId FreezeLimit, + OldestXmin; vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit); *************** *** 1186,1194 **** vacrelstats->rel_tuples = 0; vacrelstats->hasindex = false; /* scan the heap */ vacuum_pages.num_pages = fraged_pages.num_pages = 0; ! scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages); /* Now open all indexes of the relation */ vac_open_indexes(onerel, AccessExclusiveLock, &nindexes, &Irel); --- 1322,1342 ---- vacrelstats->rel_tuples = 0; vacrelstats->hasindex = false; + /* + * Set initial minimum Xid, which will be updated if a smaller Xid is found + * in the relation by scan_heap. + * + * We use RecentXmin here (the minimum Xid that belongs to a transaction + * that is still open according to our snapshot), because it is the + * earliest transaction that could insert new tuples in the table after our + * VACUUM is done. + */ + vacrelstats->minxid = RecentXmin; + /* scan the heap */ vacuum_pages.num_pages = fraged_pages.num_pages = 0; ! scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages, FreezeLimit, ! OldestXmin); /* Now open all indexes of the relation */ vac_open_indexes(onerel, AccessExclusiveLock, &nindexes, &Irel); *************** *** 1216,1222 **** { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, ! nindexes, Irel); vac_close_indexes(nindexes, Irel, NoLock); } else --- 1364,1370 ---- { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, ! nindexes, Irel, OldestXmin); vac_close_indexes(nindexes, Irel, NoLock); } else *************** *** 1234,1240 **** /* update statistics in pg_class */ vac_update_relstats(onerel, vacrelstats->rel_pages, ! vacrelstats->rel_tuples, vacrelstats->hasindex); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, --- 1382,1389 ---- /* update statistics in pg_class */ vac_update_relstats(onerel, vacrelstats->rel_pages, ! vacrelstats->rel_tuples, vacrelstats->hasindex, ! vacrelstats->minxid, OldestXmin); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, *************** *** 1248,1263 **** * This routine sets commit status bits, constructs vacuum_pages (list * of pages we need to compact free space on and/or clean indexes of * deleted tuples), constructs fraged_pages (list of pages with free ! * space that tuples could be moved into), and calculates statistics ! * on the number of live tuples in the heap. */ static void scan_heap(VRelStats *vacrelstats, Relation onerel, ! VacPageList vacuum_pages, VacPageList fraged_pages) { BlockNumber nblocks, blkno; - HeapTupleData tuple; char *relname; VacPage vacpage; BlockNumber empty_pages, --- 1397,1413 ---- * This routine sets commit status bits, constructs vacuum_pages (list * of pages we need to compact free space on and/or clean indexes of * deleted tuples), constructs fraged_pages (list of pages with free ! * space that tuples could be moved into), calculates statistics on the ! * number of live tuples in the heap, and figures out the minimum normal ! * Xid present anywhere on the table. */ static void scan_heap(VRelStats *vacrelstats, Relation onerel, ! VacPageList vacuum_pages, VacPageList fraged_pages, ! TransactionId FreezeLimit, TransactionId OldestXmin) { BlockNumber nblocks, blkno; char *relname; VacPage vacpage; BlockNumber empty_pages, *************** *** 1371,1376 **** --- 1521,1527 ---- { ItemId itemid = PageGetItemId(page, offnum); bool tupgone = false; + HeapTupleData tuple; /* * Collect un-used items too - it's possible to have indexes *************** *** 1512,1517 **** --- 1663,1691 ---- min_tlen = tuple.t_len; if (tuple.t_len > max_tlen) max_tlen = tuple.t_len; + + /* + * Checks for pg_class.relminxid: determine the earliest + * Xid in any tuple of any table. + */ + if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple.t_data)) && + TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data), + vacrelstats->minxid)) + vacrelstats->minxid = HeapTupleHeaderGetXmin(tuple.t_data); + + /* + * If XMAX is not marked INVALID, we assume it's valid without + * making any check on it --- it must be recently obsoleted or + * still running, else HeapTupleSatisfiesVacuum would have + * deemed it removable. + */ + if (!(tuple.t_data->t_infomask | HEAP_XMAX_INVALID)) + { + if (TransactionIdIsNormal(HeapTupleHeaderGetXmax(tuple.t_data)) && + TransactionIdPrecedes(HeapTupleHeaderGetXmax(tuple.t_data), + vacrelstats->minxid)) + vacrelstats->minxid = HeapTupleHeaderGetXmax(tuple.t_data); + } } } /* scan along page */ *************** *** 1651,1657 **** static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, ! int nindexes, Relation *Irel) { TransactionId myXID = GetCurrentTransactionId(); Buffer dst_buffer = InvalidBuffer; --- 1825,1831 ---- static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, ! int nindexes, Relation *Irel, TransactionId OldestXmin) { TransactionId myXID = GetCurrentTransactionId(); Buffer dst_buffer = InvalidBuffer; *************** *** 2994,3000 **** /* now update statistics in pg_class */ vac_update_relstats(indrel, stats->num_pages, stats->num_index_tuples, ! false); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", --- 3168,3174 ---- /* now update statistics in pg_class */ vac_update_relstats(indrel, stats->num_pages, stats->num_index_tuples, ! false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", *************** *** 3063,3069 **** /* now update statistics in pg_class */ vac_update_relstats(indrel, stats->num_pages, stats->num_index_tuples, ! false); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", --- 3237,3243 ---- /* now update statistics in pg_class */ vac_update_relstats(indrel, stats->num_pages, stats->num_index_tuples, ! false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", diff -rc -X diff-ignore 11fixclass/src/backend/commands/vacuumlazy.c 12ntrelminxid/src/backend/commands/vacuumlazy.c *** 11fixclass/src/backend/commands/vacuumlazy.c 2006-06-10 19:17:47.000000000 -0400 --- 12ntrelminxid/src/backend/commands/vacuumlazy.c 2006-06-11 02:41:44.000000000 -0400 *************** *** 42,47 **** --- 42,50 ---- #include "access/genam.h" #include "access/heapam.h" #include "access/xlog.h" + #include "catalog/catalog.h" + #include "catalog/heap.h" + #include "catalog/pg_ntclass.h" #include "commands/vacuum.h" #include "miscadmin.h" #include "pgstat.h" *************** *** 72,77 **** --- 75,81 ---- double tuples_deleted; BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */ Size threshold; /* minimum interesting free space */ + TransactionId minxid; /* minimum Xid present anywhere in table */ /* List of TIDs of tuples we intend to delete */ /* NB: this list is ordered by TID address */ int num_dead_tuples; /* current # of entries */ *************** *** 88,100 **** static int elevel = -1; - static TransactionId OldestXmin; - static TransactionId FreezeLimit; - /* non-export function prototypes */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ! Relation *Irel, int nindexes); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, --- 92,102 ---- static int elevel = -1; /* non-export function prototypes */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ! Relation *Irel, int nindexes, TransactionId FreezeLimit, ! TransactionId OldestXmin); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, *************** *** 104,112 **** LVRelStats *vacrelstats); static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, int tupindex, LVRelStats *vacrelstats); ! static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats); static BlockNumber count_nondeletable_pages(Relation onerel, ! LVRelStats *vacrelstats); static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks); static void lazy_record_dead_tuple(LVRelStats *vacrelstats, ItemPointer itemptr); --- 106,115 ---- LVRelStats *vacrelstats); static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, int tupindex, LVRelStats *vacrelstats); ! static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, ! TransactionId OldestXmin); static BlockNumber count_nondeletable_pages(Relation onerel, ! LVRelStats *vacrelstats, TransactionId OldestXmin); static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks); static void lazy_record_dead_tuple(LVRelStats *vacrelstats, ItemPointer itemptr); *************** *** 122,128 **** * lazy_vacuum_rel() -- perform LAZY VACUUM for one heap relation * * This routine vacuums a single heap, cleans out its indexes, and ! * updates its num_pages and num_tuples statistics. * * At entry, we have already established a transaction and opened * and locked the relation. --- 125,132 ---- * lazy_vacuum_rel() -- perform LAZY VACUUM for one heap relation * * This routine vacuums a single heap, cleans out its indexes, and ! * updates its relpages and reltuples statistics, as well as the ! * relminxid and relvacuumxid information. * * At entry, we have already established a transaction and opened * and locked the relation. *************** *** 135,146 **** --- 139,177 ---- int nindexes; bool hasindex; BlockNumber possibly_freeable; + TransactionId OldestXmin, + FreezeLimit; if (vacstmt->verbose) elevel = INFO; else elevel = DEBUG2; + /* + * We can skip vacuuming a frozen table, since we know nobody has touched + * it since the last VACUUM. + */ + { + HeapTuple ctup; + Form_pg_ntclass ntForm; + Relation ntRel; + bool canskip = false; + + ntRel = heap_open(NtClassRelationId, AccessShareLock); + ctup = get_ntclass_entry(ntRel, onerel); + + ntForm = (Form_pg_ntclass) GETSTRUCT(ctup); + + if (TransactionIdEquals(ntForm->relminxid, FrozenTransactionId)) + canskip = true; + + heap_freetuple(ctup); + heap_close(ntRel, AccessShareLock); + + if (canskip) + return; + } + vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit); *************** *** 150,161 **** /* XXX should we scale it up or down? Adjust vacuum.c too, if so */ vacrelstats->threshold = GetAvgFSMRequestSize(&onerel->rd_node); /* Open all indexes of the relation */ vac_open_indexes(onerel, ShareUpdateExclusiveLock, &nindexes, &Irel); hasindex = (nindexes > 0); /* Do the vacuuming */ ! lazy_scan_heap(onerel, vacrelstats, Irel, nindexes); /* Done with indexes */ vac_close_indexes(nindexes, Irel, NoLock); --- 181,214 ---- /* XXX should we scale it up or down? Adjust vacuum.c too, if so */ vacrelstats->threshold = GetAvgFSMRequestSize(&onerel->rd_node); + /* + * Set initial minimum Xid, which will be updated if a smaller Xid is found + * in the relation by lazy_scan_heap. + * + * In VACUUM FREEZE, we use FrozenTransactionId here. This is safe + * because we acquired ExclusiveLock above, so no one can be inserting + * newer tuples in pages earlier to those we have scanned. If there's any + * tuple whose Xid we can't change, the lower bound will be raised. + * + * In the non-FREEZE case, we use RecentXmin here (the minimum Xid that + * belongs to a transaction that is still open according to our snapshot), + * because it is the earliest transaction that could concurrently insert + * new tuples in the table. + * + * The FREEZE case doesn't have an equivalent in VACUUM FULL because FULL + * in combination with FREEZE is verboten. + */ + if (vacstmt->freeze) + vacrelstats->minxid = FrozenTransactionId; + else + vacrelstats->minxid = RecentXmin; + /* Open all indexes of the relation */ vac_open_indexes(onerel, ShareUpdateExclusiveLock, &nindexes, &Irel); hasindex = (nindexes > 0); /* Do the vacuuming */ ! lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, FreezeLimit, OldestXmin); /* Done with indexes */ vac_close_indexes(nindexes, Irel, NoLock); *************** *** 169,175 **** possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; if (possibly_freeable >= REL_TRUNCATE_MINIMUM || possibly_freeable >= vacrelstats->rel_pages / REL_TRUNCATE_FRACTION) ! lazy_truncate_heap(onerel, vacrelstats); /* Update shared free space map with final free space info */ lazy_update_fsm(onerel, vacrelstats); --- 222,228 ---- possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; if (possibly_freeable >= REL_TRUNCATE_MINIMUM || possibly_freeable >= vacrelstats->rel_pages / REL_TRUNCATE_FRACTION) ! lazy_truncate_heap(onerel, vacrelstats, OldestXmin); /* Update shared free space map with final free space info */ lazy_update_fsm(onerel, vacrelstats); *************** *** 177,184 **** /* Update statistics in pg_class */ vac_update_relstats(onerel, vacrelstats->rel_pages, ! vacrelstats->rel_tuples, ! hasindex); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, --- 230,237 ---- /* Update statistics in pg_class */ vac_update_relstats(onerel, vacrelstats->rel_pages, ! vacrelstats->rel_tuples, hasindex, ! vacrelstats->minxid, OldestXmin); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, *************** *** 193,202 **** * and pages with free space, and calculates statistics on the number * of live tuples in the heap. When done, or when we run low on space * for dead-tuple TIDs, invoke vacuuming of indexes and heap. */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ! Relation *Irel, int nindexes) { BlockNumber nblocks, blkno; --- 246,259 ---- * and pages with free space, and calculates statistics on the number * of live tuples in the heap. When done, or when we run low on space * for dead-tuple TIDs, invoke vacuuming of indexes and heap. + * + * It also stores in vacrelstats.minxid the minimum Xid found anywhere on + * the table, for later recording it in pg_ntclass.relminxid. */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ! Relation *Irel, int nindexes, TransactionId FreezeLimit, ! TransactionId OldestXmin) { BlockNumber nblocks, blkno; *************** *** 408,413 **** --- 465,484 ---- { num_tuples += 1; hastup = true; + + /* + * If the tuple is alive, we consider it for the "minxid" + * calculations. + */ + if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple.t_data)) && + TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data), + vacrelstats->minxid)) + vacrelstats->minxid = HeapTupleHeaderGetXmin(tuple.t_data); + + if (TransactionIdIsNormal(HeapTupleHeaderGetXmax(tuple.t_data)) && + TransactionIdPrecedes(HeapTupleHeaderGetXmax(tuple.t_data), + vacrelstats->minxid)) + vacrelstats->minxid = HeapTupleHeaderGetXmax(tuple.t_data); } } /* scan along page */ *************** *** 668,676 **** /* now update statistics in pg_class */ vac_update_relstats(indrel, ! stats->num_pages, ! stats->num_index_tuples, ! false); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", --- 739,746 ---- /* now update statistics in pg_class */ vac_update_relstats(indrel, ! stats->num_pages, stats->num_index_tuples, ! false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", *************** *** 691,697 **** * lazy_truncate_heap - try to truncate off any empty pages at the end */ static void ! lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) { BlockNumber old_rel_pages = vacrelstats->rel_pages; BlockNumber new_rel_pages; --- 761,768 ---- * lazy_truncate_heap - try to truncate off any empty pages at the end */ static void ! lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, ! TransactionId OldestXmin) { BlockNumber old_rel_pages = vacrelstats->rel_pages; BlockNumber new_rel_pages; *************** *** 732,738 **** * because other backends could have added tuples to these pages whilst we * were vacuuming. */ ! new_rel_pages = count_nondeletable_pages(onerel, vacrelstats); if (new_rel_pages >= old_rel_pages) { --- 803,809 ---- * because other backends could have added tuples to these pages whilst we * were vacuuming. */ ! new_rel_pages = count_nondeletable_pages(onerel, vacrelstats, OldestXmin); if (new_rel_pages >= old_rel_pages) { *************** *** 787,793 **** * Returns number of nondeletable pages (last nonempty page + 1). */ static BlockNumber ! count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats) { BlockNumber blkno; HeapTupleData tuple; --- 858,865 ---- * Returns number of nondeletable pages (last nonempty page + 1). */ static BlockNumber ! count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats, ! TransactionId OldestXmin) { BlockNumber blkno; HeapTupleData tuple; diff -rc -X diff-ignore 11fixclass/src/backend/libpq/hba.c 12ntrelminxid/src/backend/libpq/hba.c *** 11fixclass/src/backend/libpq/hba.c 2006-03-08 16:23:25.000000000 -0300 --- 12ntrelminxid/src/backend/libpq/hba.c 2006-06-10 19:38:04.000000000 -0400 *************** *** 1005,1011 **** * dbname: gets database name (must be of size NAMEDATALEN bytes) * dboid: gets database OID * dbtablespace: gets database's default tablespace's OID ! * dbfrozenxid: gets database's frozen XID * dbvacuumxid: gets database's vacuum XID * * This is not much related to the other functions in hba.c, but we put it --- 1005,1011 ---- * dbname: gets database name (must be of size NAMEDATALEN bytes) * dboid: gets database OID * dbtablespace: gets database's default tablespace's OID ! * dbminxid: gets database's minimum XID * dbvacuumxid: gets database's vacuum XID * * This is not much related to the other functions in hba.c, but we put it *************** *** 1013,1019 **** */ bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, ! Oid *dbtablespace, TransactionId *dbfrozenxid, TransactionId *dbvacuumxid) { char buf[MAX_TOKEN]; --- 1013,1019 ---- */ bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, ! Oid *dbtablespace, TransactionId *dbminxid, TransactionId *dbvacuumxid) { char buf[MAX_TOKEN]; *************** *** 1036,1042 **** next_token(fp, buf, sizeof(buf)); if (!isdigit((unsigned char) buf[0])) elog(FATAL, "bad data in flat pg_database file"); ! *dbfrozenxid = atoxid(buf); next_token(fp, buf, sizeof(buf)); if (!isdigit((unsigned char) buf[0])) elog(FATAL, "bad data in flat pg_database file"); --- 1036,1042 ---- next_token(fp, buf, sizeof(buf)); if (!isdigit((unsigned char) buf[0])) elog(FATAL, "bad data in flat pg_database file"); ! *dbminxid = atoxid(buf); next_token(fp, buf, sizeof(buf)); if (!isdigit((unsigned char) buf[0])) elog(FATAL, "bad data in flat pg_database file"); diff -rc -X diff-ignore 11fixclass/src/backend/postmaster/autovacuum.c 12ntrelminxid/src/backend/postmaster/autovacuum.c *** 11fixclass/src/backend/postmaster/autovacuum.c 2006-06-10 19:17:47.000000000 -0400 --- 12ntrelminxid/src/backend/postmaster/autovacuum.c 2006-06-10 19:38:04.000000000 -0400 *************** *** 80,86 **** { Oid oid; char *name; ! TransactionId frozenxid; TransactionId vacuumxid; PgStat_StatDBEntry *entry; int32 age; --- 80,86 ---- { Oid oid; char *name; ! TransactionId minxid; TransactionId vacuumxid; PgStat_StatDBEntry *entry; int32 age; *************** *** 350,356 **** { autovac_dbase *tmp = lfirst(cell); bool this_whole_db; ! int32 freeze_age, vacuum_age; /* --- 350,356 ---- { autovac_dbase *tmp = lfirst(cell); bool this_whole_db; ! int32 true_age, vacuum_age; /* *************** *** 363,371 **** * Unlike vacuum.c, we also look at vacuumxid. This is so that * pg_clog can be kept trimmed to a reasonable size. */ ! freeze_age = (int32) (nextXid - tmp->frozenxid); vacuum_age = (int32) (nextXid - tmp->vacuumxid); ! tmp->age = Max(freeze_age, vacuum_age); this_whole_db = (tmp->age > (int32) ((MaxTransactionId >> 3) * 3 - 100000)); --- 363,371 ---- * Unlike vacuum.c, we also look at vacuumxid. This is so that * pg_clog can be kept trimmed to a reasonable size. */ ! true_age = (int32) (nextXid - tmp->minxid); vacuum_age = (int32) (nextXid - tmp->vacuumxid); ! tmp->age = Max(true_age, vacuum_age); this_whole_db = (tmp->age > (int32) ((MaxTransactionId >> 3) * 3 - 100000)); *************** *** 456,462 **** FILE *db_file; Oid db_id; Oid db_tablespace; ! TransactionId db_frozenxid; TransactionId db_vacuumxid; filename = database_getflatfilename(); --- 456,462 ---- FILE *db_file; Oid db_id; Oid db_tablespace; ! TransactionId db_minxid; TransactionId db_vacuumxid; filename = database_getflatfilename(); *************** *** 467,473 **** errmsg("could not open file \"%s\": %m", filename))); while (read_pg_database_line(db_file, thisname, &db_id, ! &db_tablespace, &db_frozenxid, &db_vacuumxid)) { autovac_dbase *db; --- 467,473 ---- errmsg("could not open file \"%s\": %m", filename))); while (read_pg_database_line(db_file, thisname, &db_id, ! &db_tablespace, &db_minxid, &db_vacuumxid)) { autovac_dbase *db; *************** *** 476,482 **** db->oid = db_id; db->name = pstrdup(thisname); ! db->frozenxid = db_frozenxid; db->vacuumxid = db_vacuumxid; /* these get set later: */ db->entry = NULL; --- 476,482 ---- db->oid = db_id; db->name = pstrdup(thisname); ! db->minxid = db_minxid; db->vacuumxid = db_vacuumxid; /* these get set later: */ db->entry = NULL; diff -rc -X diff-ignore 11fixclass/src/backend/storage/lmgr/lmgr.c 12ntrelminxid/src/backend/storage/lmgr/lmgr.c *** 11fixclass/src/backend/storage/lmgr/lmgr.c 2006-05-04 21:36:11.000000000 -0400 --- 12ntrelminxid/src/backend/storage/lmgr/lmgr.c 2006-06-10 22:42:34.000000000 -0400 *************** *** 15,20 **** --- 15,21 ---- #include "postgres.h" + #include "access/heapam.h" #include "access/subtrans.h" #include "access/transam.h" #include "access/xact.h" *************** *** 53,58 **** --- 54,66 ---- LOCKTAG tag; LockAcquireResult res; + /* + * If somebody tries to lock a relation for more than a simple SELECT, + * unfreeze it. + */ + if (lockmode > AccessShareLock) + heap_unfreeze(relation); + SET_LOCKTAG_RELATION(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); *************** *** 89,94 **** --- 97,109 ---- LOCKTAG tag; LockAcquireResult res; + /* + * If somebody tries to lock a relation for more than a simple SELECT, + * unfreeze it. + */ + if (lockmode > AccessShareLock) + heap_unfreeze(relation); + SET_LOCKTAG_RELATION(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); diff -rc -X diff-ignore 11fixclass/src/backend/utils/init/flatfiles.c 12ntrelminxid/src/backend/utils/init/flatfiles.c *** 11fixclass/src/backend/utils/init/flatfiles.c 2006-05-04 21:36:12.000000000 -0400 --- 12ntrelminxid/src/backend/utils/init/flatfiles.c 2006-06-10 19:38:04.000000000 -0400 *************** *** 163,169 **** /* * write_database_file: update the flat database file * ! * A side effect is to determine the oldest database's datfrozenxid * so we can set or update the XID wrap limit. */ static void --- 163,169 ---- /* * write_database_file: update the flat database file * ! * A side effect is to determine the oldest database's datminxid * so we can set or update the XID wrap limit. */ static void *************** *** 177,183 **** HeapScanDesc scan; HeapTuple tuple; NameData oldest_datname; ! TransactionId oldest_datfrozenxid = InvalidTransactionId; /* * Create a temporary filename to be renamed later. This prevents the --- 177,183 ---- HeapScanDesc scan; HeapTuple tuple; NameData oldest_datname; ! TransactionId oldest_datminxid = InvalidTransactionId; /* * Create a temporary filename to be renamed later. This prevents the *************** *** 208,234 **** char *datname; Oid datoid; Oid dattablespace; ! TransactionId datfrozenxid, datvacuumxid; datname = NameStr(dbform->datname); datoid = HeapTupleGetOid(tuple); dattablespace = dbform->dattablespace; ! datfrozenxid = dbform->datfrozenxid; datvacuumxid = dbform->datvacuumxid; /* ! * Identify the oldest datfrozenxid, ignoring databases that are not * connectable (we assume they are safely frozen). This must match * the logic in vac_truncate_clog() in vacuum.c. */ if (dbform->datallowconn && ! TransactionIdIsNormal(datfrozenxid)) { ! if (oldest_datfrozenxid == InvalidTransactionId || ! TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid)) { ! oldest_datfrozenxid = datfrozenxid; namestrcpy(&oldest_datname, datname); } } --- 208,234 ---- char *datname; Oid datoid; Oid dattablespace; ! TransactionId datminxid, datvacuumxid; datname = NameStr(dbform->datname); datoid = HeapTupleGetOid(tuple); dattablespace = dbform->dattablespace; ! datminxid = dbform->datminxid; datvacuumxid = dbform->datvacuumxid; /* ! * Identify the oldest datminxid, ignoring databases that are not * connectable (we assume they are safely frozen). This must match * the logic in vac_truncate_clog() in vacuum.c. */ if (dbform->datallowconn && ! TransactionIdIsNormal(datminxid)) { ! if (oldest_datminxid == InvalidTransactionId || ! TransactionIdPrecedes(datminxid, oldest_datminxid)) { ! oldest_datminxid = datminxid; namestrcpy(&oldest_datname, datname); } } *************** *** 244,257 **** } /* ! * The file format is: "dbname" oid tablespace frozenxid vacuumxid * * The xids are not needed for backend startup, but are of use to * autovacuum, and might also be helpful for forensic purposes. */ fputs_quote(datname, fp); fprintf(fp, " %u %u %u %u\n", ! datoid, dattablespace, datfrozenxid, datvacuumxid); } heap_endscan(scan); --- 244,257 ---- } /* ! * The file format is: "dbname" oid tablespace minxid vacuumxid * * The xids are not needed for backend startup, but are of use to * autovacuum, and might also be helpful for forensic purposes. */ fputs_quote(datname, fp); fprintf(fp, " %u %u %u %u\n", ! datoid, dattablespace, datminxid, datvacuumxid); } heap_endscan(scan); *************** *** 272,281 **** tempname, filename))); /* ! * Set the transaction ID wrap limit using the oldest datfrozenxid */ ! if (oldest_datfrozenxid != InvalidTransactionId) ! SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname); } --- 272,281 ---- tempname, filename))); /* ! * Set the transaction ID wrap limit using the oldest datminxid */ ! if (oldest_datminxid != InvalidTransactionId) ! SetTransactionIdLimit(oldest_datminxid, &oldest_datname); } diff -rc -X diff-ignore 11fixclass/src/backend/utils/init/postinit.c 12ntrelminxid/src/backend/utils/init/postinit.c *** 11fixclass/src/backend/utils/init/postinit.c 2006-05-04 21:36:12.000000000 -0400 --- 12ntrelminxid/src/backend/utils/init/postinit.c 2006-06-10 19:38:04.000000000 -0400 *************** *** 24,29 **** --- 24,30 ---- #include "catalog/pg_authid.h" #include "catalog/pg_database.h" #include "catalog/pg_tablespace.h" + #include "commands/dbcommands.h" #include "libpq/hba.h" #include "mb/pg_wchar.h" #include "miscadmin.h" *************** *** 193,198 **** --- 194,210 ---- PGC_BACKEND, PGC_S_DEFAULT); /* + * If the database is marked as frozen, unfreeze it to make sure we won't + * leave non-vacuumed tuples hidden behind a frozen pg_database entry. + * + * This is more paranoid than it needs to be -- if we had a way of + * declaring a session as being guaranteed-read-only, we could skip doing + * this for such sessions. In the meantime, be safe. + */ + if (TransactionIdEquals(dbform->datminxid, FrozenTransactionId)) + UnfreezeDatabase(MyDatabaseId, GetCurrentTransactionId()); + + /* * Lastly, set up any database-specific configuration variables. */ if (IsUnderPostmaster) diff -rc -X diff-ignore 11fixclass/src/backend/utils/mmgr/aset.c 12ntrelminxid/src/backend/utils/mmgr/aset.c *** 11fixclass/src/backend/utils/mmgr/aset.c 2006-03-08 16:23:32.000000000 -0300 --- 12ntrelminxid/src/backend/utils/mmgr/aset.c 2006-06-11 02:50:35.000000000 -0400 *************** *** 11,17 **** * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION ! * $PostgreSQL: pgsql/src/backend/utils/mmgr/aset.c,v 1.66 2006-03-05 15:58:49 momjian Exp $ * * NOTE: * This is a new (Feb. 05, 1999) implementation of the allocation set --- 11,17 ---- * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION ! * $PostgreSQL: pgsql/src/backend/utils/mmgr/aset.c,v 1.66 2006/03/05 15:58:49 momjian Exp $ * * NOTE: * This is a new (Feb. 05, 1999) implementation of the allocation set diff -rc -X diff-ignore 11fixclass/src/bin/initdb/initdb.c 12ntrelminxid/src/bin/initdb/initdb.c *** 11fixclass/src/bin/initdb/initdb.c 2006-06-11 17:31:15.000000000 -0400 --- 12ntrelminxid/src/bin/initdb/initdb.c 2006-06-11 17:56:25.000000000 -0400 *************** *** 184,189 **** --- 184,190 ---- static void setup_schema(void); static void vacuum_db(void); static void make_template0(void); + static void freeze_template0(void); static void make_postgres(void); static void trapsig(int signum); static void check_ok(void); *************** *** 2014,2019 **** --- 2015,2054 ---- } /* + * freeze template0 + * + * Note that this routine connects to template0, not template1 like all the + * rest. + */ + static void + freeze_template0(void) + { + PG_CMD_DECL; + char **line; + static char *template_freeze[] = { + "VACUUM FREEZE;\n", + NULL + }; + + fputs(_("freezing template0 ... "), stdout); + fflush(stdout); + + snprintf(cmd, sizeof(cmd), + "\"%s\" %s template0 >%s", + backend_exec, backend_options, + DEVNULL); + + PG_CMD_OPEN; + + for (line = template_freeze; *line; line++) + PG_CMD_PUTS(*line); + + PG_CMD_CLOSE; + + check_ok(); + } + + /* * copy template1 to postgres */ static void *************** *** 2953,2958 **** --- 2988,2995 ---- make_template0(); + freeze_template0(); + make_postgres(); if (authwarning != NULL) Sólo en 12ntrelminxid/src/bin/initdb: .#initdb.c.1.116 diff -rc -X diff-ignore 11fixclass/src/include/access/heapam.h 12ntrelminxid/src/include/access/heapam.h *** 11fixclass/src/include/access/heapam.h 2006-05-13 19:12:34.000000000 -0400 --- 12ntrelminxid/src/include/access/heapam.h 2006-06-10 19:38:04.000000000 -0400 *************** *** 122,127 **** --- 122,129 ---- /* heapam.c */ + extern bool disable_heap_unfreeze; + typedef enum { LockTupleShared, *************** *** 168,173 **** --- 170,176 ---- Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, CommandId cid, LockTupleMode mode, bool nowait); + extern void heap_unfreeze(Relation rel); extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern Oid simple_heap_insert(Relation relation, HeapTuple tup); diff -rc -X diff-ignore 11fixclass/src/include/access/transam.h 12ntrelminxid/src/include/access/transam.h *** 11fixclass/src/include/access/transam.h 2006-03-08 16:23:46.000000000 -0300 --- 12ntrelminxid/src/include/access/transam.h 2006-06-10 19:38:04.000000000 -0400 *************** *** 123,129 **** /* in transam/varsup.c */ extern TransactionId GetNewTransactionId(bool isSubXact); extern TransactionId ReadNewTransactionId(void); ! extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Name oldest_datname); extern Oid GetNewObjectId(void); --- 123,129 ---- /* in transam/varsup.c */ extern TransactionId GetNewTransactionId(bool isSubXact); extern TransactionId ReadNewTransactionId(void); ! extern void SetTransactionIdLimit(TransactionId oldest_datminxid, Name oldest_datname); extern Oid GetNewObjectId(void); diff -rc -X diff-ignore 11fixclass/src/include/catalog/pg_attribute.h 12ntrelminxid/src/include/catalog/pg_attribute.h *** 11fixclass/src/include/catalog/pg_attribute.h 2006-06-09 18:47:58.000000000 -0400 --- 12ntrelminxid/src/include/catalog/pg_attribute.h 2006-06-10 22:45:18.000000000 -0400 *************** *** 442,452 **** * ---------------- */ #define Schema_pg_ntclass \ ! { 1004, {"relpages"}, 23, -1, 4, 1, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ ! { 1004, {"reltuples"}, 700, -1, 4, 2, 0, -1, -1, false, 'p', 'i', true, false, false, true, 0 } DATA(insert ( 1004 relpages 23 -1 4 1 0 -1 -1 t p i t f f t 0)); DATA(insert ( 1004 reltuples 700 -1 4 2 0 -1 -1 f p i t f f t 0)); DATA(insert ( 1004 ctid 27 0 6 -1 0 -1 -1 f p s t f f t 0)); /* no OIDs in pg_ntclass */ DATA(insert ( 1004 xmin 28 0 4 -3 0 -1 -1 t p i t f f t 0)); --- 442,456 ---- * ---------------- */ #define Schema_pg_ntclass \ ! { 1004, {"relpages"}, 23, -1, 4, 1, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ ! { 1004, {"reltuples"}, 700, -1, 4, 2, 0, -1, -1, false, 'p', 'i', true, false, false, true, 0 }, \ ! { 1004, {"relminxid"}, 28, -1, 4, 3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ ! { 1004, {"relvacuumxid"}, 28, -1, 4, 4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 } DATA(insert ( 1004 relpages 23 -1 4 1 0 -1 -1 t p i t f f t 0)); DATA(insert ( 1004 reltuples 700 -1 4 2 0 -1 -1 f p i t f f t 0)); + DATA(insert ( 1004 relminxid 28 -1 4 3 0 -1 -1 t p i t f f t 0)); + DATA(insert ( 1004 relvacuumxid 28 -1 4 4 0 -1 -1 t p i t f f t 0)); DATA(insert ( 1004 ctid 27 0 6 -1 0 -1 -1 f p s t f f t 0)); /* no OIDs in pg_ntclass */ DATA(insert ( 1004 xmin 28 0 4 -3 0 -1 -1 t p i t f f t 0)); diff -rc -X diff-ignore 11fixclass/src/include/catalog/pg_class.h 12ntrelminxid/src/include/catalog/pg_class.h *** 11fixclass/src/include/catalog/pg_class.h 2006-06-10 18:10:25.000000000 -0400 --- 12ntrelminxid/src/include/catalog/pg_class.h 2006-06-11 01:42:29.000000000 -0400 *************** *** 149,155 **** DESCR(""); DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 "(0,4)" f f r 24 0 0 0 0 0 t f f f _null_ )); DESCR(""); ! DATA(insert OID = 1004 ( pg_ntclass PGNSP 86 PGUID 0 1004 0 0 0 "(0,5)" f f n 2 0 0 0 0 0 f f f f _null_ )); DESCR(""); #define RELKIND_INDEX 'i' /* secondary index */ --- 149,155 ---- DESCR(""); DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 "(0,4)" f f r 24 0 0 0 0 0 t f f f _null_ )); DESCR(""); ! DATA(insert OID = 1004 ( pg_ntclass PGNSP 86 PGUID 0 1004 0 0 0 "(0,5)" f f n 4 0 0 0 0 0 f f f f _null_ )); DESCR(""); #define RELKIND_INDEX 'i' /* secondary index */ diff -rc -X diff-ignore 11fixclass/src/include/catalog/pg_database.h 12ntrelminxid/src/include/catalog/pg_database.h *** 11fixclass/src/include/catalog/pg_database.h 2006-03-08 16:23:46.000000000 -0300 --- 12ntrelminxid/src/include/catalog/pg_database.h 2006-06-10 22:39:48.000000000 -0400 *************** *** 43,49 **** int4 datconnlimit; /* max connections allowed (-1=no limit) */ Oid datlastsysoid; /* highest OID to consider a system OID */ TransactionId datvacuumxid; /* all XIDs before this are vacuumed */ ! TransactionId datfrozenxid; /* all XIDs before this are frozen */ Oid dattablespace; /* default table space for this DB */ text datconfig[1]; /* database-specific GUC (VAR LENGTH) */ aclitem datacl[1]; /* access permissions (VAR LENGTH) */ --- 43,49 ---- int4 datconnlimit; /* max connections allowed (-1=no limit) */ Oid datlastsysoid; /* highest OID to consider a system OID */ TransactionId datvacuumxid; /* all XIDs before this are vacuumed */ ! TransactionId datminxid; /* minimum XID present anywhere in the DB */ Oid dattablespace; /* default table space for this DB */ text datconfig[1]; /* database-specific GUC (VAR LENGTH) */ aclitem datacl[1]; /* access permissions (VAR LENGTH) */ *************** *** 69,75 **** #define Anum_pg_database_datconnlimit 6 #define Anum_pg_database_datlastsysoid 7 #define Anum_pg_database_datvacuumxid 8 ! #define Anum_pg_database_datfrozenxid 9 #define Anum_pg_database_dattablespace 10 #define Anum_pg_database_datconfig 11 #define Anum_pg_database_datacl 12 --- 69,75 ---- #define Anum_pg_database_datconnlimit 6 #define Anum_pg_database_datlastsysoid 7 #define Anum_pg_database_datvacuumxid 8 ! #define Anum_pg_database_datminxid 9 #define Anum_pg_database_dattablespace 10 #define Anum_pg_database_datconfig 11 #define Anum_pg_database_datacl 12 diff -rc -X diff-ignore 11fixclass/src/include/catalog/pg_ntclass.h 12ntrelminxid/src/include/catalog/pg_ntclass.h *** 11fixclass/src/include/catalog/pg_ntclass.h 2006-06-10 22:21:01.000000000 -0400 --- 12ntrelminxid/src/include/catalog/pg_ntclass.h 2006-06-11 01:33:58.000000000 -0400 *************** *** 38,48 **** { int4 relpages; /* # of blocks (not always up-to-date) */ float4 reltuples; /* # of tuples (not always up-to-date) */ } FormData_pg_ntclass; /* Size of pg_ntclass tuples */ #define NTCLASS_TUPLE_SIZE \ ! (offsetof(FormData_pg_ntclass,reltuples) + sizeof(float4)) /* ---------------- * Form_pg_ntclass corresponds to a pointer to a tuple with * the format of pg_ntclass relation. --- 38,50 ---- { int4 relpages; /* # of blocks (not always up-to-date) */ float4 reltuples; /* # of tuples (not always up-to-date) */ + TransactionId relminxid; /* minimum Xid present in table */ + TransactionId relvacuumxid; /* cutoff point used at last VACUUM */ } FormData_pg_ntclass; /* Size of pg_ntclass tuples */ #define NTCLASS_TUPLE_SIZE \ ! (offsetof(FormData_pg_ntclass,relvacuumxid) + sizeof(TransactionId)) /* ---------------- * Form_pg_ntclass corresponds to a pointer to a tuple with * the format of pg_ntclass relation. *************** *** 54,62 **** * compiler constants for pg_ntclass * ---------------- */ ! #define Natts_pg_ntclass 2 #define Anum_pg_ntclass_relpages 1 #define Anum_pg_ntclass_reltuples 2 /* ---------------- * initial contents of pg_ntclass --- 56,66 ---- * compiler constants for pg_ntclass * ---------------- */ ! #define Natts_pg_ntclass 4 #define Anum_pg_ntclass_relpages 1 #define Anum_pg_ntclass_reltuples 2 + #define Anum_pg_ntclass_relminxid 3 + #define Anum_pg_ntclass_relvacuumxid 4 /* ---------------- * initial contents of pg_ntclass *************** *** 66,75 **** * ---------------- */ ! DATA(insert ( 0 0 )); ! DATA(insert ( 0 0 )); ! DATA(insert ( 0 0 )); ! DATA(insert ( 0 0 )); ! DATA(insert ( 0 0 )); #endif /* PG_NTCLASS_H */ --- 70,79 ---- * ---------------- */ ! DATA(insert ( 0 0 0 0 )); ! DATA(insert ( 0 0 0 0 )); ! DATA(insert ( 0 0 0 0 )); ! DATA(insert ( 0 0 0 0 )); ! DATA(insert ( 0 0 0 0 )); #endif /* PG_NTCLASS_H */ diff -rc -X diff-ignore 11fixclass/src/include/commands/dbcommands.h 12ntrelminxid/src/include/commands/dbcommands.h *** 11fixclass/src/include/commands/dbcommands.h 2006-05-04 21:36:12.000000000 -0400 --- 12ntrelminxid/src/include/commands/dbcommands.h 2006-06-10 19:38:04.000000000 -0400 *************** *** 58,63 **** --- 58,64 ---- extern void AlterDatabase(AlterDatabaseStmt *stmt); extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt); extern void AlterDatabaseOwner(const char *dbname, Oid newOwnerId); + extern void UnfreezeDatabase(Oid dbid, TransactionId unfreezeXid); extern Oid get_database_oid(const char *dbname); extern char *get_database_name(Oid dbid); diff -rc -X diff-ignore 11fixclass/src/include/commands/vacuum.h 12ntrelminxid/src/include/commands/vacuum.h *** 11fixclass/src/include/commands/vacuum.h 2006-06-04 22:36:52.000000000 -0400 --- 12ntrelminxid/src/include/commands/vacuum.h 2006-06-10 20:22:17.000000000 -0400 *************** *** 114,123 **** extern void vac_open_indexes(Relation relation, LOCKMODE lockmode, int *nindexes, Relation **Irel); extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode); ! extern void vac_update_relstats(Relation rel, ! BlockNumber num_pages, ! double num_tuples, ! bool hasindex); extern void vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit); --- 114,122 ---- extern void vac_open_indexes(Relation relation, LOCKMODE lockmode, int *nindexes, Relation **Irel); extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode); ! extern void vac_update_relstats(Relation rel, BlockNumber num_pages, ! double num_tuples, bool hasindex, ! TransactionId minxid, TransactionId vacuumxid); extern void vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit); diff -rc -X diff-ignore 11fixclass/src/include/libpq/hba.h 12ntrelminxid/src/include/libpq/hba.h *** 11fixclass/src/include/libpq/hba.h 2006-03-08 16:23:47.000000000 -0300 --- 12ntrelminxid/src/include/libpq/hba.h 2006-06-10 19:38:04.000000000 -0400 *************** *** 40,46 **** extern int hba_getauthmethod(hbaPort *port); extern int authident(hbaPort *port); extern bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, ! Oid *dbtablespace, TransactionId *dbfrozenxid, TransactionId *dbvacuumxid); #endif /* HBA_H */ --- 40,46 ---- extern int hba_getauthmethod(hbaPort *port); extern int authident(hbaPort *port); extern bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, ! Oid *dbtablespace, TransactionId *dbminxid, TransactionId *dbvacuumxid); #endif /* HBA_H */