Check the file system on postmaster startup and report any unreferenced
authorBruce Momjian <[email protected]>
Mon, 2 May 2005 18:26:54 +0000 (18:26 +0000)
committerBruce Momjian <[email protected]>
Mon, 2 May 2005 18:26:54 +0000 (18:26 +0000)
files in the server log.

Heikki Linnakangas

doc/src/sgml/maintenance.sgml
src/backend/access/transam/xlog.c
src/backend/catalog/catalog.c
src/backend/commands/tablespace.c
src/backend/utils/adt/misc.c
src/backend/utils/init/Makefile
src/backend/utils/init/checkfiles.c [new file with mode: 0644]
src/include/catalog/catalog.h
src/include/utils/flatfiles.h

index c8dcb84f53de048a8d2d7abd4690d915be7c9da1..17bdd6b49b4cb73d503933f484d4a948d787e879 100644 (file)
@@ -474,6 +474,23 @@ HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".
   </para>
  </sect1>
 
+ <sect1 id="check-files-after-crash">
+  <title>Check files after crash</title>
+
+  <indexterm zone="check-files-after-crash">
+   <primary>stale file</primary>
+  </indexterm>
+
+  <para>
+   <productname>PostgreSQL</productname> recovers automatically after crash
+   using the write-ahead log (see <xref linkend="wal">) and no manual 
+   operations are normally needed. However, if there was a transaction running 
+   when the crash occured that created or dropped a relation, the 
+   transaction might have left a stale file in the data directory. If this 
+   happens, you will get a notice in the log file stating which files can be 
+   deleted.
+  </para>
+ </sect1>
 
  <sect1 id="logfile-maintenance">
   <title>Log File Maintenance</title>
index e54984665e3a07c49dc26be0859de4a81b2c057c..9ea5ce57c443e749b8e34c9483c92147791d1a36 100644 (file)
@@ -43,6 +43,7 @@
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/relcache.h"
+#include "utils/flatfiles.h"
 
 
 /*
@@ -4525,6 +4526,8 @@ StartupXLOG(void)
 
                CreateCheckPoint(true, true);
 
+               CheckStaleRelFiles();
+
                /*
                 * Close down recovery environment
                 */
@@ -4536,6 +4539,12 @@ StartupXLOG(void)
                 */
                remove_backup_label();
        }
+       else
+       {
+               XLogInitRelationCache();
+               CheckStaleRelFiles();
+               XLogCloseRelationCache();
+       }
 
        /*
         * Preallocate additional log files, if wanted.
index 68c7e64188c5d851f7d58c0031ebe47db7b755fd..4229980cdea875fd40b4e3dc942258dd2524af57 100644 (file)
@@ -106,6 +106,39 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
        return path;
 }
 
+/*
+ * GetTablespacePath   - construct path to a tablespace symbolic link
+ *
+ * Result is a palloc'd string.
+ *
+ * XXX this must agree with relpath and GetDatabasePath!
+ */
+char *
+GetTablespacePath(Oid spcNode)
+{
+       int                     pathlen;
+       char       *path;
+
+       Assert(spcNode != GLOBALTABLESPACE_OID);
+
+       if (spcNode == DEFAULTTABLESPACE_OID)
+       {
+               /* The default tablespace is {datadir}/base */
+               pathlen = strlen(DataDir) + 5 + 1;
+               path = (char *) palloc(pathlen);
+               snprintf(path, pathlen, "%s/base",
+                                DataDir);
+       }
+       else
+       {
+               /* All other tablespaces have symlinks in pg_tblspc */
+               pathlen = strlen(DataDir) + 11 + OIDCHARS + 1;
+               path = (char *) palloc(pathlen);
+               snprintf(path, pathlen, "%s/pg_tblspc/%u",
+                                DataDir, spcNode);
+       }
+       return path;
+}
 
 /*
  * IsSystemRelation
index 5f4d02d7f20afe722bd39a958eca760a9173be38..49afd1aa187e6d2c020d641f03b02dbfc0ee0d32 100644 (file)
@@ -341,8 +341,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
        /*
         * All seems well, create the symlink
         */
