Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 64c6048

Browse files
committed
Improve the -l (limit) option recently added to contrib/vacuumlo.
Instead of just stopping after removing an arbitrary subset of orphaned large objects, commit and start a new transaction after each -l objects. This is just as effective as the original patch at limiting the number of locks used, and it doesn't require doing the OID collection process repeatedly to get everything. Since the option no longer changes the fundamental behavior of vacuumlo, and it avoids a known server-side limitation, enable it by default (with a default limit of 1000 LOs per transaction). In passing, be more careful about properly quoting the names of tables and fields, and do some other cosmetic cleanup.
1 parent 9d23a70 commit 64c6048

File tree

2 files changed

+109
-48
lines changed

2 files changed

+109
-48
lines changed

contrib/vacuumlo/vacuumlo.c

Lines changed: 94 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929

3030
extern char *optarg;
3131
extern int optind,
32-
opterr,
33-
optopt;
32+
opterr;
3433

3534
enum trivalue
3635
{
@@ -50,16 +49,16 @@ struct _param
5049
long transaction_limit;
5150
};
5251

53-
int vacuumlo(char *, struct _param *);
54-
void usage(const char *progname);
52+
static int vacuumlo(const char *database, const struct _param * param);
53+
static void usage(const char *progname);
5554

5655

5756

5857
/*
5958
* This vacuums LOs of one database. It returns 0 on success, -1 on failure.
6059
*/
61-
int
62-
vacuumlo(char *database, struct _param * param)
60+
static int
61+
vacuumlo(const char *database, const struct _param * param)
6362
{
6463
PGconn *conn;
6564
PGresult *res,
@@ -72,6 +71,7 @@ vacuumlo(char *database, struct _param * param)
7271
bool new_pass;
7372
bool success = true;
7473

74+
/* Note: password can be carried over from a previous call */
7575
if (param->pg_prompt == TRI_YES && password == NULL)
7676
password = simple_prompt("Password: ", 100, false);
7777

@@ -119,7 +119,7 @@ vacuumlo(char *database, struct _param * param)
119119

120120
if (param->verbose)
121121
{
122-
fprintf(stdout, "Connected to %s\n", database);
122+
fprintf(stdout, "Connected to database \"%s\"\n", database);
123123
if (param->dry_run)
124124
fprintf(stdout, "Test run: no large objects will be removed!\n");
125125
}
@@ -220,9 +220,21 @@ vacuumlo(char *database, struct _param * param)
220220
if (param->verbose)
221221
fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table);
222222

223+
schema = PQescapeIdentifier(conn, schema, strlen(schema));
224+
table = PQescapeIdentifier(conn, table, strlen(table));
225+
field = PQescapeIdentifier(conn, field, strlen(field));
226+
227+
if (!schema || !table || !field)
228+
{
229+
fprintf(stderr, "Out of memory\n");
230+
PQclear(res);
231+
PQfinish(conn);
232+
return -1;
233+
}
234+
223235
snprintf(buf, BUFSIZE,
224236
"DELETE FROM vacuum_l "
225-
"WHERE lo IN (SELECT \"%s\" FROM \"%s\".\"%s\")",
237+
"WHERE lo IN (SELECT %s FROM %s.%s)",
226238
field, schema, table);
227239
res2 = PQexec(conn, buf);
228240
if (PQresultStatus(res2) != PGRES_COMMAND_OK)
@@ -236,23 +248,35 @@ vacuumlo(char *database, struct _param * param)
236248
return -1;
237249
}
238250
PQclear(res2);
251+
252+
PQfreemem(schema);
253+
PQfreemem(table);
254+
PQfreemem(field);
239255
}
240256
PQclear(res);
241257

242258
/*
243-
* Run the actual deletes in a single transaction. Note that this would
244-
* be a bad idea in pre-7.1 Postgres releases (since rolling back a table
245-
* delete used to cause problems), but it should be safe now.
259+
* Now, those entries remaining in vacuum_l are orphans. Delete 'em.
260+
*
261+
* We don't want to run each delete as an individual transaction, because
262+
* the commit overhead would be high. However, since 9.0 the backend will
263+
* acquire a lock per deleted LO, so deleting too many LOs per transaction
264+
* risks running out of room in the shared-memory lock table.
265+
* Accordingly, we delete up to transaction_limit LOs per transaction.
246266
*/
247267
res = PQexec(conn, "begin");
268+
if (PQresultStatus(res) != PGRES_COMMAND_OK)
269+
{
270+
fprintf(stderr, "Failed to start transaction:\n");
271+
fprintf(stderr, "%s", PQerrorMessage(conn));
272+
PQclear(res);
273+
PQfinish(conn);
274+
return -1;
275+
}
248276
PQclear(res);
249277

