]> granicus.if.org Git - postgresql/commitdiff
Support rewritten-based full vacuum as VACUUM FULL. Traditional
authorItagaki Takahiro <itagaki.takahiro@gmail.com>
Wed, 6 Jan 2010 05:31:14 +0000 (05:31 +0000)
committerItagaki Takahiro <itagaki.takahiro@gmail.com>
Wed, 6 Jan 2010 05:31:14 +0000 (05:31 +0000)
VACUUM FULL was renamed to VACUUM FULL INPLACE. Also added a new
option -i, --inplace for vacuumdb to perform FULL INPLACE vacuuming.

Since the new VACUUM FULL uses CLUSTER infrastructure, we cannot
use it for system tables. VACUUM FULL for system tables always
fall back into VACUUM FULL INPLACE silently.

Itagaki Takahiro, reviewed by Jeff Davis and Simon Riggs.

doc/src/sgml/ref/vacuum.sgml
doc/src/sgml/ref/vacuumdb.sgml
src/backend/commands/cluster.c
src/backend/commands/vacuum.c
src/backend/parser/gram.y
src/bin/scripts/vacuumdb.c
src/include/commands/cluster.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/test/regress/expected/vacuum.out
src/test/regress/sql/vacuum.sql

index 3b5d241703da75a9ee232ca6172cc5b020d4a857..07559e38c42eefe787ca04211f5d150c630fcf5e 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.56 2009/11/16 21:32:06 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.57 2010/01/06 05:31:13 itagaki Exp $
 PostgreSQL documentation
 -->
 
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-VACUUM [ ( { FULL | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
+VACUUM [ ( { FULL [ INPLACE ] | FREEZE | VERBOSE | ANALYZE } [, ...] ) ] [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
 VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
 VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
 </synopsis>
@@ -86,6 +86,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
       Selects <quote>full</quote> vacuum, which can reclaim more
       space, but takes much longer and exclusively locks the table.
      </para>
+     <para>
+      For user tables, all table data and indexes are rewritten. This
+      method requires extra disk space in which to write the new data,
+      and is generally useful when a significant amount of space needs
+      to be reclaimed from within the table.
+     </para>
+     <para>
+      For system tables, all table data and indexes are modified in
+      place to reclaim space. This method may require less disk space
+      for the table data than <command>VACUUM FULL</command> on a
+      comparable user table, but the indexes will grow which may
+      counteract that benefit. Additionally, the operation is often
+      slower than <command>VACUUM FULL</command> on a comparable user
+      table.
+     </para>
+     <para>
+      If <literal>FULL INPLACE</literal> is specified, the space is
+      reclaimed in the same manner as a system table, even if it is a
+      user table. Specifying <literal>INPLACE</literal> explicitly is
+      rarely useful.
+     </para>
     </listitem>
    </varlistentry>
 
index ad6f3a8cf3d29a155c36375d7c2af46003cede24..2d891c56cf69f54bafe1cd607960097f90808af3 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/vacuumdb.sgml,v 1.46 2010/01/06 02:59:44 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/vacuumdb.sgml,v 1.47 2010/01/06 05:31:13 itagaki Exp $
 PostgreSQL documentation
 -->
 
@@ -24,6 +24,7 @@ PostgreSQL documentation
    <command>vacuumdb</command>
    <arg rep="repeat"><replaceable>connection-option</replaceable></arg>
    <group><arg>--full</arg><arg>-f</arg></group>
+   <group><arg>--inplace</arg><arg>-i</arg></group>
    <group><arg>--freeze</arg><arg>-F</arg></group>
    <group><arg>--verbose</arg><arg>-v</arg></group>
    <group><arg>--analyze</arg><arg>-z</arg></group>
@@ -37,6 +38,7 @@ PostgreSQL documentation
    <arg rep="repeat"><replaceable>connection-options</replaceable></arg>
    <group><arg>--all</arg><arg>-a</arg></group>
    <group><arg>--full</arg><arg>-f</arg></group>
+   <group><arg>--inplace</arg><arg>-i</arg></group>
    <group><arg>--freeze</arg><arg>-F</arg></group>
    <group><arg>--verbose</arg><arg>-v</arg></group>
    <group><arg>--analyze</arg><arg>-z</arg></group>
@@ -129,6 +131,16 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-i</option></term>
+      <term><option>--inplace</option></term>
+      <listitem>
+       <para>
+        Perform <quote>full inplace</quote> vacuuming.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-o</option></term>
       <term><option>--only-analyze</option></term>
index 48ba57518f41ae955ba59e8ca96720d4bed116d5..1b1053d8a87bbc18fc7d643e2cbcecdae3c68dbf 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.190 2010/01/06 03:04:00 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.191 2010/01/06 05:31:13 itagaki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,9 +61,10 @@ typedef struct
 } RelToCluster;
 
 
-static void cluster_rel(RelToCluster *rv, bool recheck, bool verbose);
-static void rebuild_relation(Relation OldHeap, Oid indexOid);
-static TransactionId copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex);
+static void rebuild_relation(Relation OldHeap, Oid indexOid,
+                                                        int freeze_min_age, int freeze_table_age);
+static TransactionId copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap,
+                                       Oid OIDOldIndex, int freeze_min_age, int freeze_table_age);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
 
 
@@ -101,7 +102,6 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
                Oid                     tableOid,
                                        indexOid = InvalidOid;
                Relation        rel;
-               RelToCluster rvtc;
 
                /* Find and lock the table */
                rel = heap_openrv(stmt->relation, AccessExclusiveLock);
@@ -169,15 +169,11 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
                                                          stmt->indexname, stmt->relation->relname)));
                }
 
