]> granicus.if.org Git - postgresql/commitdiff
Back-patch contrib/vacuumlo's new -l (limit) option into 9.0 and 9.1.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 21 Mar 2012 17:04:07 +0000 (13:04 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 21 Mar 2012 17:04:55 +0000 (13:04 -0400)
Since 9.0, removing lots of large objects in a single transaction risks
exceeding max_locks_per_transaction, because we merged large object removal
into the generic object-drop mechanism, which takes out an exclusive lock
on each object to be dropped.  This creates a hazard for contrib/vacuumlo,
which has historically tried to drop all unreferenced large objects in one
transaction.  There doesn't seem to be any correctness requirement to do it
that way, though; we only need to drop enough large objects per transaction
to amortize the commit costs.

To prevent a regression from pre-9.0 releases wherein vacuumlo worked just
fine, back-patch commits b69f2e36402aaa222ed03c1769b3de6d5be5f302 and
64c604898e812aa93c124c666e8709fff1b8dd26, which break vacuumlo's deletions
into multiple transactions with a user-controllable upper limit on the
number of objects dropped per transaction.

Tim Lewis, Robert Haas, Tom Lane

contrib/vacuumlo/vacuumlo.c
doc/src/sgml/vacuumlo.sgml

index f6e2a28cf38cc5645c83a6eb0b73aebe87bdb2cc..641a8c3425d97614a14a547f91651f0bbd83eae8 100644 (file)
@@ -3,7 +3,7 @@
  * vacuumlo.c
  *       This removes orphaned large objects from a database.
  *
- * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
@@ -22,7 +22,6 @@
 #endif
 
 #include "libpq-fe.h"
-#include "libpq/libpq-fs.h"
 
 #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
 
@@ -30,8 +29,7 @@
 
 extern char *optarg;
 extern int     optind,
-                       opterr,
-                       optopt;
+                       opterr;
 
 enum trivalue
 {
@@ -48,29 +46,32 @@ struct _param
        char       *pg_host;
        int                     verbose;
        int                     dry_run;
+       long            transaction_limit;
 };
 
-int                    vacuumlo(char *, struct _param *);
-void           usage(const char *progname);
+static int     vacuumlo(const char *database, const struct _param * param);
+static void usage(const char *progname);
 
 
 
 /*
  * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
  */
-int
-vacuumlo(char *database, struct _param * param)
+static int
+vacuumlo(const char *database, const struct _param * param)
 {
        PGconn     *conn;
        PGresult   *res,
                           *res2;
        char            buf[BUFSIZE];
-       int                     matched;
-       int                     deleted;
+       long            matched;
+       long            deleted;
        int                     i;
        static char *password = NULL;
        bool            new_pass;
+       bool        success = true;
 
+       /* Note: password can be carried over from a previous call */
        if (param->pg_prompt == TRI_YES && password == NULL)
                password = simple_prompt("Password: ", 100, false);
 
@@ -118,7 +119,7 @@ vacuumlo(char *database, struct _param * param)
 
        if (param->verbose)
        {
-               fprintf(stdout, "Connected to %s\n", database);
+               fprintf(stdout, "Connected to database \"%s\"\n", database);
                if (param->dry_run)
                        fprintf(stdout, "Test run: no large objects will be removed!\n");
        }
@@ -219,9 +220,21 @@ vacuumlo(char *database, struct _param * param)
                if (param->verbose)
                        fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table);
 
+               schema = PQescapeIdentifier(conn, schema, strlen(schema));
+               table = PQescapeIdentifier(conn, table, strlen(table));
+               field = PQescapeIdentifier(conn, field, strlen(field));
+
+               if (!schema || !table || !field)
+               {
+                       fprintf(stderr, "Out of memory\n");
+                       PQclear(res);
+                       PQfinish(conn);
+                       return -1;
+               }
+
                snprintf(buf, BUFSIZE,
                                 "DELETE FROM vacuum_l "
-                                "WHERE lo IN (SELECT \"%s\" FROM \"%s\".\"%s\")",
+                                "WHERE lo IN (SELECT %s FROM %s.%s)",
                                 field, schema, table);
                res2 = PQexec(conn, buf);
                if (PQresultStatus(res2) != PGRES_COMMAND_OK)