250-
/*
251-
* Finally, those entries remaining in vacuum_l are orphans.
252-
*/
253278
buf[0] = '\0';
254-
strcat(buf, "SELECT lo ");
255-
strcat(buf, "FROM vacuum_l");
279+
strcat(buf, "SELECT lo FROM vacuum_l");
256280
res = PQexec(conn, buf);
257281
if (PQresultStatus(res) != PGRES_TUPLES_OK)
258282
{
@@ -292,44 +316,76 @@ vacuumlo(char *database, struct _param * param)
292316
}
293317
else
294318
deleted++;
295-
if (param->transaction_limit != 0 && deleted >= param->transaction_limit)
296-
break;
319+
if (param->transaction_limit > 0 &&
320+
(deleted % param->transaction_limit) == 0)
321+
{
322+
res2 = PQexec(conn, "commit");
323+
if (PQresultStatus(res2) != PGRES_COMMAND_OK)
324+
{
325+
fprintf(stderr, "Failed to commit transaction:\n");
326+
fprintf(stderr, "%s", PQerrorMessage(conn));
327+
PQclear(res2);
328+
PQclear(res);
329+
PQfinish(conn);
330+
return -1;
331+
}
332+
PQclear(res2);
333+
res2 = PQexec(conn, "begin");
334+
if (PQresultStatus(res2) != PGRES_COMMAND_OK)
335+
{
336+
fprintf(stderr, "Failed to start transaction:\n");
337+
fprintf(stderr, "%s", PQerrorMessage(conn));
338+
PQclear(res2);
339+
PQclear(res);
340+
PQfinish(conn);
341+
return -1;
342+
}
343+
PQclear(res2);
344+
}
297345
}
298346
PQclear(res);
299347

300348
/*
301349
* That's all folks!
302350
*/
303-
res = PQexec(conn, "end");
351+
res = PQexec(conn, "commit");
352+
if (PQresultStatus(res) != PGRES_COMMAND_OK)
353+
{
354+
fprintf(stderr, "Failed to commit transaction:\n");
355+
fprintf(stderr, "%s", PQerrorMessage(conn));
356+
PQclear(res);
357+
PQfinish(conn);
358+
return -1;
359+
}
304360
PQclear(res);
305361

306362
PQfinish(conn);
307363