-               /* All other checks are done in cluster_rel() */
-               rvtc.tableOid = tableOid;
-               rvtc.indexOid = indexOid;
-
                /* close relation, keep lock till commit */
                heap_close(rel, NoLock);
 
                /* Do the job */
-               cluster_rel(&rvtc, false, stmt->verbose);
+               cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1);
        }
        else
        {
@@ -226,7 +222,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
                        StartTransactionCommand();
                        /* functions in indexes may want a snapshot set */
                        PushActiveSnapshot(GetTransactionSnapshot());
-                       cluster_rel(rvtc, true, stmt->verbose);
+                       cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, -1, -1);
                        PopActiveSnapshot();
                        CommitTransactionCommand();
                }
@@ -252,9 +248,13 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
  * same way we do for the relation.  Since we are effectively bulk-loading
  * the new table, it's better to create the indexes afterwards than to fill
  * them incrementally while we load the table.
+ *
+ * If indexOid is InvalidOid, the table will be rewritten in physical order
+ * instead of index order.
  */
-static void
-cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose)
+void
+cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
+                       int freeze_min_age, int freeze_table_age)
 {
        Relation        OldHeap;
 
@@ -267,7 +267,7 @@ cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose)
         * case, since cluster() already did it.)  The index lock is taken inside
         * check_index_is_clusterable.
         */
-       OldHeap = try_relation_open(rvtc->tableOid, AccessExclusiveLock);
+       OldHeap = try_relation_open(tableOid, AccessExclusiveLock);
 
        /* If the table has gone away, we can skip processing it */
        if (!OldHeap)
@@ -287,7 +287,7 @@ cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose)
                Form_pg_index indexForm;
 
                /* Check that the user still owns the relation */
-               if (!pg_class_ownercheck(rvtc->tableOid, GetUserId()))
+               if (!pg_class_ownercheck(tableOid, GetUserId()))
                {
                        relation_close(OldHeap, AccessExclusiveLock);
                        return;
@@ -308,53 +308,62 @@ cluster_rel(RelToCluster *rvtc, bool recheck, bool verbose)
                        return;
                }
 
-               /*
-                * Check that the index still exists
-                */
-               if (!SearchSysCacheExists(RELOID,
-                                                                 ObjectIdGetDatum(rvtc->indexOid),
-                                                                 0, 0, 0))
+               if (OidIsValid(indexOid))
                {
-                       relation_close(OldHeap, AccessExclusiveLock);
-                       return;
-               }
+                       /*
+                        * Check that the index still exists
+                        */
+                       if (!SearchSysCacheExists(RELOID,
+                                                                         ObjectIdGetDatum(indexOid),
+                                                                         0, 0, 0))
+                       {
+                               relation_close(OldHeap, AccessExclusiveLock);
+                               return;
+                       }
 
-               /*
-                * Check that the index is still the one with indisclustered set.
-                */
-               tuple = SearchSysCache(INDEXRELID,
-                                                          ObjectIdGetDatum(rvtc->indexOid),
-                                                          0, 0, 0);
-               if (!HeapTupleIsValid(tuple))   /* probably can't happen */
-               {
-                       relation_close(OldHeap, AccessExclusiveLock);
-                       return;
-               }
-               indexForm = (Form_pg_index) GETSTRUCT(tuple);
-               if (!indexForm->indisclustered)
-               {
+                       /*
+                        * Check that the index is still the one with indisclustered set.
+                        */
+                       tuple = SearchSysCache(INDEXRELID,
+                                                                  ObjectIdGetDatum(indexOid),
+                                                                  0, 0, 0);
+                       if (!HeapTupleIsValid(tuple))   /* probably can't happen */
+                       {
+                               relation_close(OldHeap, AccessExclusiveLock);
+                               return;
+                       }
+                       indexForm = (Form_pg_index) GETSTRUCT(tuple);
+                       if (!indexForm->indisclustered)
+                       {
+                               ReleaseSysCache(tuple);
+                               relation_close(OldHeap, AccessExclusiveLock);
+                               return;
+                       }
                        ReleaseSysCache(tuple);
-                       relation_close(OldHeap, AccessExclusiveLock);
-                       return;
                }
