aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlvaro Herrera <alvherre@alvh.no-ip.org>2009-11-10 18:01:27 +0000
committerAlvaro Herrera <alvherre@alvh.no-ip.org>2009-11-10 18:01:27 +0000
commit5804f4b39bc11fdeed4fe652015170fd75ac8aba (patch)
tree88d16795f9b06269d5091a2c0a18aa9922cd4b5b
parent4276fe3a3ba058daceec5a1b64738ead2b70f253 (diff)
downloadpostgresql-5804f4b39bc11fdeed4fe652015170fd75ac8aba.tar.gz
postgresql-5804f4b39bc11fdeed4fe652015170fd75ac8aba.zip
Fix longstanding problems in VACUUM caused by untimely interruptions
In VACUUM FULL, an interrupt after the initial transaction has been recorded as committed can cause postmaster to restart with the following error message: PANIC: cannot abort transaction NNNN, it was already committed This problem has been reported many times. In lazy VACUUM, an interrupt after the table has been truncated by lazy_truncate_heap causes other backends' relcache to still point to the removed pages; this can cause future INSERT and UPDATE queries to error out with the following error message: could not read block XX of relation 1663/NNN/MMMM: read only 0 of 8192 bytes The window to this race condition is extremely narrow, but it has been seen in the wild involving a cancelled autovacuum process. The solution for both problems is to inhibit interrupts in both operations until after the respective transactions have been committed. It's not a complete solution, because the transaction could theoretically be aborted by some other error, but at least fixes the most common causes of both problems.
-rw-r--r--src/backend/commands/vacuum.c41
-rw-r--r--src/backend/commands/vacuumlazy.c20
-rw-r--r--src/include/commands/vacuum.h4
3 files changed, 52 insertions, 13 deletions
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 478cf8253ff..dc481f30f5f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.299.4.4 2008/02/11 19:14:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.299.4.5 2009/11/10 18:01:26 alvherre Exp $
*
*-------------------------------------------------------------------------
*/
@@ -176,10 +176,10 @@ static void vac_update_dbstats(Oid dbid,
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 bool 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,
+static bool repair_frag(VRelStats *vacrelstats, Relation onerel,
VacPageList vacuum_pages, VacPageList fraged_pages,
int nindexes, Relation *Irel);
static void move_chain_tuple(Relation rel,
@@ -885,6 +885,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
bool result;
AclId save_userid;
bool save_secdefcxt;
+ bool heldoff;
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
@@ -1011,9 +1012,9 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
* Do the actual work --- either FULL or "lazy" vacuum
*/
if (vacstmt->full)
- full_vacuum_rel(onerel, vacstmt);
+ heldoff = full_vacuum_rel(onerel, vacstmt);
else
- lazy_vacuum_rel(onerel, vacstmt);
+ heldoff = lazy_vacuum_rel(onerel, vacstmt);
result = true; /* did the vacuum */
@@ -1029,6 +1030,10 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
StrategyHintVacuum(false);
CommitTransactionCommand();
+ /* now we can allow interrupts again, if disabled */
+ if (heldoff)
+ RESUME_INTERRUPTS();
+
/*
* If the relation has a secondary toast rel, vacuum that too while we
* still hold the session lock on the master table. Note however that
@@ -1067,8 +1072,11 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
*
* At entry, we have already established a transaction and opened
* and locked the relation.
+ *
+ * The return value indicates whether this function has held off
+ * interrupts -- caller must RESUME_INTERRUPTS() after commit if true.
*/
-static void
+static bool
full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
{
VacPageListData vacuum_pages; /* List of pages to vacuum and/or
@@ -1079,6 +1087,7 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
int nindexes,
i;
VRelStats *vacrelstats;
+ bool heldoff = false;
vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
&OldestXmin, &FreezeLimit);
@@ -1120,8 +1129,8 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
if (fraged_pages.num_pages > 0)
{
/* Try to shrink heap */
- repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages,
- nindexes, Irel);
+ heldoff = repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages,
+ nindexes, Irel);
vac_close_indexes(nindexes, Irel, NoLock);
}
else
@@ -1150,6 +1159,8 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
/* update statistics in pg_class */
vac_update_relstats(RelationGetRelid(onerel), vacrelstats->rel_pages,
vacrelstats->rel_tuples, vacrelstats->hasindex);
+
+ return heldoff;
}
@@ -1563,8 +1574,11 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
* for them after committing (in hack-manner - without losing locks
* and freeing memory!) current transaction. It truncates relation
* if some end-blocks are gone away.
+ *
+ * The return value indicates whether this function has held off
+ * interrupts -- caller must RESUME_INTERRUPTS() after commit if true.
*/
-static void
+static bool
repair_frag(VRelStats *vacrelstats, Relation onerel,
VacPageList vacuum_pages, VacPageList fraged_pages,
int nindexes, Relation *Irel)
@@ -1588,6 +1602,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
vacuumed_pages;
int keep_tuples = 0;
VacRUsage ru0;
+ bool heldoff = false;
vac_init_rusage(&ru0);
@@ -2292,7 +2307,13 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
* a lot of extra code to close and re-open the relation, indexes,
* etc. For now, a quick hack: record status of current
* transaction as committed, and continue.
+ *
+ * We prevent cancel interrupts after this point to mitigate the
+ * problem that you can't abort the transaction now; caller is
+ * responsible for re-enabling them after committing the transaction.
*/
+ HOLD_INTERRUPTS();
+ heldoff = true;
RecordTransactionCommit();
}
@@ -2491,6 +2512,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
pfree(vacrelstats->vtlinks);
ExecContext_Finish(&ec);
+
+ return heldoff;
}
/*
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a7a9065f103..a7ce4b03ecd 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -33,7 +33,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.50.4.7 2009/01/06 14:56:03 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.50.4.8 2009/11/10 18:01:27 alvherre Exp $
*
*-------------------------------------------------------------------------
*/
@@ -135,8 +135,11 @@ static int vac_cmp_page_spaces(const void *left, const void *right);
*
* At entry, we have already established a transaction and opened
* and locked the relation.
+ *
+ * The return value indicates whether this function has held off
+ * interrupts -- caller must RESUME_INTERRUPTS() after commit if true.
*/
-void
+bool
lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
{
LVRelStats *vacrelstats;
@@ -144,6 +147,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
int nindexes;
bool hasindex;
BlockNumber possibly_freeable;
+ bool heldoff = false;
if (vacstmt->verbose)
elevel = INFO;
@@ -174,12 +178,22 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
*
* Don't even think about it unless we have a shot at releasing a goodly
* number of pages. Otherwise, the time taken isn't worth it.
+ *
+ * Note that after we've truncated the heap, it's too late to abort the
+ * transaction; doing so would lose the sinval messages needed to tell the
+ * other backends about the table being shrunk. We prevent interrupts in
+ * that case; caller is responsible for re-enabling them after
+ * committing the transaction.
*/
possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
if (possibly_freeable > 0 &&
(possibly_freeable >= REL_TRUNCATE_MINIMUM ||
possibly_freeable >= vacrelstats->rel_pages / REL_TRUNCATE_FRACTION))
+ {
+ HOLD_INTERRUPTS();
+ heldoff = true;
lazy_truncate_heap(onerel, vacrelstats);
+ }
/* Update shared free space map with final free space info */
lazy_update_fsm(onerel, vacrelstats);
@@ -189,6 +203,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
vacrelstats->rel_pages,
vacrelstats->rel_tuples,
hasindex);
+
+ return heldoff;
}
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 0434b6f5ec4..48e22523485 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.59 2004/12/31 22:03:28 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.59.4.1 2009/11/10 18:01:27 alvherre Exp $
*
*-------------------------------------------------------------------------
*/
@@ -142,7 +142,7 @@ extern const char *vac_show_rusage(VacRUsage *ru0);
extern void vacuum_delay_point(void);
/* in commands/vacuumlazy.c */
-extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
+extern bool lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
/* in commands/analyze.c */
extern void analyze_rel(Oid relid, VacuumStmt *vacstmt);