308364
if (param->verbose)
309365
{
310366
if (param->dry_run)
311-
fprintf(stdout, "\rWould remove %ld large objects from %s.\n",
367+
fprintf(stdout, "\rWould remove %ld large objects from database \"%s\".\n",
312368
deleted, database);
313369
else if (success)
314370
fprintf(stdout,
315-
"\rSuccessfully removed %ld large objects from %s.\n",
371+
"\rSuccessfully removed %ld large objects from database \"%s\".\n",
316372
deleted, database);
317373
else
318-
fprintf(stdout, "\rRemoval from %s failed at object %ld of %ld.\n",
374+
fprintf(stdout, "\rRemoval from database \"%s\" failed at object %ld of %ld.\n",
319375
database, deleted, matched);
320376
}
321377

322378
return ((param->dry_run || success) ? 0 : -1);
323379
}
324380

325-
void
381+
static void
326382
usage(const char *progname)
327383
{
328384
printf("%s removes unreferenced large objects from databases.\n\n", progname);
329385
printf("Usage:\n %s [OPTION]... DBNAME...\n\n", progname);
330386
printf("Options:\n");
331387
printf(" -h HOSTNAME database server host or socket directory\n");
332-
printf(" -l LIMIT stop after removing LIMIT large objects\n");
388+
printf(" -l LIMIT commit after removing each LIMIT large objects\n");
333389
printf(" -n don't remove large objects, just show what would be done\n");
334390
printf(" -p PORT database server port\n");
335391
printf(" -U USERNAME user name to connect as\n");
@@ -354,15 +410,16 @@ main(int argc, char **argv)
354410

355411
progname = get_progname(argv[0]);
356412

357-
/* Parameter handling */
413+
/* Set default parameter values */
358414
param.pg_user = NULL;
359415
param.pg_prompt = TRI_DEFAULT;
360416
param.pg_host = NULL;
361417
param.pg_port = NULL;
362418
param.verbose = 0;
363419
param.dry_run = 0;
364-
param.transaction_limit = 0;
420+
param.transaction_limit = 1000;
365421

422+
/* Process command-line arguments */
366423
if (argc > 1)
367424
{
368425
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
@@ -397,6 +454,16 @@ main(int argc, char **argv)
397454
param.dry_run = 1;
398455
param.verbose = 1;
399456
break;
457+
case 'l':
458+
param.transaction_limit = strtol(optarg, NULL, 10);
459+
if (param.transaction_limit < 0)
460+
{
461+
fprintf(stderr,
462+
"%s: transaction limit must not be negative (0 disables)\n",
463+
progname);
464+
exit(1);
465+
}
466+
break;
400467
case 'U':
401468
param.pg_user = strdup(optarg);
402469
break;
@@ -415,16 +482,6 @@ main(int argc, char **argv)
415482
}
416483
param.pg_port = strdup(optarg);
417484
break;
418-
case 'l':
419-
param.transaction_limit = strtol(optarg, NULL, 10);
420-
if (param.transaction_limit < 0)
421-
{
422-
fprintf(stderr,
423-
"%s: transaction limit must not be negative (0 disables)\n",
424-
progname);
425-
exit(1);
426-
}
427-
break;
428485
case 'h':
429486
param.pg_host = strdup(optarg);
430487
break;
@@ -435,7 +492,7 @@ main(int argc, char **argv)
435492
if (optind >= argc)
436493
{
437494
fprintf(stderr, "vacuumlo: missing required argument: database name\n");
438-
fprintf(stderr, "Try 'vacuumlo -?' for help.\n");
495+
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
439496
exit(1);
440497
}
441498

doc/src/sgml/vacuumlo.sgml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,22 @@ vacuumlo [options] database [database2 ... databaseN]
5050
</varlistentry>
5151

5252
<varlistentry>
53-
<term><option>-U</option> <replaceable>username</></term>
53+
<term><option>-l</option> <replaceable>limit</></term>
5454
<listitem>
55-
<para>User name to connect as.</para>
55+
<para>
56+
Remove no more than <replaceable>limit</> large objects per
57+
transaction (default 1000). Since the server acquires a lock per LO
58+
removed, removing too many LOs in one transaction risks exceeding
59+
<xref linkend="guc-max-locks-per-transaction">. Set the limit to
60+
zero if you want all removals done in a single transaction.
61+
</para>
5662
</listitem>
5763
</varlistentry>
5864

5965
<varlistentry>
60-
<term><option>-l</option> <replaceable>limit</></term>
66+
<term><option>-U</option> <replaceable>username</></term>
6167
<listitem>
62-
<para>
63-
Stop after removing LIMIT large objects. Useful to avoid
64-
exceeding <xref linkend="guc-max-locks-per-transaction">.
65-
</para>
68+
<para>User name to connect as.</para>
6669
</listitem>
6770
</varlistentry>
6871

@@ -120,18 +123,19 @@ vacuumlo [options] database [database2 ... databaseN]
120123
<title>Method</title>
121124

122125
<para>
123-
First, it builds a temporary table which contains all of the OIDs of the
124-
large objects in that database.
126+
First, <application>vacuumlo</> builds a temporary table which contains all
127+
of the OIDs of the large objects in the selected database.
125128
</para>
126129

127130
<para>
128131
It then scans through all columns in the database that are of type
129132
<type>oid</> or <type>lo</>, and removes matching entries from the
130-
temporary table.
133+
temporary table. (Note: only types with these names are considered;
134+
in particular, domains over them are not considered.)
131135
</para>
132136

133137
<para>
134-
The remaining entries in the temp table identify orphaned LOs.
138+
The remaining entries in the temporary table identify orphaned LOs.
135139
These are removed.
136140
</para>
137141
</sect2>

0 commit comments

Comments
 (0)