-               ReleaseSysCache(tuple);
        }
 
-       /* Check index is valid to cluster on */
-       check_index_is_clusterable(OldHeap, rvtc->indexOid, recheck);
+       /* Check heap and index are valid to cluster on */
+       check_index_is_clusterable(OldHeap, indexOid, recheck);
 
        /* rebuild_relation does all the dirty work */
-       ereport(verbose ? INFO : DEBUG2,
-                       (errmsg("clustering \"%s.%s\"",
-                                       get_namespace_name(RelationGetNamespace(OldHeap)),
-                                       RelationGetRelationName(OldHeap))));
-       rebuild_relation(OldHeap, rvtc->indexOid);
+       if (OidIsValid(indexOid))
+               ereport(verbose ? INFO : DEBUG2,
+                               (errmsg("clustering \"%s.%s\"",
+                                               get_namespace_name(RelationGetNamespace(OldHeap)),
+                                               RelationGetRelationName(OldHeap))));
+       else
+               ereport(verbose ? INFO : DEBUG2,
+                               (errmsg("vacuuming \"%s.%s\"",
+                                               get_namespace_name(RelationGetNamespace(OldHeap)),
+                                               RelationGetRelationName(OldHeap))));
+       rebuild_relation(OldHeap, indexOid, freeze_min_age, freeze_table_age);
 
        /* NB: rebuild_relation does heap_close() on OldHeap */
 }
 
 /*
- * Verify that the specified index is a legitimate index to cluster on
+ * Verify that the specified heap and index are valid to cluster on
  *
  * Side effect: obtains exclusive lock on the index.  The caller should
  * already have exclusive lock on the table, so the index lock is likely
@@ -366,6 +375,38 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck)
 {
        Relation        OldIndex;
 
+       /*
+        * Disallow clustering system relations.  This will definitely NOT work
+        * for shared relations (we have no way to update pg_class rows in other
+        * databases), nor for nailed-in-cache relations (the relfilenode values
+        * for those are hardwired, see relcache.c).  It might work for other
+        * system relations, but I ain't gonna risk it.
+        */
+       if (IsSystemRelation(OldHeap))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("\"%s\" is a system catalog",
+                                               RelationGetRelationName(OldHeap))));
+
+       /*
+        * Don't allow cluster on temp tables of other backends ... their local
+        * buffer manager is not going to cope.
+        */
+       if (RELATION_IS_OTHER_TEMP(OldHeap))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                          errmsg("cannot cluster temporary tables of other sessions")));
+
+       /*
+        * Also check for active uses of the relation in the current transaction,
+        * including open scans and pending AFTER trigger events.
+        */
+       CheckTableNotInUse(OldHeap, "CLUSTER");
+
+       /* Skip checks for index if not specified. */
+       if (!OidIsValid(indexOid))
+               return;
+
        OldIndex = index_open(indexOid, AccessExclusiveLock);
 
        /*
@@ -448,34 +489,6 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck)
                                 errmsg("cannot cluster on invalid index \"%s\"",
                                                RelationGetRelationName(OldIndex))));
 
-       /*
-        * Disallow clustering system relations.  This will definitely NOT work
-        * for shared relations (we have no way to update pg_class rows in other
-        * databases), nor for nailed-in-cache relations (the relfilenode values
-        * for those are hardwired, see relcache.c).  It might work for other
-        * system relations, but I ain't gonna risk it.
-        */
-       if (IsSystemRelation(OldHeap))
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("\"%s\" is a system catalog",
-                                               RelationGetRelationName(OldHeap))));
-
-       /*
-        * Don't allow cluster on temp tables of other backends ... their local
-        * buffer manager is not going to cope.
-        */
-       if (RELATION_IS_OTHER_TEMP(OldHeap))
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                          errmsg("cannot cluster temporary tables of other sessions")));
-
-       /*
-        * Also check for active uses of the relation in the current transaction,
-        * including open scans and pending AFTER trigger events.
-        */
-       CheckTableNotInUse(OldHeap, "CLUSTER");
-
        /* Drop relcache refcnt on OldIndex, but keep lock */
        index_close(OldIndex, NoLock);
 }
@@ -557,15 +570,16 @@ mark_index_clustered(Relation rel, Oid indexOid)
 }
 
 /*
- * rebuild_relation: rebuild an existing relation in index order
+ * rebuild_relation: rebuild an existing relation in index or physical order
  *
  * OldHeap: table to rebuild --- must be opened and exclusive-locked!
- * indexOid: index to cluster by
+ * indexOid: index to cluster by, or InvalidOid to rewrite in physical order.
  *
  * NB: this routine closes OldHeap at the right time; caller should not.
  */
 static void