@@ -235,23 +248,35 @@ vacuumlo(char *database, struct _param * param)
                        return -1;
                }
                PQclear(res2);
+
+               PQfreemem(schema);
+               PQfreemem(table);
+               PQfreemem(field);
        }
        PQclear(res);
 
        /*
-        * Run the actual deletes in a single transaction.      Note that this would
-        * be a bad idea in pre-7.1 Postgres releases (since rolling back a table
-        * delete used to cause problems), but it should be safe now.
+        * Now, those entries remaining in vacuum_l are orphans.  Delete 'em.
+        *
+        * We don't want to run each delete as an individual transaction, because
+        * the commit overhead would be high.  However, since 9.0 the backend will
+        * acquire a lock per deleted LO, so deleting too many LOs per transaction
+        * risks running out of room in the shared-memory lock table.
+        * Accordingly, we delete up to transaction_limit LOs per transaction.
         */
        res = PQexec(conn, "begin");
+       if (PQresultStatus(res) != PGRES_COMMAND_OK)
+       {
+               fprintf(stderr, "Failed to start transaction:\n");
+               fprintf(stderr, "%s", PQerrorMessage(conn));
+               PQclear(res);
+               PQfinish(conn);
+               return -1;
+       }
        PQclear(res);
 
-       /*
-        * Finally, those entries remaining in vacuum_l are orphans.
-        */
        buf[0] = '\0';
-       strcat(buf, "SELECT lo ");
-       strcat(buf, "FROM vacuum_l");
+       strcat(buf, "SELECT lo FROM vacuum_l");
        res = PQexec(conn, buf);
        if (PQresultStatus(res) != PGRES_TUPLES_OK)
        {
@@ -280,37 +305,87 @@ vacuumlo(char *database, struct _param * param)
                        {
                                fprintf(stderr, "\nFailed to remove lo %u: ", lo);
                                fprintf(stderr, "%s", PQerrorMessage(conn));
+                               if (PQtransactionStatus(conn) == PQTRANS_INERROR)
+                               {
+                                       success = false;
+                                       break;
+                               }
                        }
                        else
                                deleted++;
                }
                else
                        deleted++;
+               if (param->transaction_limit > 0 &&
+                       (deleted % param->transaction_limit) == 0)
+               {
+                       res2 = PQexec(conn, "commit");
+                       if (PQresultStatus(res2) != PGRES_COMMAND_OK)
+                       {
+                               fprintf(stderr, "Failed to commit transaction:\n");
+                               fprintf(stderr, "%s", PQerrorMessage(conn));
+                               PQclear(res2);
+                               PQclear(res);
+                               PQfinish(conn);
+                               return -1;
+                       }
+                       PQclear(res2);
+                       res2 = PQexec(conn, "begin");
+                       if (PQresultStatus(res2) != PGRES_COMMAND_OK)
+                       {
+                               fprintf(stderr, "Failed to start transaction:\n");
+                               fprintf(stderr, "%s", PQerrorMessage(conn));
+                               PQclear(res2);
+                               PQclear(res);
+                               PQfinish(conn);
+                               return -1;
+                       }
+                       PQclear(res2);
+               }
        }
        PQclear(res);
 
        /*
         * That's all folks!
         */
-       res = PQexec(conn, "end");
+       res = PQexec(conn, "commit");
+       if (PQresultStatus(res) != PGRES_COMMAND_OK)
+       {
+               fprintf(stderr, "Failed to commit transaction:\n");
+               fprintf(stderr, "%s", PQerrorMessage(conn));
+               PQclear(res);
+               PQfinish(conn);
+               return -1;
+       }
        PQclear(res);
 
        PQfinish(conn);
 
        if (param->verbose)
-               fprintf(stdout, "\r%s %d large objects from %s.\n",
-                  (param->dry_run ? "Would remove" : "Removed"), deleted, database);
+       {
+               if (param->dry_run)
+                       fprintf(stdout, "\rWould remove %ld large objects from database \"%s\".\n",
+                                       deleted, database);
+               else if (success)
+                       fprintf(stdout,
+                                       "\rSuccessfully removed %ld large objects from database \"%s\".\n",
+                                       deleted, database);
+               else
+                       fprintf(stdout, "\rRemoval from database \"%s\" failed at object %ld of %ld.\n",
+                                       database, deleted, matched);
+       }
 
