@@ -43,6 +43,8 @@ typedef struct vacuumingOptions
4343 bool freeze ;
4444 bool disable_page_skipping ;
4545 bool skip_locked ;
46+ int min_xid_age ;
47+ int min_mxid_age ;
4648} vacuumingOptions ;
4749
4850
@@ -113,6 +115,8 @@ main(int argc, char *argv[])
113115 {"analyze-in-stages" , no_argument , NULL , 3 },
114116 {"disable-page-skipping" , no_argument , NULL , 4 },
115117 {"skip-locked" , no_argument , NULL , 5 },
118+ {"min-xid-age" , required_argument , NULL , 6 },
119+ {"min-mxid-age" , required_argument , NULL , 7 },
116120 {NULL , 0 , NULL , 0 }
117121 };
118122
@@ -222,6 +226,24 @@ main(int argc, char *argv[])
222226 case 5 :
223227 vacopts .skip_locked = true;
224228 break ;
229+ case 6 :
230+ vacopts .min_xid_age = atoi (optarg );
231+ if (vacopts .min_xid_age <= 0 )
232+ {
233+ fprintf (stderr , _ ("%s: minimum transaction ID age must be at least 1\n" ),
234+ progname );
235+ exit (1 );
236+ }
237+ break ;
238+ case 7 :
239+ vacopts .min_mxid_age = atoi (optarg );
240+ if (vacopts .min_mxid_age <= 0 )
241+ {
242+ fprintf (stderr , _ ("%s: minimum multixact ID age must be at least 1\n" ),
243+ progname );
244+ exit (1 );
245+ }
246+ break ;
225247 default :
226248 fprintf (stderr , _ ("Try \"%s --help\" for more information.\n" ), progname );
227249 exit (1 );
@@ -370,6 +392,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
370392 bool failed = false;
371393 bool parallel = concurrentCons > 1 ;
372394 bool tables_listed = false;
395+ bool has_where = false;
373396 const char * stage_commands [] = {
374397 "SET default_statistics_target=1; SET vacuum_cost_delay=0;" ,
375398 "SET default_statistics_target=10; RESET vacuum_cost_delay;" ,
@@ -403,6 +426,20 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
403426 exit (1 );
404427 }
405428
429+ if (vacopts -> min_xid_age != 0 && PQserverVersion (conn ) < 90600 )
430+ {
431+ fprintf (stderr , _ ("%s: cannot use the \"%s\" option on server versions older than PostgreSQL 9.6\n" ),
432+ progname , "--min-xid-age" );
433+ exit (1 );
434+ }
435+
436+ if (vacopts -> min_mxid_age != 0 && PQserverVersion (conn ) < 90600 )
437+ {
438+ fprintf (stderr , _ ("%s: cannot use the \"%s\" option on server versions older than PostgreSQL 9.6\n" ),
439+ progname , "--min-mxid-age" );
440+ exit (1 );
441+ }
442+
406443 if (!quiet )
407444 {
408445 if (stage != ANALYZE_NO_STAGE )
@@ -477,7 +514,9 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
477514 appendPQExpBuffer (& catalog_query ,
478515 " FROM pg_catalog.pg_class c\n"
479516 " JOIN pg_catalog.pg_namespace ns"
480- " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n" );
517+ " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
518+ " LEFT JOIN pg_catalog.pg_class t"
519+ " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n" );
481520
482521 /* Used to match the tables listed by the user */
483522 if (tables_listed )
@@ -491,9 +530,43 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
491530 * processed in which case the user will know about it.
492531 */
493532 if (!tables_listed )
533+ {
494534 appendPQExpBuffer (& catalog_query , " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array["
495535 CppAsString2 (RELKIND_RELATION ) ", "
496536 CppAsString2 (RELKIND_MATVIEW ) "])\n" );
537+ has_where = true;
538+ }
539+
540+ /*
541+ * For --min-xid-age and --min-mxid-age, the age of the relation is the
542+ * greatest of the ages of the main relation and its associated TOAST
543+ * table. The commands generated by vacuumdb will also process the TOAST
544+ * table for the relation if necessary, so it does not need to be
545+ * considered separately.
546+ */
547+ if (vacopts -> min_xid_age != 0 )
548+ {
549+ appendPQExpBuffer (& catalog_query ,
550+ " %s GREATEST(pg_catalog.age(c.relfrozenxid),"
551+ " pg_catalog.age(t.relfrozenxid)) "
552+ " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
553+ " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
554+ " '0'::pg_catalog.xid\n" ,
555+ has_where ? "AND" : "WHERE" , vacopts -> min_xid_age );
556+ has_where = true;
557+ }
558+
559+ if (vacopts -> min_mxid_age != 0 )
560+ {
561+ appendPQExpBuffer (& catalog_query ,
562+ " %s GREATEST(pg_catalog.mxid_age(c.relminmxid),"
563+ " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
564+ " '%d'::pg_catalog.int4\n"
565+ " AND c.relminmxid OPERATOR(pg_catalog.!=)"
566+ " '0'::pg_catalog.xid\n" ,
567+ has_where ? "AND" : "WHERE" , vacopts -> min_mxid_age );
568+ has_where = true;
569+ }
497570
498571 /*
499572 * Execute the catalog query. We use the default search_path for this
@@ -1152,6 +1225,8 @@ help(const char *progname)
11521225 printf (_ (" -f, --full do full vacuuming\n" ));
11531226 printf (_ (" -F, --freeze freeze row transaction information\n" ));
11541227 printf (_ (" -j, --jobs=NUM use this many concurrent connections to vacuum\n" ));
1228+ printf (_ (" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n" ));
1229+ printf (_ (" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n" ));
11551230 printf (_ (" -q, --quiet don't write any messages\n" ));
11561231 printf (_ (" --skip-locked skip relations that cannot be immediately locked\n" ));
11571232 printf (_ (" -t, --table='TABLE[(COLUMNS)]' vacuum specific table(s) only\n" ));
0 commit comments