-rebuild_relation(Relation OldHeap, Oid indexOid)
+rebuild_relation(Relation OldHeap, Oid indexOid,
+                                int freeze_min_age, int freeze_table_age)
 {
        Oid                     tableOid = RelationGetRelid(OldHeap);
        Oid                     tableSpace = OldHeap->rd_rel->reltablespace;
@@ -576,7 +590,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
        Relation        newrel;
 
        /* Mark the correct index as clustered */
-       mark_index_clustered(OldHeap, indexOid);
+       if (OidIsValid(indexOid))
+               mark_index_clustered(OldHeap, indexOid);
 
        /* Close relcache entry, but keep lock until transaction commit */
        heap_close(OldHeap, NoLock);
@@ -599,7 +614,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
        /*
         * Copy the heap data into the new table in the desired order.
         */
-       frozenXid = copy_heap_data(OIDNewHeap, tableOid, indexOid);
+       frozenXid = copy_heap_data(OIDNewHeap, tableOid, indexOid,
+                                                          freeze_min_age, freeze_table_age);
 
        /* To make the new heap's data visible (probably not needed?). */
        CommandCounterIncrement();
@@ -758,7 +774,8 @@ make_new_heap(Oid OIDOldHeap, const char *NewName, Oid NewTableSpace)
  * freeze cutoff point for the tuples.
  */
 static TransactionId
-copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
+copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
+                          int freeze_min_age, int freeze_table_age)
 {
        Relation        NewHeap,
                                OldHeap,
@@ -768,8 +785,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
        int                     natts;
        Datum      *values;
        bool       *isnull;
-       IndexScanDesc scan;
-       HeapTuple       tuple;
+       IndexScanDesc indexScan;
+       HeapScanDesc heapScan;
        bool            use_wal;
        TransactionId OldestXmin;
        TransactionId FreezeXid;
@@ -780,7 +797,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
         */
        NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock);
        OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock);
-       OldIndex = index_open(OIDOldIndex, AccessExclusiveLock);
+       if (OidIsValid(OIDOldIndex))
+               OldIndex = index_open(OIDOldIndex, AccessExclusiveLock);
+       else
+               OldIndex = NULL;
 
        /*
         * Their tuple descriptors should be exactly alike, but here we only need
@@ -809,8 +829,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
         * freeze_min_age to avoid having CLUSTER freeze tuples earlier than a
         * plain VACUUM would.
         */
-       vacuum_set_xid_limits(-1, -1, OldHeap->rd_rel->relisshared,
-                                                 &OldestXmin, &FreezeXid, NULL);
+       vacuum_set_xid_limits(freeze_min_age, freeze_table_age,
+                               OldHeap->rd_rel->relisshared, &OldestXmin, &FreezeXid, NULL);
 
        /*
         * FreezeXid will become the table's new relfrozenxid, and that mustn't go
@@ -828,25 +848,46 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
         * copied, we scan with SnapshotAny and use HeapTupleSatisfiesVacuum for
         * the visibility test.
         */
-       scan = index_beginscan(OldHeap, OldIndex,
+       if (OldIndex != NULL)
+               indexScan = index_beginscan(OldHeap, OldIndex,
                                                   SnapshotAny, 0, (ScanKey) NULL);
+       else
+               heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 
-       while ((tuple = index_getnext(scan, ForwardScanDirection)) != NULL)
+       for (;;)
        {
+               HeapTuple       tuple;
                HeapTuple       copiedTuple;
+               Buffer          buf;
                bool            isdead;
                int                     i;
 
                CHECK_FOR_INTERRUPTS();
 
-               /* Since we used no scan keys, should never need to recheck */
-               if (scan->xs_recheck)
-                       elog(ERROR, "CLUSTER does not support lossy index conditions");
+               if (OldIndex != NULL)
+               {
+                       tuple = index_getnext(indexScan, ForwardScanDirection);
+                       if (tuple == NULL)
+                               break;
 
-               LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+                       /* Since we used no scan keys, should never need to recheck */
+                       if (indexScan->xs_recheck)
+                               elog(ERROR, "CLUSTER does not support lossy index conditions");
 
-               switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin,
-                                                                                scan->xs_cbuf))
+                       buf = indexScan->xs_cbuf;
+               }
+               else
+               {
+                       tuple = heap_getnext(heapScan, ForwardScanDirection);
+                       if (tuple == NULL)
+                               break;
+
+                       buf = heapScan->rs_cbuf;
+               }
+
+               LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+               switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, buf))
                {
                        case HEAPTUPLE_DEAD:
                                /* Definitely dead */
@@ -888,7 +929,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
                                break;
                }
 
-               LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+               LockBuffer(buf, BUFFER_LOCK_UNLOCK);
 
                if (isdead)
                {
@@ -932,7 +973,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
                heap_freetuple(copiedTuple);
        }
 
-       index_endscan(scan);
+       if (OldIndex != NULL)
+               index_endscan(indexScan);
+       else
+               heap_endscan(heapScan);
 
        /* Write out any remaining tuples, and fsync if needed */
        end_heap_rewrite(rwstate);
