It adds a new function, CleanupStaleRelFiles, that scans through the data directory and removes all table files that are not mentioned in pg_class of the corresponding database. CleanupStaleRelFiles is called after WAL recovery.
Actually, the patch doesn't currently delete the files, just issues a warning. Testing is easier if the files don't keep getting deleted :).
The patch also adds a GetTablespacePath function similar to GetDatabasePath that constructs the path to a tablespace symbolic link. commands/tablespace.c is modified to use it, in addition to the new CleanupStaleRelFiles function.
- Heikki
Index: src/backend/access/transam/xlog.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/access/transam/xlog.c,v
retrieving revision 1.181
diff -c -r1.181 xlog.c
*** src/backend/access/transam/xlog.c 12 Feb 2005 23:53:37 -0000 1.181
--- src/backend/access/transam/xlog.c 5 Mar 2005 22:33:56 -0000
***************
*** 43,48 ****
--- 43,55 ----
#include "utils/guc.h"
#include "utils/relcache.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"
/*
* This chunk of hackery attempts to determine which file sync methods
***************
*** 465,470 ****
--- 472,480 ----
static bool read_backup_label(XLogRecPtr *checkPointLoc);
static void remove_backup_label(void);
+ static void CleanupStaleRelFilesFrom(Oid tablespaceoid, Oid dboid);
+ static void CleanupStaleRelFilesFromTablespace(Oid tablespaceoid);
+ static void CleanupStaleRelFiles(void);
/*
* Insert an XLOG record having the specified RMID and info bytes,
***************
*** 4483,4488 ****
--- 4493,4500 ----
CreateCheckPoint(true, true);
+ CleanupStaleRelFiles();
+
/*
* Close down recovery environment
*/
***************
*** 5656,5658 ****
--- 5668,5852 ----
errmsg("could not remove file \"%s\":
%m",
labelfilepath)));
}
+
+ /* 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 CleanupStaleRelFiles deletes it.
+ */
+ static void
+ CleanupStaleRelFiles(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;
+
+ tablespaceoid = (Oid) strtol(de->d_name, &invalid, 10);
+ if(invalid[0] == '\0')
+ CleanupStaleRelFilesFromTablespace(tablespaceoid);
+ }
+ pfree(path);
+
+ CleanupStaleRelFilesFromTablespace(DEFAULTTABLESPACE_OID);
+ }
+
+ /* Scan a specific tablespace for stale relations */
+ static void
+ CleanupStaleRelFilesFromTablespace(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')
+ CleanupStaleRelFilesFrom(tablespaceoid, dboid);
+ }
+ 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 deleted.
+ */
+ static void
+ CleanupStaleRelFilesFrom(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, "CleanupStaleRelFiles",
+
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;
+ ResourceOwner oldowner;
+ Relation rel;
+ HeapScanDesc scan;
+ HeapTuple tuple;
+
+ /* Need a resowner to keep the heapam and buffer code happy */
+ owner = ResourceOwnerCreate(NULL, "CleanupStaleRelFiles");
+ oldowner = CurrentResourceOwner;
+ CurrentResourceOwner = owner;
+
+ rnode.spcNode = tablespaceoid;
+ rnode.dbNode = dboid;
+ rnode.relNode = RelOid_pg_class;
+ 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);
+
+ /* unlink(filepath); */
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("The file \"%s\" can be
safely deleted",
+ filepath)));
+ pfree(filepath);
+ }
+ }
+ }
+ pfree(path);
+ hash_destroy(relfilenodeHash);
+ MemoryContextDelete(mcxt);
+ }
+
Index: src/backend/catalog/catalog.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/catalog.c,v
retrieving revision 1.57
diff -c -r1.57 catalog.c
*** src/backend/catalog/catalog.c 31 Dec 2004 21:59:38 -0000 1.57
--- src/backend/catalog/catalog.c 5 Mar 2005 22:33:56 -0000
***************
*** 107,112 ****
--- 107,145 ----
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: src/backend/commands/tablespace.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/tablespace.c,v
retrieving revision 1.16
diff -c -r1.16 tablespace.c
*** src/backend/commands/tablespace.c 27 Jan 2005 23:23:55 -0000 1.16
--- src/backend/commands/tablespace.c 5 Mar 2005 22:33:56 -0000
***************
*** 342,349 ****
/*
* All seems well, create the symlink
*/
! linkloc = (char *) palloc(strlen(DataDir) + 11 + 10 + 1);
! sprintf(linkloc, "%s/pg_tblspc/%u", DataDir, tablespaceoid);
if (symlink(location, linkloc) < 0)
ereport(ERROR,
--- 342,348 ----
/*
* All seems well, create the symlink
*/
! linkloc = GetTablespacePath(tablespaceoid);
if (symlink(location, linkloc) < 0)
ereport(ERROR,
***************
*** 496,503 ****
char *subfile;
struct stat st;
! location = (char *) palloc(strlen(DataDir) + 11 + 10 + 1);
! sprintf(location, "%s/pg_tblspc/%u", DataDir, tablespaceoid);
/*
* Check if the tablespace still contains any files. We try to rmdir
--- 495,501 ----
char *subfile;
struct stat st;
! location = GetTablespacePath(tablespaceoid);
/*
* Check if the tablespace still contains any files. We try to rmdir
***************
*** 1037,1044 ****
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);
if (symlink(location, linkloc) < 0)
{
--- 1035,1041 ----
set_short_version(location);
/* Create the symlink if not already present */
! linkloc = GetTablespacePath(xlrec->ts_id);
if (symlink(location, linkloc) < 0)
{
Index: src/include/catalog/catalog.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/catalog/catalog.h,v
retrieving revision 1.30
diff -c -r1.30 catalog.h
*** src/include/catalog/catalog.h 31 Dec 2004 22:03:24 -0000 1.30
--- src/include/catalog/catalog.h 5 Mar 2005 22:33:57 -0000
***************
*** 19,24 ****
--- 19,25 ----
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);
---------------------------(end of broadcast)--------------------------- TIP 5: Have you checked our extensive FAQ?
http://www.postgresql.org/docs/faq
