mirror of https://github.com/postgres/postgres
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
5.3 KiB
205 lines
5.3 KiB
/*-------------------------------------------------------------------------
|
|
*
|
|
* checkfiles.c
|
|
* check for stale relation files during 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: pgsql/src/backend/utils/init/checkfiles.c,v 1.2 2005/05/05 22:18:27 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/relscan.h"
|
|
#include "access/skey.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/fd.h"
|
|
#include "utils/flatfiles.h"
|
|
#include "utils/fmgroids.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.
|
|
*/
|
|
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;
|
|
RelFileNode rnode;
|
|
char *path;
|
|
|
|
/*
|
|
* 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 = sizeof(Oid);
|
|
hashctl.hash = tag_hash;
|
|
relfilenodeHash = hash_create("relfilenodeHash", 100, &hashctl,
|
|
HASH_FUNCTION | HASH_ELEM);
|
|
|
|
/* 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("table or index file \"%s\" is stale and can safely be removed",
|
|
filepath)));
|
|
pfree(filepath);
|
|
}
|
|
}
|
|
}
|
|
FreeDir(dirdesc);
|
|
pfree(path);
|
|
hash_destroy(relfilenodeHash);
|
|
}
|
|
|