@@ -940,7 +984,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
        pfree(values);
        pfree(isnull);
 
-       index_close(OldIndex, NoLock);
+       if (OldIndex != NULL)
+               index_close(OldIndex, NoLock);
        heap_close(OldHeap, NoLock);
        heap_close(NewHeap, NoLock);
 
index 0078ec7d89d685299710ba4f5eb72220f65af262..850680950e53f71f0afed84c0c5293eef27e9b42 100644 (file)
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.402 2010/01/02 16:57:40 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.403 2010/01/06 05:31:13 itagaki Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "catalog/catalog.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/storage.h"
+#include "commands/cluster.h"
 #include "commands/dbcommands.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -302,6 +304,8 @@ vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
        Assert((vacstmt->options & VACOPT_VACUUM) ||
                   !(vacstmt->options & (VACOPT_FULL | VACOPT_FREEZE)));
        Assert((vacstmt->options & VACOPT_ANALYZE) || vacstmt->va_cols == NIL);
+       Assert((vacstmt->options & VACOPT_FULL) ||
+                  !(vacstmt->options & VACOPT_INPLACE));
 
        stmttype = (vacstmt->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
 
@@ -1178,12 +1182,24 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound,
        save_nestlevel = NewGUCNestLevel();
 
        /*
-        * Do the actual work --- either FULL or "lazy" vacuum
+        * Do the actual work --- either FULL, FULL INPLACE, or "lazy" vacuum.
+        * We can use only FULL INPLACE vacuum for system relations.
         */
-       if (vacstmt->options & VACOPT_FULL)
+       if (!(vacstmt->options & VACOPT_FULL))
+               heldoff = lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all);
+       else if ((vacstmt->options & VACOPT_INPLACE) || IsSystemRelation(onerel))
                heldoff = full_vacuum_rel(onerel, vacstmt);
        else
-               heldoff = lazy_vacuum_rel(onerel, vacstmt, vac_strategy, scanned_all);
+       {
+               /* close relation before clustering, but hold lock until commit */
+               relation_close(onerel, NoLock);
+               onerel = NULL;
+
+               cluster_rel(relid, InvalidOid, false,
+                       (vacstmt->options & VACOPT_VERBOSE) != 0,
+                       vacstmt->freeze_min_age, vacstmt->freeze_table_age);
+               heldoff = false;
+       }
 
        /* Roll back any GUC changes executed by index functions */
        AtEOXact_GUC(false, save_nestlevel);
@@ -1192,7 +1208,8 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound,
        SetUserIdAndSecContext(save_userid, save_sec_context);
 
        /* all done with this class, but hold lock until commit */
-       relation_close(onerel, NoLock);
+       if (onerel)
+               relation_close(onerel, NoLock);
 
        /*
         * Complete the transaction and free all temporary memory used.
index 2ea08893f3b3bba0d9b8c0d4fd7c16d489cb883e..af26d80863305366b0fe2fc30e64254a952d0d25 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.702 2010/01/05 21:53:58 rhaas Exp $
+ *       $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.703 2010/01/06 05:31:13 itagaki Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -490,7 +490,7 @@ static TypeName *TableFuncTypeName(List *columns);
 
        IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P
        INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
-       INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
+       INNER_P INOUT INPLACE INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
        INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
        JOIN
@@ -6791,6 +6791,7 @@ vacuum_option_elem:
                        | VERBOSE                       { $$ = VACOPT_VERBOSE; }
                        | FREEZE                        { $$ = VACOPT_FREEZE; }
                        | FULL                          { $$ = VACOPT_FULL; }
+                       | FULL INPLACE          { $$ = VACOPT_FULL | VACOPT_INPLACE; }
                ;
 
 AnalyzeStmt:
@@ -10781,6 +10782,7 @@ unreserved_keyword:
                        | INHERIT
                        | INHERITS
                        | INLINE_P
+                       | INPLACE
                        | INPUT_P
                        | INSENSITIVE
                        | INSERT
index 658284da8608a3c43dc189ed27f0f7be0bb544f8..281e32db5b3bd3faf7f04561e5a5bfbdeeca9403 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/scripts/vacuumdb.c,v 1.29 2010/01/06 02:59:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/scripts/vacuumdb.c,v 1.30 2010/01/06 05:31:14 itagaki Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "common.h"
 
 
-static void vacuum_one_database(const char *dbname, bool full, bool verbose,
+static void vacuum_one_database(const char *dbname, bool full, bool inplace, bool verbose,
                                        bool and_analyze, bool only_analyze, bool freeze,
                                        const char *table, const char *host, const char *port,
                                        const char *username, enum trivalue prompt_password,
                                        const char *progname, bool echo);
-static void vacuum_all_databases(bool full, bool verbose, bool and_analyze,
+static void vacuum_all_databases(bool full, bool inplace, bool verbose, bool and_analyze,
                                         bool only_analyze, bool freeze,
                                         const char *host, const char *port,
                                         const char *username, enum trivalue prompt_password,
@@ -47,6 +47,7 @@ main(int argc, char *argv[])
                {"table", required_argument, NULL, 't'},
                {"full", no_argument, NULL, 'f'},
                {"verbose", no_argument, NULL, 'v'},
+               {"inplace", no_argument, NULL, 'i'},
                {NULL, 0, NULL, 0}
        };
 
@@ -68,13 +69,14 @@ main(int argc, char *argv[])
        char       *table = NULL;
        bool            full = false;
        bool            verbose = false;
+       bool            inplace = false;
 
        progname = get_progname(argv[0]);
        set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
        handle_help_version_opts(argc, argv, "vacuumdb", help);
 
-       while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zaFt:fv", long_options, &optindex)) != -1)
+       while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zaFt:fiv", long_options, &optindex)) != -1)
        {
                switch (c)
                {
@@ -120,6 +122,9 @@ main(int argc, char *argv[])
                        case 'f':
                                full = true;
                                break;
+                       case 'i':
+                               inplace = true;
+                               break;
                        case 'v':
                                verbose = true;
                                break;
@@ -143,7 +148,12 @@ main(int argc, char *argv[])
                        exit(1);
        }
 
-       setup_cancel_handler();
+       if (inplace && !full)
+       {
+               fprintf(stderr, _("%s: cannot use the \"inplace\" option when performing full vacuum\n"),
+                               progname);
+               exit(1);
+       }
 
        if (only_analyze)
        {
@@ -162,6 +172,8 @@ main(int argc, char *argv[])
                /* ignore 'and_analyze' */
        }
 