-       return 0;
+       return ((param->dry_run || success) ? 0 : -1);
 }
 
-void
+static void
 usage(const char *progname)
 {
        printf("%s removes unreferenced large objects from databases.\n\n", progname);
        printf("Usage:\n  %s [OPTION]... DBNAME...\n\n", progname);
        printf("Options:\n");
        printf("  -h HOSTNAME  database server host or socket directory\n");
+       printf("  -l LIMIT     commit after removing each LIMIT large objects\n");
        printf("  -n           don't remove large objects, just show what would be done\n");
        printf("  -p PORT      database server port\n");
        printf("  -U USERNAME  user name to connect as\n");
@@ -335,14 +410,16 @@ main(int argc, char **argv)
 
        progname = get_progname(argv[0]);
 
-       /* Parameter handling */
+       /* Set default parameter values */
        param.pg_user = NULL;
        param.pg_prompt = TRI_DEFAULT;
        param.pg_host = NULL;
        param.pg_port = NULL;
        param.verbose = 0;
        param.dry_run = 0;
+       param.transaction_limit = 1000;
 
+       /* Process command-line arguments */
        if (argc > 1)
        {
                if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
@@ -359,7 +436,7 @@ main(int argc, char **argv)
 
        while (1)
        {
-               c = getopt(argc, argv, "h:U:p:vnwW");
+               c = getopt(argc, argv, "h:l:U:p:vnwW");
                if (c == -1)
                        break;
 
@@ -377,6 +454,16 @@ main(int argc, char **argv)
                                param.dry_run = 1;
                                param.verbose = 1;
                                break;
+                       case 'l':
+                               param.transaction_limit = strtol(optarg, NULL, 10);
+                               if (param.transaction_limit < 0)
+                               {
+                                       fprintf(stderr,
+                               "%s: transaction limit must not be negative (0 disables)\n",
+                                               progname);
+                                       exit(1);
+                               }
+                               break;
                        case 'U':
                                param.pg_user = strdup(optarg);
                                break;
@@ -405,7 +492,7 @@ main(int argc, char **argv)
        if (optind >= argc)
        {
                fprintf(stderr, "vacuumlo: missing required argument: database name\n");
-               fprintf(stderr, "Try 'vacuumlo -?' for help.\n");
+               fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
                exit(1);
        }
 
index 471a6ca32be9e75e7a38b3fe51891373ce02e357..97753de6c0c3ab0b9a045cbd36b89df25bc66a77 100644 (file)
@@ -49,6 +49,19 @@ vacuumlo [options] database [database2 ... databaseN]
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><option>-l</option> <replaceable>limit</></term>
+    <listitem>
+     <para>
+      Remove no more than <replaceable>limit</> large objects per
+      transaction (default 1000).  Since the server acquires a lock per LO
+      removed, removing too many LOs in one transaction risks exceeding
+      <xref linkend="guc-max-locks-per-transaction">.  Set the limit to
+      zero if you want all removals done in a single transaction.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><option>-U</option> <replaceable>username</></term>
     <listitem>
@@ -110,18 +123,19 @@ vacuumlo [options] database [database2 ... databaseN]
   <title>Method</title>
 
   <para>
-   First, it builds a temporary table which contains all of the OIDs of the
-   large objects in that database.
+   First, <application>vacuumlo</> builds a temporary table which contains all
+   of the OIDs of the large objects in the selected database.
   </para>
 
   <para>
    It then scans through all columns in the database that are of type
    <type>oid</> or <type>lo</>, and removes matching entries from the
-  temporary table.
+   temporary table.  (Note: only types with these names are considered;
+   in particular, domains over them are not considered.)
   </para>
 
   <para>
-   The remaining entries in the temp table identify orphaned LOs.
+   The remaining entries in the temporary table identify orphaned LOs.
    These are removed.
   </para>
  </sect2>