Index: doc/src/sgml/catalogs.sgml =================================================================== RCS file: /home/alvherre/cvs/pgsql/doc/src/sgml/catalogs.sgml,v retrieving revision 2.124 diff -c -r2.124 catalogs.sgml *** doc/src/sgml/catalogs.sgml 3 Jun 2006 02:53:04 -0000 2.124 --- doc/src/sgml/catalogs.sgml 28 Jun 2006 18:02:59 -0000 *************** *** 1633,1638 **** --- 1633,1662 ---- + 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[] *************** *** 2006,2026 **** 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. --- 2030,2056 ---- 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. Index: src/backend/access/transam/varsup.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/access/transam/varsup.c,v retrieving revision 1.70 diff -c -r1.70 varsup.c *** src/backend/access/transam/varsup.c 5 Mar 2006 15:58:22 -0000 1.70 --- src/backend/access/transam/varsup.c 28 Jun 2006 18:02:59 -0000 *************** *** 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; Index: src/backend/catalog/heap.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/catalog/heap.c,v retrieving revision 1.299 diff -c -r1.299 heap.c *** src/backend/catalog/heap.c 10 May 2006 23:18:39 -0000 1.299 --- src/backend/catalog/heap.c 28 Jun 2006 22:04:56 -0000 *************** *** 568,573 **** --- 568,602 ---- */ new_rel_reltup = new_rel_desc->rd_rel; + /* Initialize relminxid and relvacuumxid */ + if (relkind == RELKIND_RELATION || + relkind == RELKIND_TOASTVALUE) + { + /* + * Only real tables have Xids stored in them; initialize our known + * value to the minimum Xid that could put tuples in the new table. + */ + if (!IsBootstrapProcessingMode()) + { + new_rel_reltup->relminxid = RecentXmin; + new_rel_reltup->relvacuumxid = RecentXmin; + } + else + { + new_rel_reltup->relminxid = FirstNormalTransactionId; + new_rel_reltup->relvacuumxid = FirstNormalTransactionId; + } + } + else + { + /* + * Other relations will not have Xids in them, so set the initial value + * to InvalidTransactionId. + */ + new_rel_reltup->relminxid = InvalidTransactionId; + new_rel_reltup->relvacuumxid = InvalidTransactionId; + } + switch (relkind) { case RELKIND_RELATION: Index: src/backend/commands/analyze.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/commands/analyze.c,v retrieving revision 1.93 diff -c -r1.93 analyze.c *** src/backend/commands/analyze.c 23 Mar 2006 00:19:28 -0000 1.93 --- src/backend/commands/analyze.c 28 Jun 2006 18:02:59 -0000 *************** *** 424,431 **** { vac_update_relstats(RelationGetRelid(onerel), RelationGetNumberOfBlocks(onerel), ! totalrows, ! hasindex); for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; --- 424,432 ---- { vac_update_relstats(RelationGetRelid(onerel), RelationGetNumberOfBlocks(onerel), ! totalrows, hasindex, ! InvalidTransactionId, InvalidTransactionId); ! for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; *************** *** 434,441 **** totalindexrows = ceil(thisdata->tupleFract * totalrows); vac_update_relstats(RelationGetRelid(Irel[ind]), RelationGetNumberOfBlocks(Irel[ind]), ! totalindexrows, ! false); } /* report results to the stats collector, too */ --- 435,442 ---- totalindexrows = ceil(thisdata->tupleFract * totalrows); vac_update_relstats(RelationGetRelid(Irel[ind]), RelationGetNumberOfBlocks(Irel[ind]), ! totalindexrows, false, ! InvalidTransactionId, InvalidTransactionId); } /* report results to the stats collector, too */ Index: src/backend/commands/dbcommands.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/commands/dbcommands.c,v retrieving revision 1.181 diff -c -r1.181 dbcommands.c *** src/backend/commands/dbcommands.c 4 May 2006 16:07:29 -0000 1.181 --- src/backend/commands/dbcommands.c 28 Jun 2006 18:24:18 -0000 *************** *** 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); --- 55,61 ---- 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; --- 76,82 ---- 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", --- 228,234 ---- 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,342 **** } /* - * 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 * error message than "unique index violation". There's a race condition * but we're willing to accept the less friendly message in that case. --- 327,332 ---- *************** *** 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); /* --- 357,363 ---- 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); /* *************** *** 1066,1072 **** Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, Oid *dbLastSysOidP, ! TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP, Oid *dbTablespace) { bool result = false; --- 1056,1062 ---- 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; --- 1145,1153 ---- /* 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; Index: src/backend/commands/vacuum.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/commands/vacuum.c,v retrieving revision 1.330 diff -c -r1.330 vacuum.c *** src/backend/commands/vacuum.c 10 May 2006 23:18:39 -0000 1.330 --- src/backend/commands/vacuum.c 28 Jun 2006 23:28:37 -0000 *************** *** 25,30 **** --- 25,31 ---- #include "access/clog.h" #include "access/genam.h" #include "access/heapam.h" + #include "access/multixact.h" #include "access/subtrans.h" #include "access/xlog.h" #include "catalog/catalog.h" *************** *** 127,132 **** --- 128,134 ---- 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; *************** *** 194,218 **** 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); ! 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, char expected_relkind); 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, --- 196,217 ---- static int elevel = -1; /* non-export function prototypes */ static List *get_rel_oids(List *relids, const RangeVar *vacrel, const char *stmttype); ! static void vac_update_dbminxid(Oid dbid, ! TransactionId *minxid, ! TransactionId *vacuumxid); ! static void vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid); ! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind); 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, *************** *** 268,275 **** 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, --- 267,272 ---- *************** *** 352,383 **** */ relations = get_rel_oids(relids, vacstmt->relation, stmttype); - 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 ---- *************** *** 445,454 **** Oid relid = lfirst_oid(cur); if (vacstmt->vacuum) ! { ! if (!vacuum_rel(relid, vacstmt, RELKIND_RELATION)) ! all_rels = false; /* forget about updating dbstats */ ! } if (vacstmt->analyze) { MemoryContext old_context = NULL; --- 416,423 ---- Oid relid = lfirst_oid(cur); if (vacstmt->vacuum) ! vacuum_rel(relid, vacstmt, RELKIND_RELATION); ! if (vacstmt->analyze) { MemoryContext old_context = NULL; *************** *** 524,529 **** --- 493,501 ---- if (vacstmt->vacuum) { + TransactionId minxid, + vacuumxid; + /* * If it was a database-wide VACUUM, print FSM usage statistics (we * don't make you be superuser to see these). *************** *** 531,547 **** 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); ! } } /* --- 503,513 ---- if (all_rels) PrintFreeSpaceMapStatistics(elevel); ! /* Update pg_database.datminxid and datvacuumxid */ ! vac_update_dbminxid(MyDatabaseId, &minxid, &vacuumxid); ! ! /* Try to truncate pg_clog. */ ! vac_truncate_clog(minxid, vacuumxid); } /* *************** *** 687,693 **** */ void vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, ! bool hasindex) { Relation rd; HeapTuple ctup; --- 653,660 ---- */ void vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, ! bool hasindex, TransactionId minxid, ! TransactionId vacuumxid) { Relation rd; HeapTuple ctup; *************** *** 735,740 **** --- 702,717 ---- dirty = true; } } + if (TransactionIdIsValid(minxid) && pgcform->relminxid != minxid) + { + pgcform->relminxid = minxid; + dirty = true; + } + if (TransactionIdIsValid(vacuumxid) && pgcform->relvacuumxid != vacuumxid) + { + pgcform->relvacuumxid = vacuumxid; + dirty = true; + } /* * If anything changed, write out the tuple *************** *** 747,756 **** /* ! * 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 --- 724,736 ---- /* ! * 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_class. datvacuumxid is updated to the minimum of all relvacuumxid ! * found in pg_class. The values are also returned in minxid and ! * vacuumxid, respectively. * * We violate transaction semantics here by overwriting the database's * existing pg_database tuple with the new values. This is reasonably *************** *** 758,775 **** * 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 */ --- 738,804 ---- * 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, TransactionId *minxid, TransactionId *vacuumxid) { HeapTuple tuple; Form_pg_database dbform; + Relation relation; + SysScanDesc scan; + HeapTuple classTup; + TransactionId newMinXid = InvalidTransactionId; + TransactionId newVacXid = InvalidTransactionId; + bool dirty = false; + + /* + * We must seqscan pg_class to find the minimum Xid, because there + * is no index that can help us here. + */ + relation = heap_open(RelationRelationId, AccessShareLock); + + scan = systable_beginscan(relation, InvalidOid, false, + SnapshotNow, 0, NULL); + + while ((classTup = systable_getnext(scan)) != NULL) + { + Form_pg_class classForm; + + classForm = (Form_pg_class) GETSTRUCT(classTup); + + /* + * Only consider heap and TOAST tables (anything else should have + * InvalidTransactionId in both fields anyway.) + */ + if (classForm->relkind != RELKIND_RELATION && + classForm->relkind != RELKIND_TOASTVALUE) + continue; + + Assert(TransactionIdIsNormal(classForm->relminxid)); + Assert(TransactionIdIsNormal(classForm->relvacuumxid)); + + /* + * Compute the minimum relminxid in all the tables in the database. + */ + if ((!TransactionIdIsValid(newMinXid) || + TransactionIdPrecedes(classForm->relminxid, newMinXid))) + newMinXid = classForm->relminxid; + + /* ditto, for relvacuumxid */ + if ((!TransactionIdIsValid(newVacXid) || + TransactionIdPrecedes(classForm->relvacuumxid, newVacXid))) + newVacXid = classForm->relvacuumxid; + } + /* we're done with pg_class */ + systable_endscan(scan); + heap_close(relation, AccessShareLock); + + Assert(TransactionIdIsNormal(newMinXid)); + Assert(TransactionIdIsNormal(newVacXid)); + + /* Now fetch the pg_database tuple we need to update. */ relation = heap_open(DatabaseRelationId, RowExclusiveLock); /* Fetch a copy of the tuple to scribble on */ *************** *** 780,795 **** 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(); } --- 809,834 ---- elog(ERROR, "could not find tuple for database %u", dbid); dbform = (Form_pg_database) GETSTRUCT(tuple); ! if (TransactionIdPrecedes(dbform->datminxid, newMinXid)) ! { ! dbform->datminxid = newMinXid; ! dirty = true; ! } ! if (TransactionIdPrecedes(dbform->datvacuumxid, newVacXid)) ! { ! dbform->datvacuumxid = newVacXid; ! dirty = true; ! } ! if (dirty) ! heap_inplace_update(relation, tuple); + heap_freetuple(tuple); heap_close(relation, RowExclusiveLock); ! /* set return values */ ! *minxid = newMinXid; ! *vacuumxid = newVacXid; } *************** *** 813,830 **** * 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)); /* --- 852,873 ---- * applied after a database-wide VACUUM operation. */ static void ! vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid) { 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. */ ! minXID = myminxid; ! vacuumXID = myvacxid; namestrcpy(&oldest_datname, get_database_name(MyDatabaseId)); /* *************** *** 839,865 **** { 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)) ! vacuumAlreadyWrapped = true; ! 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); ! } } } --- 882,901 ---- { Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); ! Assert(TransactionIdIsNormal(dbform->datvacuumxid)); ! Assert(TransactionIdIsNormal(dbform->datminxid)); ! if (TransactionIdPrecedes(myXID, dbform->datvacuumxid)) ! vacuumAlreadyWrapped = true; ! else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID)) ! vacuumXID = dbform->datvacuumxid; ! ! if (TransactionIdPrecedes(myXID, dbform->datminxid)) ! minAlreadyWrapped = true; ! else if (TransactionIdPrecedes(dbform->datminxid, minXID)) { ! minXID = dbform->datminxid; ! namecpy(&oldest_datname, &dbform->datname); } } *************** *** 886,892 **** * 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"), --- 922,928 ---- * 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"), *************** *** 895,904 **** } /* 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", --- 931,940 ---- } /* 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", *************** *** 920,930 **** /* * 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 --- 956,961 ---- *************** *** 933,952 **** * * At entry and exit, we are not inside a transaction. */ ! static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) { LOCKMODE lmode; Relation onerel; LockRelId onerelid; Oid toast_relid; - bool result; /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); /* functions in indexes may want a snapshot set */ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); /* * Tell the cache replacement strategy that vacuum is causing all * following IO --- 964,983 ---- * * At entry and exit, we are not inside a transaction. */ ! static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) { LOCKMODE lmode; Relation onerel; LockRelId onerelid; Oid toast_relid; /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); /* functions in indexes may want a snapshot set */ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); + /* * Tell the cache replacement strategy that vacuum is causing all * following IO *************** *** 969,975 **** { StrategyHintVacuum(false); CommitTransactionCommand(); ! return true; /* okay 'cause no data there */ } /* --- 1000,1006 ---- { StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* *************** *** 1000,1006 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return false; } /* --- 1031,1037 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* *************** *** 1015,1021 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return false; } /* --- 1046,1052 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; } /* *************** *** 1030,1036 **** relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return true; /* assume no long-lived data in temp tables */ } /* --- 1061,1067 ---- relation_close(onerel, lmode); StrategyHintVacuum(false); CommitTransactionCommand(); ! return; /* assume no long-lived data in temp tables */ } /* *************** *** 1059,1066 **** 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); --- 1090,1095 ---- *************** *** 1078,1094 **** * totally unimportant for toast relations. */ if (toast_relid != InvalidOid) ! { ! if (!vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE)) ! result = false; /* failed to vacuum the TOAST table? */ ! } /* * Now release the session-level lock on the master table. */ UnlockRelationForSession(&onerelid, lmode); ! return result; } --- 1107,1120 ---- * totally unimportant for toast relations. */ if (toast_relid != InvalidOid) ! vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE); /* * Now release the session-level lock on the master table. */ UnlockRelationForSession(&onerelid, lmode); ! return; } *************** *** 1120,1125 **** --- 1146,1153 ---- int nindexes, i; VRelStats *vacrelstats; + TransactionId FreezeLimit, + OldestXmin; vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit); *************** *** 1132,1140 **** 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); --- 1160,1180 ---- 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); *************** *** 1162,1168 **** { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, ! nindexes, Irel); vac_close_indexes(nindexes, Irel, NoLock); } else --- 1202,1208 ---- { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, ! nindexes, Irel, OldestXmin); vac_close_indexes(nindexes, Irel, NoLock); } else *************** *** 1180,1186 **** /* update statistics in pg_class */ vac_update_relstats(RelationGetRelid(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, --- 1220,1227 ---- /* update statistics in pg_class */ vac_update_relstats(RelationGetRelid(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, *************** *** 1196,1209 **** * 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, --- 1237,1253 ---- * 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. + * + * It also updates the minimum Xid found anywhere on the table in + * vacrelstats->minxid, for later storing it in pg_class.relminxid. */ 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, *************** *** 1317,1322 **** --- 1361,1367 ---- { ItemId itemid = PageGetItemId(page, offnum); bool tupgone = false; + HeapTupleData tuple; /* * Collect un-used items too - it's possible to have indexes *************** *** 1452,1463 **** --- 1497,1519 ---- } else { + TransactionId min; + num_tuples += 1; notup = false; if (tuple.t_len < min_tlen) min_tlen = tuple.t_len; if (tuple.t_len > max_tlen) max_tlen = tuple.t_len; + + /* + * If the tuple is alive, we consider it for the "minxid" + * calculations. + */ + min = vactuple_get_minxid(&tuple); + if (TransactionIdIsValid(min) && + TransactionIdPrecedes(min, vacrelstats->minxid)) + vacrelstats->minxid = min; } } /* scan along page */ *************** *** 1583,1588 **** --- 1639,1701 ---- pg_rusage_show(&ru0)))); } + /* + * vactuple_get_minxid + * + * Get the minimum relevant Xid for a tuple, not considering FrozenXid. + * Return InvalidXid if none (i.e., xmin=FrozenXid, xmax=InvalidXid). + * This is for the purpose of calculating pg_class.relminxid for a table + * we're vacuuming. + */ + TransactionId + vactuple_get_minxid(HeapTuple tuple) + { + TransactionId min = InvalidTransactionId; + + /* + * Initialize calculations with Xmin. NB -- may be FrozenXid and + * we don't want that one. + */ + if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple->t_data))) + min = HeapTupleHeaderGetXmin(tuple->t_data); + + /* + * If Xmax is not marked INVALID, we assume it's valid without making + * further checks 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)) + { + TransactionId xmax = HeapTupleHeaderGetXmax(tuple->t_data); + + /* If xmax is a plain Xid, consider it by itself */ + if (!(tuple->t_data->t_infomask | HEAP_XMAX_IS_MULTI)) + { + if (!TransactionIdIsValid(min) || + (TransactionIdIsNormal(xmax) && + TransactionIdPrecedes(xmax, min))) + min = xmax; + } + else + { + /* If it's a MultiXactId, consider each of its members */ + TransactionId *members; + int nmembers, + membno; + + nmembers = GetMultiXactIdMembers(xmax, &members); + + for (membno = 0; membno < nmembers; membno++) + { + if (!TransactionIdIsValid(min) || + TransactionIdPrecedes(members[membno], min)) + min = members[membno]; + } + } + } + + return min; + } /* * repair_frag() -- try to repair relation's fragmentation *************** *** 1597,1603 **** 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; --- 1710,1716 ---- 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; *************** *** 2940,2946 **** /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, ! false); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", --- 3053,3059 ---- /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, ! false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", *************** *** 3009,3015 **** /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, ! false); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", --- 3122,3128 ---- /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, ! false, InvalidTransactionId, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", Index: src/backend/commands/vacuumlazy.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/commands/vacuumlazy.c,v retrieving revision 1.70 diff -c -r1.70 vacuumlazy.c *** src/backend/commands/vacuumlazy.c 2 May 2006 22:25:10 -0000 1.70 --- src/backend/commands/vacuumlazy.c 28 Jun 2006 21:43:37 -0000 *************** *** 42,47 **** --- 42,48 ---- #include "access/genam.h" #include "access/heapam.h" #include "access/xlog.h" + #include "catalog/catalog.h" #include "commands/vacuum.h" #include "miscadmin.h" #include "pgstat.h" *************** *** 72,77 **** --- 73,79 ---- 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, --- 90,100 ---- 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); --- 104,113 ---- 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. --- 123,130 ---- * 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,140 **** --- 137,144 ---- int nindexes; bool hasindex; BlockNumber possibly_freeable; + TransactionId OldestXmin, + FreezeLimit; if (vacstmt->verbose) elevel = INFO; *************** *** 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); --- 154,176 ---- /* 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. + * + * 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. + */ + 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,184 **** 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); /* Update statistics in pg_class */ ! vac_update_relstats(RelationGetRelid(onerel), ! vacrelstats->rel_pages, ! vacrelstats->rel_tuples, ! hasindex); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, --- 184,198 ---- 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); /* Update statistics in pg_class */ ! vac_update_relstats(RelationGetRelid(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; --- 207,220 ---- * 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 updates the minimum Xid found anywhere on the table in + * vacrelstats->minxid, for later storing it in pg_class.relminxid. */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, ! Relation *Irel, int nindexes, TransactionId FreezeLimit, ! TransactionId OldestXmin) { BlockNumber nblocks, blkno; *************** *** 406,413 **** --- 424,442 ---- } else { + TransactionId min; + num_tuples += 1; hastup = true; + + /* + * If the tuple is alive, we consider it for the "minxid" + * calculations. + */ + min = vactuple_get_minxid(&tuple); + if (TransactionIdIsValid(min) && + TransactionIdPrecedes(min, vacrelstats->minxid)) + vacrelstats->minxid = min; } } /* scan along page */ *************** *** 668,676 **** /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), ! stats->num_pages, ! stats->num_index_tuples, ! false); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", --- 697,704 ---- /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(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; --- 719,726 ---- * 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) { --- 761,767 ---- * 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; --- 816,823 ---- * Returns number of nondeletable pages (last nonempty page + 1). */ static BlockNumber ! count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats, ! TransactionId OldestXmin) { BlockNumber blkno; HeapTupleData tuple; Index: src/backend/libpq/hba.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/libpq/hba.c,v retrieving revision 1.151 diff -c -r1.151 hba.c *** src/backend/libpq/hba.c 6 Mar 2006 17:41:43 -0000 1.151 --- src/backend/libpq/hba.c 28 Jun 2006 18:02:59 -0000 *************** *** 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"); Index: src/backend/postmaster/autovacuum.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/postmaster/autovacuum.c,v retrieving revision 1.19 diff -c -r1.19 autovacuum.c *** src/backend/postmaster/autovacuum.c 19 May 2006 15:15:37 -0000 1.19 --- src/backend/postmaster/autovacuum.c 28 Jun 2006 18:02:59 -0000 *************** *** 79,85 **** { Oid oid; char *name; ! TransactionId frozenxid; TransactionId vacuumxid; PgStat_StatDBEntry *entry; int32 age; --- 79,85 ---- { Oid oid; char *name; ! TransactionId minxid; TransactionId vacuumxid; PgStat_StatDBEntry *entry; int32 age; *************** *** 349,355 **** { autovac_dbase *tmp = lfirst(cell); bool this_whole_db; ! int32 freeze_age, vacuum_age; /* --- 349,355 ---- { autovac_dbase *tmp = lfirst(cell); bool this_whole_db; ! int32 true_age, vacuum_age; /* *************** *** 362,370 **** * 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)); --- 362,370 ---- * 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)); *************** *** 455,461 **** FILE *db_file; Oid db_id; Oid db_tablespace; ! TransactionId db_frozenxid; TransactionId db_vacuumxid; filename = database_getflatfilename(); --- 455,461 ---- FILE *db_file; Oid db_id; Oid db_tablespace; ! TransactionId db_minxid; TransactionId db_vacuumxid; filename = database_getflatfilename(); *************** *** 466,472 **** 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; --- 466,472 ---- 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; *************** *** 475,481 **** db->oid = db_id; db->name = pstrdup(thisname); ! db->frozenxid = db_frozenxid; db->vacuumxid = db_vacuumxid; /* these get set later: */ db->entry = NULL; --- 475,481 ---- db->oid = db_id; db->name = pstrdup(thisname); ! db->minxid = db_minxid; db->vacuumxid = db_vacuumxid; /* these get set later: */ db->entry = NULL; Index: src/backend/utils/init/flatfiles.c =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/backend/utils/init/flatfiles.c,v retrieving revision 1.18 diff -c -r1.18 flatfiles.c *** src/backend/utils/init/flatfiles.c 4 May 2006 16:07:29 -0000 1.18 --- src/backend/utils/init/flatfiles.c 28 Jun 2006 18:02:59 -0000 *************** *** 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); } Index: src/include/access/transam.h =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/include/access/transam.h,v retrieving revision 1.57 diff -c -r1.57 transam.h *** src/include/access/transam.h 5 Mar 2006 15:58:53 -0000 1.57 --- src/include/access/transam.h 28 Jun 2006 21:49:51 -0000 *************** *** 23,28 **** --- 23,29 ---- * always be considered valid. * * FirstNormalTransactionId is the first "normal" transaction id. + * Note: if you need to change it, you must change it in pg_class.h as well. * ---------------- */ #define InvalidTransactionId ((TransactionId) 0) *************** *** 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); --- 124,130 ---- /* 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); Index: src/include/catalog/pg_attribute.h =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/include/catalog/pg_attribute.h,v retrieving revision 1.120 diff -c -r1.120 pg_attribute.h *** src/include/catalog/pg_attribute.h 5 Mar 2006 15:58:54 -0000 1.120 --- src/include/catalog/pg_attribute.h 28 Jun 2006 18:02:59 -0000 *************** *** 404,410 **** { 1259, {"relhaspkey"}, 16, -1, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ { 1259, {"relhasrules"}, 16, -1, 1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ { 1259, {"relhassubclass"},16, -1, 1, 24, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ ! { 1259, {"relacl"}, 1034, -1, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 } DATA(insert ( 1259 relname 19 -1 NAMEDATALEN 1 0 -1 -1 f p i t f f t 0)); DATA(insert ( 1259 relnamespace 26 -1 4 2 0 -1 -1 t p i t f f t 0)); --- 404,412 ---- { 1259, {"relhaspkey"}, 16, -1, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ { 1259, {"relhasrules"}, 16, -1, 1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ { 1259, {"relhassubclass"},16, -1, 1, 24, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ ! { 1259, {"relminxid"}, 28, -1, 4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ ! { 1259, {"relvacuumxid"}, 28, -1, 4, 26, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ ! { 1259, {"relacl"}, 1034, -1, -1, 27, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 } DATA(insert ( 1259 relname 19 -1 NAMEDATALEN 1 0 -1 -1 f p i t f f t 0)); DATA(insert ( 1259 relnamespace 26 -1 4 2 0 -1 -1 t p i t f f t 0)); *************** *** 430,436 **** DATA(insert ( 1259 relhaspkey 16 -1 1 22 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhasrules 16 -1 1 23 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhassubclass 16 -1 1 24 0 -1 -1 t p c t f f t 0)); ! DATA(insert ( 1259 relacl 1034 -1 -1 25 1 -1 -1 f x i f f f t 0)); DATA(insert ( 1259 ctid 27 0 6 -1 0 -1 -1 f p s t f f t 0)); DATA(insert ( 1259 oid 26 0 4 -2 0 -1 -1 t p i t f f t 0)); DATA(insert ( 1259 xmin 28 0 4 -3 0 -1 -1 t p i t f f t 0)); --- 432,440 ---- DATA(insert ( 1259 relhaspkey 16 -1 1 22 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhasrules 16 -1 1 23 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhassubclass 16 -1 1 24 0 -1 -1 t p c t f f t 0)); ! DATA(insert ( 1259 relminxid 28 -1 4 25 0 -1 -1 t p i t f f t 0)); ! DATA(insert ( 1259 relvacuumxid 28 -1 4 26 0 -1 -1 t p i t f f t 0)); ! DATA(insert ( 1259 relacl 1034 -1 -1 27 1 -1 -1 f x i f f f t 0)); DATA(insert ( 1259 ctid 27 0 6 -1 0 -1 -1 f p s t f f t 0)); DATA(insert ( 1259 oid 26 0 4 -2 0 -1 -1 t p i t f f t 0)); DATA(insert ( 1259 xmin 28 0 4 -3 0 -1 -1 t p i t f f t 0)); Index: src/include/catalog/pg_class.h =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/include/catalog/pg_class.h,v retrieving revision 1.92 diff -c -r1.92 pg_class.h *** src/include/catalog/pg_class.h 28 May 2006 02:27:08 -0000 1.92 --- src/include/catalog/pg_class.h 28 Jun 2006 21:49:26 -0000 *************** *** 74,79 **** --- 74,81 ---- bool relhaspkey; /* has PRIMARY KEY index */ bool relhasrules; /* has associated rules */ bool relhassubclass; /* has derived classes */ + TransactionId relminxid; /* minimum Xid present in table */ + TransactionId relvacuumxid; /* Xid used as last vacuum OldestXmin */ /* * relacl may or may not be present, see note above! *************** *** 83,89 **** /* Size of fixed part of pg_class tuples, not counting relacl or padding */ #define CLASS_TUPLE_SIZE \ ! (offsetof(FormData_pg_class,relhassubclass) + sizeof(bool)) /* ---------------- * Form_pg_class corresponds to a pointer to a tuple with --- 85,91 ---- /* Size of fixed part of pg_class tuples, not counting relacl or padding */ #define CLASS_TUPLE_SIZE \ ! (offsetof(FormData_pg_class,relvacuumxid) + sizeof(TransactionId)) /* ---------------- * Form_pg_class corresponds to a pointer to a tuple with *************** *** 103,110 **** * relacl field. This is a kluge. * ---------------- */ ! #define Natts_pg_class_fixed 24 ! #define Natts_pg_class 25 #define Anum_pg_class_relname 1 #define Anum_pg_class_relnamespace 2 #define Anum_pg_class_reltype 3 --- 105,112 ---- * relacl field. This is a kluge. * ---------------- */ ! #define Natts_pg_class_fixed 26 ! #define Natts_pg_class 27 #define Anum_pg_class_relname 1 #define Anum_pg_class_relnamespace 2 #define Anum_pg_class_reltype 3 *************** *** 129,135 **** #define Anum_pg_class_relhaspkey 22 #define Anum_pg_class_relhasrules 23 #define Anum_pg_class_relhassubclass 24 ! #define Anum_pg_class_relacl 25 /* ---------------- * initial contents of pg_class --- 131,139 ---- #define Anum_pg_class_relhaspkey 22 #define Anum_pg_class_relhasrules 23 #define Anum_pg_class_relhassubclass 24 ! #define Anum_pg_class_relminxid 25 ! #define Anum_pg_class_relvacuumxid 26 ! #define Anum_pg_class_relacl 27 /* ---------------- * initial contents of pg_class *************** *** 139,151 **** * ---------------- */ ! DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 23 0 0 0 0 0 t f f f _null_ )); DESCR(""); ! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f _null_ )); DESCR(""); ! DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 18 0 0 0 0 0 t f f f _null_ )); DESCR(""); ! DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f r 25 0 0 0 0 0 t f f f _null_ )); DESCR(""); #define RELKIND_INDEX 'i' /* secondary index */ --- 143,156 ---- * ---------------- */ ! /* Note: the "3" here stands for FirstNormalTransactionId */ ! DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 23 0 0 0 0 0 t f f f 3 3 _null_ )); DESCR(""); ! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f 3 3 _null_ )); DESCR(""); ! DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 18 0 0 0 0 0 t f f f 3 3 _null_ )); DESCR(""); ! DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f r 27 0 0 0 0 0 t f f f 3 3 _null_ )); DESCR(""); #define RELKIND_INDEX 'i' /* secondary index */ Index: src/include/catalog/pg_database.h =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/include/catalog/pg_database.h,v retrieving revision 1.40 diff -c -r1.40 pg_database.h *** src/include/catalog/pg_database.h 5 Mar 2006 15:58:54 -0000 1.40 --- src/include/catalog/pg_database.h 28 Jun 2006 21:33:43 -0000 *************** *** 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 Index: src/include/commands/vacuum.h =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/include/commands/vacuum.h,v retrieving revision 1.63 diff -c -r1.63 vacuum.h *** src/include/commands/vacuum.h 5 Mar 2006 15:58:55 -0000 1.63 --- src/include/commands/vacuum.h 28 Jun 2006 21:28:14 -0000 *************** *** 114,128 **** 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(Oid relid, ! BlockNumber num_pages, ! double num_tuples, ! bool hasindex); extern void vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit); extern bool vac_is_partial_index(Relation indrel); extern void vacuum_delay_point(void); /* in commands/vacuumlazy.c */ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); --- 114,128 ---- 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(Oid relid, 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); extern bool vac_is_partial_index(Relation indrel); extern void vacuum_delay_point(void); + extern TransactionId vactuple_get_minxid(HeapTuple tuple); /* in commands/vacuumlazy.c */ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); Index: src/include/libpq/hba.h =================================================================== RCS file: /home/alvherre/cvs/pgsql/src/include/libpq/hba.h,v retrieving revision 1.42 diff -c -r1.42 hba.h *** src/include/libpq/hba.h 6 Mar 2006 17:41:44 -0000 1.42 --- src/include/libpq/hba.h 28 Jun 2006 18:02:59 -0000 *************** *** 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 */