+       setup_cancel_handler();
+
        if (alldb)
        {
                if (dbname)
@@ -177,7 +189,7 @@ main(int argc, char *argv[])
                        exit(1);
                }
 
-               vacuum_all_databases(full, verbose, and_analyze, only_analyze, freeze,
+               vacuum_all_databases(full, inplace, verbose, and_analyze, only_analyze, freeze,
                                                         host, port, username, prompt_password,
                                                         progname, echo, quiet);
        }
@@ -193,7 +205,7 @@ main(int argc, char *argv[])
                                dbname = get_user_name(progname);
                }
 
-               vacuum_one_database(dbname, full, verbose, and_analyze, only_analyze,
+               vacuum_one_database(dbname, full, inplace, verbose, and_analyze, only_analyze,
                                                        freeze, table,
                                                        host, port, username, prompt_password,
                                                        progname, echo);
@@ -204,7 +216,7 @@ main(int argc, char *argv[])
 
 
 static void
-vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyze,
+vacuum_one_database(const char *dbname, bool full, bool inplace, bool verbose, bool and_analyze,
                                        bool only_analyze, bool freeze, const char *table,
                                        const char *host, const char *port,
                                        const char *username, enum trivalue prompt_password,
@@ -216,25 +228,67 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz
 
        initPQExpBuffer(&sql);
 
+       conn = connectDatabase(dbname, host, port, username, prompt_password, progname);
+
        if (only_analyze)
+       {
                appendPQExpBuffer(&sql, "ANALYZE");
+               if (verbose)
+                       appendPQExpBuffer(&sql, " VERBOSE");
+       }
        else
        {
                appendPQExpBuffer(&sql, "VACUUM");
-               if (full)
-                       appendPQExpBuffer(&sql, " FULL");
-               if (freeze)
-                       appendPQExpBuffer(&sql, " FREEZE");
-               if (and_analyze)
-                       appendPQExpBuffer(&sql, " ANALYZE");
+               if (PQserverVersion(conn) >= 80500)
+               {
+                       const char *paren = " (";
+                       const char *comma = ", ";
+                       const char *sep = paren;
+
+                       if (full)
+                       {
+                               appendPQExpBuffer(&sql, "%sFULL%s", sep,
+                                                                 inplace ? " INPLACE" : "");
+                               sep = comma;
+                       }
+                       if (freeze)
+                       {
+                               appendPQExpBuffer(&sql, "%sFREEZE", sep);
+                               sep = comma;
+                       }
+                       if (verbose)
+                       {
+                               appendPQExpBuffer(&sql, "%sVERBOSE", sep);
+                               sep = comma;
+                       }
+                       if (and_analyze)
+                       {
+                               appendPQExpBuffer(&sql, "%sANALYZE", sep);
+                               sep = comma;
+                       }
+                       if (sep != paren)
+                               appendPQExpBuffer(&sql, ")");
+               }
+               else
+               {
+                       /*
+                        * On older servers, VACUUM FULL is equivalent to VACUUM (FULL
+                        * INPLACE) on newer servers, so we can ignore 'inplace'.
+                        */
+                       if (full)
+                               appendPQExpBuffer(&sql, " FULL");
+                       if (freeze)
+                               appendPQExpBuffer(&sql, " FREEZE");
+                       if (verbose)
+                               appendPQExpBuffer(&sql, " VERBOSE");
+                       if (and_analyze)
+                               appendPQExpBuffer(&sql, " ANALYZE");
+               }
        }
-       if (verbose)
-               appendPQExpBuffer(&sql, " VERBOSE");
        if (table)
                appendPQExpBuffer(&sql, " %s", table);
        appendPQExpBuffer(&sql, ";\n");
 
-       conn = connectDatabase(dbname, host, port, username, prompt_password, progname);
        if (!executeMaintenanceCommand(conn, sql.data, echo))
        {
                if (table)
@@ -252,7 +306,7 @@ vacuum_one_database(const char *dbname, bool full, bool verbose, bool and_analyz
 
 
 static void
-vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool only_analyze,
+vacuum_all_databases(bool full, bool inplace, bool verbose, bool and_analyze, bool only_analyze,
                                         bool freeze, const char *host, const char *port,
                                         const char *username, enum trivalue prompt_password,
                                         const char *progname, bool echo, bool quiet)
@@ -275,7 +329,7 @@ vacuum_all_databases(bool full, bool verbose, bool and_analyze, bool only_analyz
                        fflush(stdout);
                }
 
-               vacuum_one_database(dbname, full, verbose, and_analyze, only_analyze,
+               vacuum_one_database(dbname, full, inplace, verbose, and_analyze, only_analyze,
                                                        freeze, NULL, host, port, username, prompt_password,
                                                        progname, echo);
        }
@@ -296,6 +350,7 @@ help(const char *progname)
        printf(_("  -e, --echo                      show the commands being sent to the server\n"));
        printf(_("  -f, --full                      do full vacuuming\n"));
        printf(_("  -F, --freeze                    freeze row transaction information\n"));
+       printf(_("  -i, --inplace                   do full inplace vacuuming\n"));
        printf(_("  -o, --only-analyze              only update optimizer hints\n"));
        printf(_("  -q, --quiet                     don't write any messages\n"));
        printf(_("  -t, --table='TABLE[(COLUMNS)]'  vacuum specific table only\n"));
index dd7a1caf1015fb4e176f65a220ea0f7208592870..c58013c640344ef86e2d0945113b8abb792b84de 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.37 2010/01/02 16:58:03 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.38 2010/01/06 05:31:14 itagaki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,8 @@
 
 
 extern void cluster(ClusterStmt *stmt, bool isTopLevel);
-
+extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck,
+                                       bool verbose, int freeze_min_age, int freeze_table_age);
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
                                                   bool recheck);
 extern void mark_index_clustered(Relation rel, Oid indexOid);
index 18673ec30d8a11fd42dc0b72119e25c692c0e9f9..9a0bb8eec3f8a734f46e7fc1390f0d369e2b47ac 100644 (file)
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.422 2010/01/05 21:53:59 rhaas Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.423 2010/01/06 05:31:14 itagaki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2244,7 +2244,8 @@ typedef enum VacuumOption
        VACOPT_ANALYZE          = 1 << 1,       /* do ANALYZE */
        VACOPT_VERBOSE          = 1 << 2,       /* print progress info */
        VACOPT_FREEZE           = 1 << 3,       /* FREEZE option */
-       VACOPT_FULL                     = 1 << 4        /* FULL (non-concurrent) vacuum */
+       VACOPT_FULL                     = 1 << 4,       /* FULL (non-concurrent) vacuum */
+       VACOPT_INPLACE          = 1 << 5        /* traditional FULL INPLACE vacuum */
 } VacuumOption;
 
 typedef struct VacuumStmt
index 72d717bfda87afa93f4c85cdfa60848aeb61b46e..b7d647ab279e54ce65356265e86e33a2939ba433 100644 (file)
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.9 2010/01/02 16:58:07 momjian Exp $
+ *       $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.10 2010/01/06 05:31:14 itagaki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -193,6 +193,7 @@ PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD)
 PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD)