-       linkloc = (char *) palloc(strlen(DataDir) + 11 + 10 + 1);
-       sprintf(linkloc, "%s/pg_tblspc/%u", DataDir, tablespaceoid);
+       linkloc = GetTablespacePath(tablespaceoid);
 
        if (symlink(location, linkloc) < 0)
                ereport(ERROR,
@@ -495,8 +494,7 @@ remove_tablespace_directories(Oid tablespaceoid, bool redo)
        char       *subfile;
        struct stat st;
 
-       location = (char *) palloc(strlen(DataDir) + 11 + 10 + 1);
-       sprintf(location, "%s/pg_tblspc/%u", DataDir, tablespaceoid);
+       location = GetTablespacePath(tablespaceoid);
 
        /*
         * Check if the tablespace still contains any files.  We try to rmdir
@@ -1036,8 +1034,7 @@ tblspc_redo(XLogRecPtr lsn, XLogRecord *record)
                set_short_version(location);
 
                /* Create the symlink if not already present */
-               linkloc = (char *) palloc(strlen(DataDir) + 11 + 10 + 1);
-               sprintf(linkloc, "%s/pg_tblspc/%u", DataDir, xlrec->ts_id);
+               linkloc = GetTablespacePath(xlrec->ts_id);
 
                if (symlink(location, linkloc) < 0)
                {
index 94d27425dd333bb5842b4292ff04df26866bdf9e..3ddc501f53caf3490fd1c4457cd576d44215d2bb 100644 (file)
@@ -26,6 +26,7 @@
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/catalog.h"
 
 #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
 
@@ -144,11 +145,6 @@ pg_tablespace_databases(PG_FUNCTION_ARGS)
 
                fctx = palloc(sizeof(ts_db_fctx));
 
-               /*
-                * size = path length + tablespace dirname length + 2 dir sep
-                * chars + oid + terminator
-                */
-               fctx->location = (char *) palloc(strlen(DataDir) + 11 + 10 + 1);
                if (tablespaceOid == GLOBALTABLESPACE_OID)
                {
                        fctx->dirdesc = NULL;
@@ -157,12 +153,7 @@ pg_tablespace_databases(PG_FUNCTION_ARGS)
                }
                else
                {
-                       if (tablespaceOid == DEFAULTTABLESPACE_OID)
-                               sprintf(fctx->location, "%s/base", DataDir);
-                       else
-                               sprintf(fctx->location, "%s/pg_tblspc/%u", DataDir,
-                                               tablespaceOid);
-
+                       fctx->location = GetTablespacePath(tablespaceOid);
                        fctx->dirdesc = AllocateDir(fctx->location);
 
                        if (!fctx->dirdesc)
index 275b3451bd7775c1edcd27091135f88582984c7b..6345b5778baafeed45542e5cf20af1c3c36e81e6 100644 (file)
@@ -12,7 +12,7 @@ subdir = src/backend/utils/init
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = flatfiles.o globals.o miscinit.o postinit.o
+OBJS = flatfiles.o globals.o miscinit.o postinit.o checkfiles.o
 
 all: SUBSYS.o
 
diff --git a/src/backend/utils/init/checkfiles.c b/src/backend/utils/init/checkfiles.c
new file mode 100644 (file)
index 0000000..3a1d8a3
--- /dev/null
@@ -0,0 +1,225 @@
+/*-------------------------------------------------------------------------
+ *
+ *     checkfiles.c
+ *       support to clean up stale relation files on crash recovery
+ *
+ *     If a backend crashes while in a transaction that has created or
+ *     deleted a relfilenode, a stale file can be left over in the data
+ *     directory. This file contains routines to clean up those stale
+ *     files on recovery.
+ *
+ *     This adds a 17% increase in startup cost for 100 empty databases.  bjm
+ *     One optimization would be to create a 'dirty' file on a postmaster recovery
+ *     and remove the dirty flag only when a clean startup detects no unreferenced
+ *     files, and use the 'dirty' flag to determine if we should run this on
+ *     a clean startup.
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "storage/fd.h"
+
+#include "utils/flatfiles.h"
+#include "miscadmin.h"
+#include "catalog/pg_tablespace.h"
+#include "catalog/catalog.h"
+#include "access/skey.h"
+#include "utils/fmgroids.h"
+#include "access/relscan.h"
+#include "access/heapam.h"
+#include "utils/resowner.h"
+
+static void CheckStaleRelFilesFrom(Oid tablespaceoid, Oid dboid);
+static void CheckStaleRelFilesFromTablespace(Oid tablespaceoid);
+
+/* Like AllocateDir, but ereports on failure */
+static DIR *
+AllocateDirChecked(char *path)
+{
+       DIR                *dirdesc = AllocateDir(path);
+
+       if (dirdesc == NULL)
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not open directory \"%s\": %m",
+                                               path)));
+       return dirdesc;
+}
+
+/*
+ * Scan through all tablespaces for relations left over
+ * by aborted transactions.
+ *
+ * For example, if a transaction issues
+ * BEGIN; CREATE TABLE foobar ();
+ * and then the backend crashes, the file is left in the
+ * tablespace until CheckStaleRelFiles deletes it.
+ */
+void
+CheckStaleRelFiles(void)
+{
+       DIR                *dirdesc;
+       struct dirent *de;
+       char       *path;
+       int                     pathlen;
+
+       pathlen = strlen(DataDir) + 11 + 1;
+       path = (char *) palloc(pathlen);
+       snprintf(path, pathlen, "%s/pg_tblspc/", DataDir);
+       dirdesc = AllocateDirChecked(path);
+       while ((de = readdir(dirdesc)) != NULL)
+       {
+               char       *invalid;
+               Oid                     tablespaceoid;
+
+               /* Check that the directory name looks like valid tablespace link.      */
+               tablespaceoid = (Oid) strtol(de->d_name, &invalid, 10);
+               if (invalid[0] == '\0')
+                       CheckStaleRelFilesFromTablespace(tablespaceoid);
+       }
+       FreeDir(dirdesc);
+       pfree(path);
+
+       CheckStaleRelFilesFromTablespace(DEFAULTTABLESPACE_OID);
+}
+
+/* Scan a specific tablespace for stale relations */
+static void
+CheckStaleRelFilesFromTablespace(Oid tablespaceoid)
+{
+       DIR                *dirdesc;
+       struct dirent *de;
+       char       *path;
+
+       path = GetTablespacePath(tablespaceoid);
+
+       dirdesc = AllocateDirChecked(path);
+       while ((de = readdir(dirdesc)) != NULL)
+       {
+               char       *invalid;
+               Oid                     dboid;
+
+               dboid = (Oid) strtol(de->d_name, &invalid, 10);
+               if (invalid[0] == '\0')
+                       CheckStaleRelFilesFrom(tablespaceoid, dboid);
+       }
+       FreeDir(dirdesc);
+       pfree(path);
+}
+
+/* Scan a specific database in a specific tablespace for stale relations.
+ *
+ * First, pg_class for the database is opened, and the relfilenodes of all
+ * relations mentioned there are stored in a hash table.
+ *
+ * Then the directory is scanned. Every file in the directory that's not
+ * found in pg_class (the hash table) is logged.
+ */
+static void
+CheckStaleRelFilesFrom(Oid tablespaceoid, Oid dboid)
+{
+       DIR                *dirdesc;
+       struct dirent *de;
+       HASHCTL         hashctl;
+       HTAB       *relfilenodeHash;
+       MemoryContext mcxt;
+       RelFileNode rnode;
+       char       *path;
+
+       /*
+        * We create a private memory context so that we can easily deallocate the
+        * hash table and its contents
+        */
+       mcxt = AllocSetContextCreate(TopMemoryContext, "CheckStaleRelFiles",
+                                                                ALLOCSET_DEFAULT_MINSIZE,
+                                                                ALLOCSET_DEFAULT_INITSIZE,
+                                                                ALLOCSET_DEFAULT_MAXSIZE);
+
+       hashctl.hash = tag_hash;
+
+       /*
+        * The entry contents is not used for anything, we just check if an oid is
+        * in the hash table or not.
+        */
+       hashctl.keysize = sizeof(Oid);
+       hashctl.entrysize = 1;
+       hashctl.hcxt = mcxt;
+       relfilenodeHash = hash_create("relfilenodeHash", 100, &hashctl,
+                                                                 HASH_FUNCTION
+                                                                 | HASH_ELEM | HASH_CONTEXT);
+
+       /* Read all relfilenodes from pg_class into the hash table */
+       {
+               ResourceOwner owner,
+                                       oldowner;
+               Relation        rel;
+               HeapScanDesc scan;
+               HeapTuple       tuple;
+
+               /* Need a resowner to keep the heapam and buffer code happy */
+               owner = ResourceOwnerCreate(NULL, "CheckStaleRelFiles");
+               oldowner = CurrentResourceOwner;
+               CurrentResourceOwner = owner;
+
+               rnode.spcNode = tablespaceoid;
+               rnode.dbNode = dboid;
+               rnode.relNode = RelationRelationId;
+               rel = XLogOpenRelation(true, 0, rnode);
+
+               scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
+               while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+               {
+                       Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);
+
+                       hash_search(relfilenodeHash, &classform->relfilenode,
+                                               HASH_ENTER, NULL);
+               }
+               heap_endscan(scan);
+
+               XLogCloseRelation(rnode);
+               CurrentResourceOwner = oldowner;
+               ResourceOwnerDelete(owner);
+       }
+
+       /* Scan the directory */
+       path = GetDatabasePath(dboid, tablespaceoid);
+
+       dirdesc = AllocateDirChecked(path);
+       while ((de = readdir(dirdesc)) != NULL)
+       {
+               char       *invalid;
+               Oid                     relfilenode;
+
+               relfilenode = strtol(de->d_name, &invalid, 10);
+               if (invalid[0] == '\0')
+               {
+                       /*
+                        * Filename was a valid number, check if pg_class knows about it
+                        */
+                       if (hash_search(relfilenodeHash, &relfilenode,
+                                                       HASH_FIND, NULL) == NULL)
+                       {
+                               char       *filepath;
+
+                               rnode.spcNode = tablespaceoid;
+                               rnode.dbNode = dboid;
+                               rnode.relNode = relfilenode;
+
+                               filepath = relpath(rnode);
+
+                               ereport(LOG,
+                                               (errcode_for_file_access(),
+                                                errmsg("The table or index file \"%s\" is stale and can be safely removed",
+                                                               filepath)));
+                               pfree(filepath);
+                       }
+               }
+       }
+       FreeDir(dirdesc);
+       pfree(path);
+       hash_destroy(relfilenodeHash);
+       MemoryContextDelete(mcxt);
+}
index 869cb4ad743d63664f7637e4b7e256251d5e1244..0a4e107076b2237dc92c07673114be22902f54f3 100644 (file)
@@ -19,6 +19,7 @@
 
 extern char *relpath(RelFileNode rnode);
 extern char *GetDatabasePath(Oid dbNode, Oid spcNode);
+extern char *GetTablespacePath(Oid spcNode);
 
 extern bool IsSystemRelation(Relation relation);
 extern bool IsToastRelation(Relation relation);
index 857247d41246e592f788e06c33dc4a3dc02c04d6..3601f6517b6798f082ac00bcfbf41ce002e79b39 100644 (file)
@@ -30,4 +30,7 @@ extern void AtEOSubXact_UpdateFlatFiles(bool isCommit,
 
 extern Datum flatfile_update_trigger(PG_FUNCTION_ARGS);
 
+/* from checkfiles.c */
+extern void CheckStaleRelFiles(void);
+
 #endif   /* FLATFILES_H */