+PG_KEYWORD("inplace", INPLACE, UNRESERVED_KEYWORD)
 PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD)
 PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD)
index 1a139d03773e920f0889fa72459888a978729db3..e94e1d538f802044fed614e822074f239479c8b4 100644 (file)
@@ -57,4 +57,65 @@ SELECT * FROM vactst;
 (0 rows)
 
 VACUUM (FULL, FREEZE) vactst;
+VACUUM (ANALYZE, FULL INPLACE) vactst;
+CREATE TABLE vaccluster (i INT PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "vaccluster_pkey" for table "vaccluster"
+ALTER TABLE vaccluster CLUSTER ON vaccluster_pkey;
+INSERT INTO vaccluster SELECT * FROM vactst;
+CREATE TEMP TABLE vacid (
+  relid  regclass,
+  filenode_0 oid,
+  filenode_1 oid,
+  filenode_2 oid,
+  filenode_3 oid
+);
+INSERT INTO vacid (relid, filenode_0)
+SELECT oid, relfilenode FROM pg_class WHERE oid::regclass IN (
+  'pg_am',       -- normal catalog
+  'pg_class',    -- fundamental catalog
+  'pg_database', -- shared catalog
+  'vaccluster' , -- clustered table
+  'vacid',       -- temp table
+  'vactst'       -- normal table
+);
+-- only clusterd table should be changed
+CLUSTER vaccluster;
+UPDATE vacid SET filenode_1 = relfilenode
+  FROM pg_class WHERE oid = relid;
+-- all tables should not be changed
+VACUUM (FULL INPLACE) pg_am;
+VACUUM (FULL INPLACE) pg_class;
+VACUUM (FULL INPLACE) pg_database;
+VACUUM (FULL INPLACE) vaccluster;
+VACUUM (FULL INPLACE) vacid;
+VACUUM (FULL INPLACE) vactst;
+UPDATE vacid SET filenode_2 = relfilenode
+  FROM pg_class WHERE oid = relid;
+-- only non-system tables should be changed
+VACUUM FULL pg_am;
+VACUUM FULL pg_class;
+VACUUM FULL pg_database;
+VACUUM FULL vaccluster;
+VACUUM FULL vacid;
+VACUUM FULL vactst;
+UPDATE vacid SET filenode_3 = relfilenode
+  FROM pg_class WHERE oid = relid;
+SELECT relid,
+       filenode_0 = filenode_1 AS cluster,
+       filenode_1 = filenode_2 AS full_inplace,
+       filenode_2 = filenode_3 AS full
+  FROM vacid
+ ORDER BY relid::text;
+    relid    | cluster | full_inplace | full 
+-------------+---------+--------------+------
+ pg_am       | t       | t            | t
+ pg_class    | t       | t            | t
+ pg_database | t       | t            | t
+ vaccluster  | f       | t            | f
+ vacid       | t       | t            | f
+ vactst      | t       | t            | f
+(6 rows)
+
+DROP TABLE vaccluster;
+DROP TABLE vacid;
 DROP TABLE vactst;
index e1686971c967427971cb5382f2b6a2b3b7c119f8..87772ad5f6d6c3c7d414e6116ea8465d04edd502 100644 (file)
@@ -40,5 +40,62 @@ DELETE FROM vactst;
 SELECT * FROM vactst;
 
 VACUUM (FULL, FREEZE) vactst;
+VACUUM (ANALYZE, FULL INPLACE) vactst;
 
+CREATE TABLE vaccluster (i INT PRIMARY KEY);
+ALTER TABLE vaccluster CLUSTER ON vaccluster_pkey;
+INSERT INTO vaccluster SELECT * FROM vactst;
+
+CREATE TEMP TABLE vacid (
+  relid  regclass,
+  filenode_0 oid,
+  filenode_1 oid,
+  filenode_2 oid,
+  filenode_3 oid
+);
+
+INSERT INTO vacid (relid, filenode_0)
+SELECT oid, relfilenode FROM pg_class WHERE oid::regclass IN (
+  'pg_am',       -- normal catalog
+  'pg_class',    -- fundamental catalog
+  'pg_database', -- shared catalog
+  'vaccluster' , -- clustered table
+  'vacid',       -- temp table
+  'vactst'       -- normal table
+);
+
+-- only clusterd table should be changed
+CLUSTER vaccluster;
+UPDATE vacid SET filenode_1 = relfilenode
+  FROM pg_class WHERE oid = relid;
+
+-- all tables should not be changed
+VACUUM (FULL INPLACE) pg_am;
+VACUUM (FULL INPLACE) pg_class;
+VACUUM (FULL INPLACE) pg_database;
+VACUUM (FULL INPLACE) vaccluster;
+VACUUM (FULL INPLACE) vacid;
+VACUUM (FULL INPLACE) vactst;
+UPDATE vacid SET filenode_2 = relfilenode
+  FROM pg_class WHERE oid = relid;
+
+-- only non-system tables should be changed
+VACUUM FULL pg_am;
+VACUUM FULL pg_class;
+VACUUM FULL pg_database;
+VACUUM FULL vaccluster;
+VACUUM FULL vacid;
+VACUUM FULL vactst;
+UPDATE vacid SET filenode_3 = relfilenode
+  FROM pg_class WHERE oid = relid;
+
+SELECT relid,
+       filenode_0 = filenode_1 AS cluster,
+       filenode_1 = filenode_2 AS full_inplace,
+       filenode_2 = filenode_3 AS full
+  FROM vacid
+ ORDER BY relid::text;
+
+DROP TABLE vaccluster;
+DROP TABLE vacid;
 DROP TABLE vactst;