mirror of https://github.com/postgres/postgres
This makes it possible to turn checksums on in a live cluster, without the previous need for dump/reload or logical replication (and to turn it off). Enabling checkusm starts a background process in the form of a launcher/worker combination that goes through the entire database and recalculates checksums on each and every page. Only when all pages have been checksummed are they fully enabled in the cluster. Any failure of the process will revert to checksums off and the process has to be started. This adds a new WAL record that indicates the state of checksums, so the process works across replicated clusters. Authors: Magnus Hagander and Daniel Gustafsson Review: Tomas Vondra, Michael Banck, Heikki Linnakangas, Andrey Borodinpull/27/merge
parent
c39e903d51
commit
1fde38beaa
@ -0,0 +1,112 @@ |
||||
<!-- |
||||
doc/src/sgml/ref/pg_verify_checksums.sgml |
||||
PostgreSQL documentation |
||||
--> |
||||
|
||||
<refentry id="pgverifychecksums"> |
||||
<indexterm zone="pgverifychecksums"> |
||||
<primary>pg_verify_checksums</primary> |
||||
</indexterm> |
||||
|
||||
<refmeta> |
||||
<refentrytitle><application>pg_verify_checksums</application></refentrytitle> |
||||
<manvolnum>1</manvolnum> |
||||
<refmiscinfo>Application</refmiscinfo> |
||||
</refmeta> |
||||
|
||||
<refnamediv> |
||||
<refname>pg_verify_checksums</refname> |
||||
<refpurpose>verify data checksums in an offline <productname>PostgreSQL</productname> database cluster</refpurpose> |
||||
</refnamediv> |
||||
|
||||
<refsynopsisdiv> |
||||
<cmdsynopsis> |
||||
<command>pg_verify_checksums</command> |
||||
<arg choice="opt"><replaceable class="parameter">option</replaceable></arg> |
||||
<arg choice="opt"><arg choice="opt"><option>-D</option></arg> <replaceable class="parameter">datadir</replaceable></arg> |
||||
</cmdsynopsis> |
||||
</refsynopsisdiv> |
||||
|
||||
<refsect1 id="r1-app-pg_verify_checksums-1"> |
||||
<title>Description</title> |
||||
<para> |
||||
<command>pg_verify_checksums</command> verifies data checksums in a PostgreSQL |
||||
cluster. It must be run against a cluster that's offline. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Options</title> |
||||
|
||||
<para> |
||||
The following command-line options are available: |
||||
|
||||
<variablelist> |
||||
|
||||
<varlistentry> |
||||
<term><option>-r <replaceable>relfilenode</replaceable></option></term> |
||||
<listitem> |
||||
<para> |
||||
Only validate checksums in the relation with specified relfilenode. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><option>-f</option></term> |
||||
<listitem> |
||||
<para> |
||||
Force check even if checksums are disabled on cluster. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><option>-d</option></term> |
||||
<listitem> |
||||
<para> |
||||
Enable debug output. Lists all checked blocks and their checksum. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><option>-V</option></term> |
||||
<term><option>--version</option></term> |
||||
<listitem> |
||||
<para> |
||||
Print the <application>pg_verify_checksums</application> version and exit. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><option>-?</option></term> |
||||
<term><option>--help</option></term> |
||||
<listitem> |
||||
<para> |
||||
Show help about <application>pg_verify_checksums</application> command line |
||||
arguments, and exit. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Notes</title> |
||||
<para> |
||||
Can only be run when the server is offline. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>See Also</title> |
||||
|
||||
<simplelist type="inline"> |
||||
<member><xref linkend="checksums"/></member> |
||||
</simplelist> |
||||
</refsect1> |
||||
|
||||
</refentry> |
||||
@ -0,0 +1,855 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* checksumhelper.c |
||||
* Background worker to walk the database and write checksums to pages |
||||
* |
||||
* When enabling data checksums on a database at initdb time, no extra process |
||||
* is required as each page is checksummed, and verified, at accesses. When |
||||
* enabling checksums on an already running cluster, which was not initialized |
||||
* with checksums, this helper worker will ensure that all pages are |
||||
* checksummed before verification of the checksums is turned on. |
||||
* |
||||
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/postmaster/checksumhelper.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/heapam.h" |
||||
#include "access/htup_details.h" |
||||
#include "access/xact.h" |
||||
#include "catalog/pg_database.h" |
||||
#include "commands/vacuum.h" |
||||
#include "common/relpath.h" |
||||
#include "miscadmin.h" |
||||
#include "pgstat.h" |
||||
#include "postmaster/bgworker.h" |
||||
#include "postmaster/bgwriter.h" |
||||
#include "postmaster/checksumhelper.h" |
||||
#include "storage/bufmgr.h" |
||||
#include "storage/checksum.h" |
||||
#include "storage/lmgr.h" |
||||
#include "storage/ipc.h" |
||||
#include "storage/procarray.h" |
||||
#include "storage/smgr.h" |
||||
#include "tcop/tcopprot.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/ps_status.h" |
||||
|
||||
|
||||
typedef enum |
||||
{ |
||||
SUCCESSFUL = 0, |
||||
ABORTED, |
||||
FAILED |
||||
} ChecksumHelperResult; |
||||
|
||||
typedef struct ChecksumHelperShmemStruct |
||||
{ |
||||
pg_atomic_flag launcher_started; |
||||
ChecksumHelperResult success; |
||||
bool process_shared_catalogs; |
||||
bool abort; |
||||
/* Parameter values set on start */ |
||||
int cost_delay; |
||||
int cost_limit; |
||||
} ChecksumHelperShmemStruct; |
||||
|
||||
/* Shared memory segment for checksumhelper */ |
||||
static ChecksumHelperShmemStruct * ChecksumHelperShmem; |
||||
|
||||
/* Bookkeeping for work to do */ |
||||
typedef struct ChecksumHelperDatabase |
||||
{ |
||||
Oid dboid; |
||||
char *dbname; |
||||
} ChecksumHelperDatabase; |
||||
|
||||
typedef struct ChecksumHelperRelation |
||||
{ |
||||
Oid reloid; |
||||
char relkind; |
||||
} ChecksumHelperRelation; |
||||
|
||||
/* Prototypes */ |
||||
static List *BuildDatabaseList(void); |
||||
static List *BuildRelationList(bool include_shared); |
||||
static List *BuildTempTableList(void); |
||||
static ChecksumHelperResult ProcessDatabase(ChecksumHelperDatabase * db); |
||||
static void launcher_cancel_handler(SIGNAL_ARGS); |
||||
|
||||
/*
|
||||
* Main entry point for checksumhelper launcher process. |
||||
*/ |
||||
bool |
||||
StartChecksumHelperLauncher(int cost_delay, int cost_limit) |
||||
{ |
||||
BackgroundWorker bgw; |
||||
BackgroundWorkerHandle *bgw_handle; |
||||
|
||||
if (ChecksumHelperShmem->abort) |
||||
{ |
||||
ereport(ERROR, |
||||
(errmsg("could not start checksumhelper: has been cancelled"))); |
||||
} |
||||
|
||||
ChecksumHelperShmem->cost_delay = cost_delay; |
||||
ChecksumHelperShmem->cost_limit = cost_limit; |
||||
|
||||
memset(&bgw, 0, sizeof(bgw)); |
||||
bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; |
||||
bgw.bgw_start_time = BgWorkerStart_RecoveryFinished; |
||||
snprintf(bgw.bgw_library_name, BGW_MAXLEN, "postgres"); |
||||
snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ChecksumHelperLauncherMain"); |
||||
snprintf(bgw.bgw_name, BGW_MAXLEN, "checksumhelper launcher"); |
||||
snprintf(bgw.bgw_type, BGW_MAXLEN, "checksumhelper launcher"); |
||||
bgw.bgw_restart_time = BGW_NEVER_RESTART; |
||||
bgw.bgw_notify_pid = MyProcPid; |
||||
bgw.bgw_main_arg = (Datum) 0; |
||||
|
||||
if (!pg_atomic_test_set_flag(&ChecksumHelperShmem->launcher_started)) |
||||
{ |
||||
/* Failed to set means somebody else started */ |
||||
ereport(ERROR, |
||||
(errmsg("could not start checksumhelper: already running"))); |
||||
} |
||||
|
||||
if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle)) |
||||
{ |
||||
pg_atomic_clear_flag(&ChecksumHelperShmem->launcher_started); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* ShutdownChecksumHelperIfRunning |
||||
* Request shutdown of the checksumhelper |
||||
* |
||||
* This does not turn off processing immediately, it signals the checksum |
||||
* process to end when done with the current block. |
||||
*/ |
||||
void |
||||
ShutdownChecksumHelperIfRunning(void) |
||||
{ |
||||
/* If the launcher isn't started, there is nothing to shut down */ |
||||
if (pg_atomic_unlocked_test_flag(&ChecksumHelperShmem->launcher_started)) |
||||
return; |
||||
|
||||
/*
|
||||
* We don't need an atomic variable for aborting, setting it multiple |
||||
* times will not change the handling. |
||||
*/ |
||||
ChecksumHelperShmem->abort = true; |
||||
} |
||||
|
||||
/*
|
||||
* ProcessSingleRelationFork |
||||
* Enable checksums in a single relation/fork. |
||||
* |
||||
* Returns true if successful, and false if *aborted*. On error, an actual |
||||
* error is raised in the lower levels. |
||||
*/ |
||||
static bool |
||||
ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy) |
||||
{ |
||||
BlockNumber numblocks = RelationGetNumberOfBlocksInFork(reln, forkNum); |
||||
BlockNumber b; |
||||
char activity[NAMEDATALEN * 2 + 128]; |
||||
|
||||
for (b = 0; b < numblocks; b++) |
||||
{ |
||||
Buffer buf = ReadBufferExtended(reln, forkNum, b, RBM_NORMAL, strategy); |
||||
|
||||
/*
|
||||
* Report to pgstat every 100 blocks (so as not to "spam") |
||||
*/ |
||||
if ((b % 100) == 0) |
||||
{ |
||||
snprintf(activity, sizeof(activity) - 1, "processing: %s.%s (%s block %d/%d)", |
||||
get_namespace_name(RelationGetNamespace(reln)), RelationGetRelationName(reln), |
||||
forkNames[forkNum], b, numblocks); |
||||
pgstat_report_activity(STATE_RUNNING, activity); |
||||
} |
||||
|
||||
/* Need to get an exclusive lock before we can flag as dirty */ |
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); |
||||
|
||||
/*
|
||||
* Mark the buffer as dirty and force a full page write. We have to |
||||
* re-write the page to WAL even if the checksum hasn't changed, |
||||
* because if there is a replica it might have a slightly different |
||||
* version of the page with an invalid checksum, caused by unlogged |
||||
* changes (e.g. hintbits) on the master happening while checksums |
||||
* were off. This can happen if there was a valid checksum on the page |
||||
* at one point in the past, so only when checksums are first on, then |
||||
* off, and then turned on again. |
||||
*/ |
||||
START_CRIT_SECTION(); |
||||
MarkBufferDirty(buf); |
||||
log_newpage_buffer(buf, false); |
||||
END_CRIT_SECTION(); |
||||
|
||||
UnlockReleaseBuffer(buf); |
||||
|
||||
/*
|
||||
* This is the only place where we check if we are asked to abort, the |
||||
* abortion will bubble up from here. |
||||
*/ |
||||
if (ChecksumHelperShmem->abort) |
||||
return false; |
||||
|
||||
vacuum_delay_point(); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* ProcessSingleRelationByOid |
||||
* Process a single relation based on oid. |
||||
* |
||||
* Returns true if successful, and false if *aborted*. On error, an actual error |
||||
* is raised in the lower levels. |
||||
*/ |
||||
static bool |
||||
ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy) |
||||
{ |
||||
Relation rel; |
||||
ForkNumber fnum; |
||||
bool aborted = false; |
||||
|
||||
StartTransactionCommand(); |
||||
|
||||
elog(DEBUG2, "Checksumhelper starting to process relation %d", relationId); |
||||
rel = try_relation_open(relationId, AccessShareLock); |
||||
if (rel == NULL) |
||||
{ |
||||
/*
|
||||
* Relation no longer exist. We consider this a success, since there |
||||
* are no pages in it that need checksums, and thus return true. |
||||
*/ |
||||
elog(DEBUG1, "Checksumhelper skipping relation %d as it no longer exists", relationId); |
||||
CommitTransactionCommand(); |
||||
pgstat_report_activity(STATE_IDLE, NULL); |
||||
return true; |
||||
} |
||||
RelationOpenSmgr(rel); |
||||
|
||||
for (fnum = 0; fnum <= MAX_FORKNUM; fnum++) |
||||
{ |
||||
if (smgrexists(rel->rd_smgr, fnum)) |
||||
{ |
||||
if (!ProcessSingleRelationFork(rel, fnum, strategy)) |
||||
{ |
||||
aborted = true; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
relation_close(rel, AccessShareLock); |
||||
elog(DEBUG2, "Checksumhelper done with relation %d: %s", |
||||
relationId, (aborted ? "aborted" : "finished")); |
||||
|
||||
CommitTransactionCommand(); |
||||
|
||||
pgstat_report_activity(STATE_IDLE, NULL); |
||||
|
||||
return !aborted; |
||||
} |
||||
|
||||
/*
|
||||
* ProcessDatabase |
||||
* Enable checksums in a single database. |
||||
* |
||||
* We do this by launching a dynamic background worker into this database, and |
||||
* waiting for it to finish. We have to do this in a separate worker, since |
||||
* each process can only be connected to one database during its lifetime. |
||||
*/ |
||||
static ChecksumHelperResult |
||||
ProcessDatabase(ChecksumHelperDatabase * db) |
||||
{ |
||||
BackgroundWorker bgw; |
||||
BackgroundWorkerHandle *bgw_handle; |
||||
BgwHandleStatus status; |
||||
pid_t pid; |
||||
char activity[NAMEDATALEN + 64]; |
||||
|
||||
ChecksumHelperShmem->success = FAILED; |
||||
|
||||
memset(&bgw, 0, sizeof(bgw)); |
||||
bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; |
||||
bgw.bgw_start_time = BgWorkerStart_RecoveryFinished; |
||||
snprintf(bgw.bgw_library_name, BGW_MAXLEN, "postgres"); |
||||
snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ChecksumHelperWorkerMain"); |
||||
snprintf(bgw.bgw_name, BGW_MAXLEN, "checksumhelper worker"); |
||||
snprintf(bgw.bgw_type, BGW_MAXLEN, "checksumhelper worker"); |
||||
bgw.bgw_restart_time = BGW_NEVER_RESTART; |
||||
bgw.bgw_notify_pid = MyProcPid; |
||||
bgw.bgw_main_arg = ObjectIdGetDatum(db->dboid); |
||||
|
||||
if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle)) |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("failed to start worker for checksumhelper in \"%s\"", |
||||
db->dbname))); |
||||
return FAILED; |
||||
} |
||||
|
||||
status = WaitForBackgroundWorkerStartup(bgw_handle, &pid); |
||||
if (status != BGWH_STARTED) |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("failed to wait for worker startup for checksumhelper in \"%s\"", |
||||
db->dbname))); |
||||
return FAILED; |
||||
} |
||||
|
||||
ereport(DEBUG1, |
||||
(errmsg("started background worker for checksums in \"%s\"", |
||||
db->dbname))); |
||||
|
||||
snprintf(activity, sizeof(activity) - 1, |
||||
"Waiting for worker in database %s (pid %d)", db->dbname, pid); |
||||
pgstat_report_activity(STATE_RUNNING, activity); |
||||
|
||||
|
||||
status = WaitForBackgroundWorkerShutdown(bgw_handle); |
||||
if (status != BGWH_STOPPED) |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("failed to wait for worker shutdown for checksumhelper in \"%s\"", |
||||
db->dbname))); |
||||
return FAILED; |
||||
} |
||||
|
||||
if (ChecksumHelperShmem->success == ABORTED) |
||||
ereport(LOG, |
||||
(errmsg("checksumhelper was aborted during processing in \"%s\"", |
||||
db->dbname))); |
||||
|
||||
ereport(DEBUG1, |
||||
(errmsg("background worker for checksums in \"%s\" completed", |
||||
db->dbname))); |
||||
|
||||
pgstat_report_activity(STATE_IDLE, NULL); |
||||
|
||||
return ChecksumHelperShmem->success; |
||||
} |
||||
|
||||
static void |
||||
launcher_exit(int code, Datum arg) |
||||
{ |
||||
ChecksumHelperShmem->abort = false; |
||||
pg_atomic_clear_flag(&ChecksumHelperShmem->launcher_started); |
||||
} |
||||
|
||||
static void |
||||
launcher_cancel_handler(SIGNAL_ARGS) |
||||
{ |
||||
ChecksumHelperShmem->abort = true; |
||||
} |
||||
|
||||
static void |
||||
WaitForAllTransactionsToFinish(void) |
||||
{ |
||||
TransactionId waitforxid; |
||||
|
||||
LWLockAcquire(XidGenLock, LW_SHARED); |
||||
waitforxid = ShmemVariableCache->nextXid; |
||||
LWLockRelease(XidGenLock); |
||||
|
||||
while (true) |
||||
{ |
||||
TransactionId oldestxid = GetOldestActiveTransactionId(); |
||||
|
||||
elog(DEBUG1, "Checking old transactions"); |
||||
if (TransactionIdPrecedes(oldestxid, waitforxid)) |
||||
{ |
||||
char activity[64]; |
||||
|
||||
/* Oldest running xid is older than us, so wait */ |
||||
snprintf(activity, sizeof(activity), "Waiting for current transactions to finish (waiting for %d)", waitforxid); |
||||
pgstat_report_activity(STATE_RUNNING, activity); |
||||
|
||||
/* Retry every 5 seconds */ |
||||
ResetLatch(MyLatch); |
||||
(void) WaitLatch(MyLatch, |
||||
WL_LATCH_SET | WL_TIMEOUT, |
||||
5000, |
||||
WAIT_EVENT_PG_SLEEP); |
||||
} |
||||
else |
||||
{ |
||||
pgstat_report_activity(STATE_IDLE, NULL); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
ChecksumHelperLauncherMain(Datum arg) |
||||
{ |
||||
List *DatabaseList; |
||||
List *remaining = NIL; |
||||
ListCell *lc, |
||||
*lc2; |
||||
List *CurrentDatabases = NIL; |
||||
bool found_failed = false; |
||||
|
||||
on_shmem_exit(launcher_exit, 0); |
||||
|
||||
ereport(DEBUG1, |
||||
(errmsg("checksumhelper launcher started"))); |
||||
|
||||
pqsignal(SIGTERM, die); |
||||
pqsignal(SIGINT, launcher_cancel_handler); |
||||
|
||||
BackgroundWorkerUnblockSignals(); |
||||
|
||||
init_ps_display(pgstat_get_backend_desc(B_CHECKSUMHELPER_LAUNCHER), "", "", ""); |
||||
|
||||
/*
|
||||
* Initialize a connection to shared catalogs only. |
||||
*/ |
||||
BackgroundWorkerInitializeConnection(NULL, NULL, 0); |
||||
|
||||
/*
|
||||
* Set up so first run processes shared catalogs, but not once in every |
||||
* db. |
||||
*/ |
||||
ChecksumHelperShmem->process_shared_catalogs = true; |
||||
|
||||
/*
|
||||
* Wait for all existing transactions to finish. This will make sure that |
||||
* we can see all tables all databases, so we don't miss any. Anything |
||||
* created after this point is known to have checksums on all pages |
||||
* already, so we don't have to care about those. |
||||
*/ |
||||
WaitForAllTransactionsToFinish(); |
||||
|
||||
/*
|
||||
* Create a database list. We don't need to concern ourselves with |
||||
* rebuilding this list during runtime since any database created after |
||||
* this process started will be running with checksums turned on from the |
||||
* start. |
||||
*/ |
||||
DatabaseList = BuildDatabaseList(); |
||||
|
||||
/*
|
||||
* If there are no databases at all to checksum, we can exit immediately |
||||
* as there is no work to do. |
||||
*/ |
||||
if (DatabaseList == NIL || list_length(DatabaseList) == 0) |
||||
return; |
||||
|
||||
foreach(lc, DatabaseList) |
||||
{ |
||||
ChecksumHelperDatabase *db = (ChecksumHelperDatabase *) lfirst(lc); |
||||
ChecksumHelperResult processing; |
||||
|
||||
processing = ProcessDatabase(db); |
||||
|
||||
if (processing == SUCCESSFUL) |
||||
{ |
||||
pfree(db->dbname); |
||||
pfree(db); |
||||
|
||||
if (ChecksumHelperShmem->process_shared_catalogs) |
||||
|
||||
/*
|
||||
* Now that one database has completed shared catalogs, we |
||||
* don't have to process them again. |
||||
*/ |
||||
ChecksumHelperShmem->process_shared_catalogs = false; |
||||
} |
||||
else if (processing == FAILED) |
||||
{ |
||||
/*
|
||||
* Put failed databases on the remaining list. |
||||
*/ |
||||
remaining = lappend(remaining, db); |
||||
} |
||||
else |
||||
/* aborted */ |
||||
return; |
||||
} |
||||
list_free(DatabaseList); |
||||
|
||||
/*
|
||||
* remaining now has all databases not yet processed. This can be because |
||||
* they failed for some reason, or because the database was dropped |
||||
* between us getting the database list and trying to process it. Get a |
||||
* fresh list of databases to detect the second case where the database |
||||
* was dropped before we had started processing it. If a database still |
||||
* exists, but enabling checksums failed then we fail the entire |
||||
* checksumming process and exit with an error. |
||||
*/ |
||||
CurrentDatabases = BuildDatabaseList(); |
||||
|
||||
foreach(lc, remaining) |
||||
{ |
||||
ChecksumHelperDatabase *db = (ChecksumHelperDatabase *) lfirst(lc); |
||||
bool found = false; |
||||
|
||||
foreach(lc2, CurrentDatabases) |
||||
{ |
||||
ChecksumHelperDatabase *db2 = (ChecksumHelperDatabase *) lfirst(lc2); |
||||
|
||||
if (db->dboid == db2->dboid) |
||||
{ |
||||
found = true; |
||||
ereport(WARNING, |
||||
(errmsg("failed to enable checksums in \"%s\"", |
||||
db->dbname))); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (found) |
||||
found_failed = true; |
||||
else |
||||
{ |
||||
ereport(LOG, |
||||
(errmsg("database \"%s\" has been dropped, skipping", |
||||
db->dbname))); |
||||
} |
||||
|
||||
pfree(db->dbname); |
||||
pfree(db); |
||||
} |
||||
list_free(remaining); |
||||
|
||||
/* Free the extra list of databases */ |
||||
foreach(lc, CurrentDatabases) |
||||
{ |
||||
ChecksumHelperDatabase *db = (ChecksumHelperDatabase *) lfirst(lc); |
||||
|
||||
pfree(db->dbname); |
||||
pfree(db); |
||||
} |
||||
list_free(CurrentDatabases); |
||||
|
||||
if (found_failed) |
||||
{ |
||||
/* Disable checksums on cluster, because we failed */ |
||||
SetDataChecksumsOff(); |
||||
ereport(ERROR, |
||||
(errmsg("checksumhelper failed to enable checksums in all databases, aborting"))); |
||||
} |
||||
|
||||
/*
|
||||
* Force a checkpoint to get everything out to disk. |
||||
*/ |
||||
RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_IMMEDIATE); |
||||
|
||||
/*
|
||||
* Everything has been processed, so flag checksums enabled. |
||||
*/ |
||||
SetDataChecksumsOn(); |
||||
|
||||
ereport(LOG, |
||||
(errmsg("checksums enabled, checksumhelper launcher shutting down"))); |
||||
} |
||||
|
||||
/*
|
||||
* ChecksumHelperShmemSize |
||||
* Compute required space for checksumhelper-related shared memory |
||||
*/ |
||||
Size |
||||
ChecksumHelperShmemSize(void) |
||||
{ |
||||
Size size; |
||||
|
||||
size = sizeof(ChecksumHelperShmemStruct); |
||||
size = MAXALIGN(size); |
||||
|
||||
return size; |
||||
} |
||||
|
||||
/*
|
||||
* ChecksumHelperShmemInit |
||||
* Allocate and initialize checksumhelper-related shared memory |
||||
*/ |
||||
void |
||||
ChecksumHelperShmemInit(void) |
||||
{ |
||||
bool found; |
||||
|
||||
ChecksumHelperShmem = (ChecksumHelperShmemStruct *) |
||||
ShmemInitStruct("ChecksumHelper Data", |
||||
ChecksumHelperShmemSize(), |
||||
&found); |
||||
|
||||
if (!found) |
||||
{ |
||||
MemSet(ChecksumHelperShmem, 0, ChecksumHelperShmemSize()); |
||||
pg_atomic_init_flag(&ChecksumHelperShmem->launcher_started); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* BuildDatabaseList |
||||
* Compile a list of all currently available databases in the cluster |
||||
* |
||||
* This creates the list of databases for the checksumhelper workers to add |
||||
* checksums to. |
||||
*/ |
||||
static List * |
||||
BuildDatabaseList(void) |
||||
{ |
||||
List *DatabaseList = NIL; |
||||
Relation rel; |
||||
HeapScanDesc scan; |
||||
HeapTuple tup; |
||||
MemoryContext ctx = CurrentMemoryContext; |
||||
MemoryContext oldctx; |
||||
|
||||
StartTransactionCommand(); |
||||
|
||||
rel = heap_open(DatabaseRelationId, AccessShareLock); |
||||
scan = heap_beginscan_catalog(rel, 0, NULL); |
||||
|
||||
while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection))) |
||||
{ |
||||
Form_pg_database pgdb = (Form_pg_database) GETSTRUCT(tup); |
||||
ChecksumHelperDatabase *db; |
||||
|
||||
oldctx = MemoryContextSwitchTo(ctx); |
||||
|
||||
db = (ChecksumHelperDatabase *) palloc(sizeof(ChecksumHelperDatabase)); |
||||
|
||||
db->dboid = HeapTupleGetOid(tup); |
||||
db->dbname = pstrdup(NameStr(pgdb->datname)); |
||||
|
||||
DatabaseList = lappend(DatabaseList, db); |
||||
|
||||
MemoryContextSwitchTo(oldctx); |
||||
} |
||||
|
||||
heap_endscan(scan); |
||||
heap_close(rel, AccessShareLock); |
||||
|
||||
CommitTransactionCommand(); |
||||
|
||||
return DatabaseList; |
||||
} |
||||
|
||||
/*
|
||||
* BuildRelationList |
||||
* Compile a list of all relations in the database |
||||
* |
||||
* If shared is true, both shared relations and local ones are returned, else |
||||
* all non-shared relations are returned. |
||||
* Temp tables are not included. |
||||
*/ |
||||
static List * |
||||
BuildRelationList(bool include_shared) |
||||
{ |
||||
List *RelationList = NIL; |
||||
Relation rel; |
||||
HeapScanDesc scan; |
||||
HeapTuple tup; |
||||
MemoryContext ctx = CurrentMemoryContext; |
||||
MemoryContext oldctx; |
||||
|
||||
StartTransactionCommand(); |
||||
|
||||
rel = heap_open(RelationRelationId, AccessShareLock); |
||||
scan = heap_beginscan_catalog(rel, 0, NULL); |
||||
|
||||
while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection))) |
||||
{ |
||||
Form_pg_class pgc = (Form_pg_class) GETSTRUCT(tup); |
||||
ChecksumHelperRelation *relentry; |
||||
|
||||
if (pgc->relpersistence == 't') |
||||
continue; |
||||
|
||||
if (pgc->relisshared && !include_shared) |
||||
continue; |
||||
|
||||
/*
|
||||
* Foreign tables have by definition no local storage that can be |
||||
* checksummed, so skip. |
||||
*/ |
||||
if (pgc->relkind == RELKIND_FOREIGN_TABLE) |
||||
continue; |
||||
|
||||
oldctx = MemoryContextSwitchTo(ctx); |
||||
relentry = (ChecksumHelperRelation *) palloc(sizeof(ChecksumHelperRelation)); |
||||
|
||||
relentry->reloid = HeapTupleGetOid(tup); |
||||
relentry->relkind = pgc->relkind; |
||||
|
||||
RelationList = lappend(RelationList, relentry); |
||||
|
||||
MemoryContextSwitchTo(oldctx); |
||||
} |
||||
|
||||
heap_endscan(scan); |
||||
heap_close(rel, AccessShareLock); |
||||
|
||||
CommitTransactionCommand(); |
||||
|
||||
return RelationList; |
||||
} |
||||
|
||||
/*
|
||||
* BuildTempTableList |
||||
* Compile a list of all temporary tables in database |
||||
* |
||||
* Returns a List of oids. |
||||
*/ |
||||
static List * |
||||
BuildTempTableList(void) |
||||
{ |
||||
List *RelationList = NIL; |
||||
Relation rel; |
||||
HeapScanDesc scan; |
||||
HeapTuple tup; |
||||
MemoryContext ctx = CurrentMemoryContext; |
||||
MemoryContext oldctx; |
||||
|
||||
StartTransactionCommand(); |
||||
|
||||
rel = heap_open(RelationRelationId, AccessShareLock); |
||||
scan = heap_beginscan_catalog(rel, 0, NULL); |
||||
|
||||
while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection))) |
||||
{ |
||||
Form_pg_class pgc = (Form_pg_class) GETSTRUCT(tup); |
||||
|
||||
if (pgc->relpersistence != 't') |
||||
continue; |
||||
|
||||
oldctx = MemoryContextSwitchTo(ctx); |
||||
RelationList = lappend_oid(RelationList, HeapTupleGetOid(tup)); |
||||
MemoryContextSwitchTo(oldctx); |
||||
} |
||||
|
||||
heap_endscan(scan); |
||||
heap_close(rel, AccessShareLock); |
||||
|
||||
CommitTransactionCommand(); |
||||
|
||||
return RelationList; |
||||
} |
||||
|
||||
/*
|
||||
* Main function for enabling checksums in a single database |
||||
*/ |
||||
void |
||||
ChecksumHelperWorkerMain(Datum arg) |
||||
{ |
||||
Oid dboid = DatumGetObjectId(arg); |
||||
List *RelationList = NIL; |
||||
List *InitialTempTableList = NIL; |
||||
ListCell *lc; |
||||
BufferAccessStrategy strategy; |
||||
bool aborted = false; |
||||
|
||||
pqsignal(SIGTERM, die); |
||||
|
||||
BackgroundWorkerUnblockSignals(); |
||||
|
||||
init_ps_display(pgstat_get_backend_desc(B_CHECKSUMHELPER_WORKER), "", "", ""); |
||||
|
||||
ereport(DEBUG1, |
||||
(errmsg("checksum worker starting for database oid %d", dboid))); |
||||
|
||||
BackgroundWorkerInitializeConnectionByOid(dboid, InvalidOid, BGWORKER_BYPASS_ALLOWCONN); |
||||
|
||||
/*
|
||||
* Get a list of all temp tables present as we start in this database. We |
||||
* need to wait until they are all gone until we are done, since we cannot |
||||
* access those files and modify them. |
||||
*/ |
||||
InitialTempTableList = BuildTempTableList(); |
||||
|
||||
/*
|
||||
* Enable vacuum cost delay, if any. |
||||
*/ |
||||
VacuumCostDelay = ChecksumHelperShmem->cost_delay; |
||||
VacuumCostLimit = ChecksumHelperShmem->cost_limit; |
||||
VacuumCostActive = (VacuumCostDelay > 0); |
||||
VacuumCostBalance = 0; |
||||
VacuumPageHit = 0; |
||||
VacuumPageMiss = 0; |
||||
VacuumPageDirty = 0; |
||||
|
||||
/*
|
||||
* Create and set the vacuum strategy as our buffer strategy. |
||||
*/ |
||||
strategy = GetAccessStrategy(BAS_VACUUM); |
||||
|
||||
RelationList = BuildRelationList(ChecksumHelperShmem->process_shared_catalogs); |
||||
foreach(lc, RelationList) |
||||
{ |
||||
ChecksumHelperRelation *rel = (ChecksumHelperRelation *) lfirst(lc); |
||||
|
||||
if (!ProcessSingleRelationByOid(rel->reloid, strategy)) |
||||
{ |
||||
aborted = true; |
||||
break; |
||||
} |
||||
} |
||||
list_free_deep(RelationList); |
||||
|
||||
if (aborted) |
||||
{ |
||||
ChecksumHelperShmem->success = ABORTED; |
||||
ereport(DEBUG1, |
||||
(errmsg("checksum worker aborted in database oid %d", dboid))); |
||||
return; |
||||
} |
||||
|
||||
/*
|
||||
* Wait for all temp tables that existed when we started to go away. This |
||||
* is necessary since we cannot "reach" them to enable checksums. Any temp |
||||
* tables created after we started will already have checksums in them |
||||
* (due to the inprogress state), so those are safe. |
||||
*/ |
||||
while (true) |
||||
{ |
||||
List *CurrentTempTables; |
||||
ListCell *lc; |
||||
int numleft; |
||||
char activity[64]; |
||||
|
||||
CurrentTempTables = BuildTempTableList(); |
||||
numleft = 0; |
||||
foreach(lc, InitialTempTableList) |
||||
{ |
||||
if (list_member_oid(CurrentTempTables, lfirst_oid(lc))) |
||||
numleft++; |
||||
} |
||||
list_free(CurrentTempTables); |
||||
|
||||
if (numleft == 0) |
||||
break; |
||||
|
||||
/* At least one temp table left to wait for */ |
||||
snprintf(activity, sizeof(activity), "Waiting for %d temp tables to be removed", numleft); |
||||
pgstat_report_activity(STATE_RUNNING, activity); |
||||
|
||||
/* Retry every 5 seconds */ |
||||
ResetLatch(MyLatch); |
||||
(void) WaitLatch(MyLatch, |
||||
WL_LATCH_SET | WL_TIMEOUT, |
||||
5000, |
||||
WAIT_EVENT_PG_SLEEP); |
||||
} |
||||
|
||||
list_free(InitialTempTableList); |
||||
|
||||
ChecksumHelperShmem->success = SUCCESSFUL; |
||||
ereport(DEBUG1, |
||||
(errmsg("checksum worker completed in database oid %d", dboid))); |
||||
} |
||||
@ -0,0 +1 @@ |
||||
/pg_verify_checksums |
||||
@ -0,0 +1,36 @@ |
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Makefile for src/bin/pg_verify_checksums
|
||||
#
|
||||
# Copyright (c) 1998-2018, PostgreSQL Global Development Group
|
||||
#
|
||||
# src/bin/pg_verify_checksums/Makefile
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
PGFILEDESC = "pg_verify_checksums - verify data checksums in an offline cluster"
|
||||
PGAPPICON=win32
|
||||
|
||||
subdir = src/bin/pg_verify_checksums
|
||||
top_builddir = ../../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
|
||||
OBJS= pg_verify_checksums.o $(WIN32RES)
|
||||
|
||||
all: pg_verify_checksums |
||||
|
||||
pg_verify_checksums: $(OBJS) | submake-libpgport |
||||
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
|
||||
|
||||
install: all installdirs |
||||
$(INSTALL_PROGRAM) pg_verify_checksums$(X) '$(DESTDIR)$(bindir)/pg_verify_checksums$(X)'
|
||||
|
||||
installdirs: |
||||
$(MKDIR_P) '$(DESTDIR)$(bindir)'
|
||||
|
||||
uninstall: |
||||
rm -f '$(DESTDIR)$(bindir)/pg_verify_checksums$(X)'
|
||||
|
||||
clean distclean maintainer-clean: |
||||
rm -f pg_verify_checksums$(X) $(OBJS)
|
||||
rm -rf tmp_check
|
||||
@ -0,0 +1,315 @@ |
||||
/*
|
||||
* pg_verify_checksums |
||||
* |
||||
* Verifies page level checksums in an offline cluster |
||||
* |
||||
* Copyright (c) 2010-2018, PostgreSQL Global Development Group |
||||
* |
||||
* src/bin/pg_verify_checksums/pg_verify_checksums.c |
||||
*/ |
||||
|
||||
#define FRONTEND 1 |
||||
|
||||
#include "postgres.h" |
||||
#include "catalog/pg_control.h" |
||||
#include "common/controldata_utils.h" |
||||
#include "storage/bufpage.h" |
||||
#include "storage/checksum.h" |
||||
#include "storage/checksum_impl.h" |
||||
|
||||
#include <sys/stat.h> |
||||
#include <dirent.h> |
||||
#include <unistd.h> |
||||
|
||||
#include "pg_getopt.h" |
||||
|
||||
|
||||
static int64 files = 0; |
||||
static int64 blocks = 0; |
||||
static int64 badblocks = 0; |
||||
static ControlFileData *ControlFile; |
||||
|
||||
static char *only_relfilenode = NULL; |
||||
static bool debug = false; |
||||
|
||||
static const char *progname; |
||||
|
||||
static void |
||||
usage() |
||||
{ |
||||
printf(_("%s verifies page level checksums in offline PostgreSQL database cluster.\n\n"), progname); |
||||
printf(_("Usage:\n")); |
||||
printf(_(" %s [OPTION] [DATADIR]\n"), progname); |
||||
printf(_("\nOptions:\n")); |
||||
printf(_(" [-D] DATADIR data directory\n")); |
||||
printf(_(" -f, force check even if checksums are disabled\n")); |
||||
printf(_(" -r relfilenode check only relation with specified relfilenode\n")); |
||||
printf(_(" -d debug output, listing all checked blocks\n")); |
||||
printf(_(" -V, --version output version information, then exit\n")); |
||||
printf(_(" -?, --help show this help, then exit\n")); |
||||
printf(_("\nIf no data directory (DATADIR) is specified, " |
||||
"the environment variable PGDATA\nis used.\n\n")); |
||||
printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n")); |
||||
} |
||||
|
||||
static const char *skip[] = { |
||||
"pg_control", |
||||
"pg_filenode.map", |
||||
"pg_internal.init", |
||||
"PG_VERSION", |
||||
NULL, |
||||
}; |
||||
|
||||
static bool |
||||
skipfile(char *fn) |
||||
{ |
||||
const char **f; |
||||
|
||||
if (strcmp(fn, ".") == 0 || |
||||
strcmp(fn, "..") == 0) |
||||
return true; |
||||
|
||||
for (f = skip; *f; f++) |
||||
if (strcmp(*f, fn) == 0) |
||||
return true; |
||||
return false; |
||||
} |
||||
|
||||
static void |
||||
scan_file(char *fn, int segmentno) |
||||
{ |
||||
char buf[BLCKSZ]; |
||||
PageHeader header = (PageHeader) buf; |
||||
int f; |
||||
int blockno; |
||||
|
||||
f = open(fn, 0); |
||||
if (f < 0) |
||||
{ |
||||
fprintf(stderr, _("%s: could not open file \"%s\": %m\n"), progname, fn); |
||||
exit(1); |
||||
} |
||||
|
||||
files++; |
||||
|
||||
for (blockno = 0;; blockno++) |
||||
{ |
||||
uint16 csum; |
||||
int r = read(f, buf, BLCKSZ); |
||||
|
||||
if (r == 0) |
||||
break; |
||||
if (r != BLCKSZ) |
||||
{ |
||||
fprintf(stderr, _("%s: short read of block %d in file \"%s\", got only %d bytes\n"), |
||||
progname, blockno, fn, r); |
||||
exit(1); |
||||
} |
||||
blocks++; |
||||
|
||||
csum = pg_checksum_page(buf, blockno + segmentno * RELSEG_SIZE); |
||||
if (csum != header->pd_checksum) |
||||
{ |
||||
if (ControlFile->data_checksum_version == PG_DATA_CHECKSUM_VERSION) |
||||
fprintf(stderr, _("%s: checksum verification failed in file \"%s\", block %d: calculated checksum %X but expected %X\n"), |
||||
progname, fn, blockno, csum, header->pd_checksum); |
||||
badblocks++; |
||||
} |
||||
else if (debug) |
||||
fprintf(stderr, _("%s: checksum verified in file \"%s\", block %d: %X\n"), |
||||
progname, fn, blockno, csum); |
||||
} |
||||
|
||||
close(f); |
||||
} |
||||
|
||||
static void |
||||
scan_directory(char *basedir, char *subdir) |
||||
{ |
||||
char path[MAXPGPATH]; |
||||
DIR *dir; |
||||
struct dirent *de; |
||||
|
||||
snprintf(path, MAXPGPATH, "%s/%s", basedir, subdir); |
||||
dir = opendir(path); |
||||
if (!dir) |
||||
{ |
||||
fprintf(stderr, _("%s: could not open directory \"%s\": %m\n"), |
||||
progname, path); |
||||
exit(1); |
||||
} |
||||
while ((de = readdir(dir)) != NULL) |
||||
{ |
||||
char fn[MAXPGPATH]; |
||||
struct stat st; |
||||
|
||||
if (skipfile(de->d_name)) |
||||
continue; |
||||
|
||||
snprintf(fn, MAXPGPATH, "%s/%s", path, de->d_name); |
||||
if (lstat(fn, &st) < 0) |
||||
{ |
||||
fprintf(stderr, _("%s: could not stat file \"%s\": %m\n"), |
||||
progname, fn); |
||||
exit(1); |
||||
} |
||||
if (S_ISREG(st.st_mode)) |
||||
{ |
||||
char *forkpath, |
||||
*segmentpath; |
||||
int segmentno = 0; |
||||
|
||||
/*
|
||||
* Cut off at the segment boundary (".") to get the segment number |
||||
* in order to mix it into the checksum. Then also cut off at the |
||||
* fork boundary, to get the relfilenode the file belongs to for |
||||
* filtering. |
||||
*/ |
||||
segmentpath = strchr(de->d_name, '.'); |
||||
if (segmentpath != NULL) |
||||
{ |
||||
*segmentpath++ = '\0'; |
||||
segmentno = atoi(segmentpath); |
||||
if (segmentno == 0) |
||||
{ |
||||
fprintf(stderr, _("%s: invalid segment number %d in filename \"%s\"\n"), |
||||
progname, segmentno, fn); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
forkpath = strchr(de->d_name, '_'); |
||||
if (forkpath != NULL) |
||||
*forkpath++ = '\0'; |
||||
|
||||
if (only_relfilenode && strcmp(only_relfilenode, de->d_name) != 0) |
||||
/* Relfilenode not to be included */ |
||||
continue; |
||||
|
||||
scan_file(fn, segmentno); |
||||
} |
||||
else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) |
||||
scan_directory(path, de->d_name); |
||||
} |
||||
closedir(dir); |
||||
} |
||||
|
||||
int |
||||
main(int argc, char *argv[]) |
||||
{ |
||||
char *DataDir = NULL; |
||||
bool force = false; |
||||
int c; |
||||
bool crc_ok; |
||||
|
||||
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_verify_checksums")); |
||||
|
||||
progname = get_progname(argv[0]); |
||||
|
||||
if (argc > 1) |
||||
{ |
||||
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) |
||||
{ |
||||
usage(); |
||||
exit(0); |
||||
} |
||||
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) |
||||
{ |
||||
puts("pg_verify_checksums (PostgreSQL) " PG_VERSION); |
||||
exit(0); |
||||
} |
||||
} |
||||
|
||||
while ((c = getopt(argc, argv, "D:fr:d")) != -1) |
||||
{ |
||||
switch (c) |
||||
{ |
||||
case 'd': |
||||
debug = true; |
||||
break; |
||||
case 'D': |
||||
DataDir = optarg; |
||||
break; |
||||
case 'f': |
||||
force = true; |
||||
break; |
||||
case 'r': |
||||
if (atoi(optarg) <= 0) |
||||
{ |
||||
fprintf(stderr, _("%s: invalid relfilenode: %s\n"), progname, optarg); |
||||
exit(1); |
||||
} |
||||
only_relfilenode = pstrdup(optarg); |
||||
break; |
||||
default: |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
if (DataDir == NULL) |
||||
{ |
||||
if (optind < argc) |
||||
DataDir = argv[optind++]; |
||||
else |
||||
DataDir = getenv("PGDATA"); |
||||
|
||||
/* If no DataDir was specified, and none could be found, error out */ |
||||
if (DataDir == NULL) |
||||
{ |
||||
fprintf(stderr, _("%s: no data directory specified\n"), progname); |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
/* Complain if any arguments remain */ |
||||
if (optind < argc) |
||||
{ |
||||
fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"), |
||||
progname, argv[optind]); |
||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||
progname); |
||||
exit(1); |
||||
} |
||||
|
||||
/* Check if cluster is running */ |
||||
ControlFile = get_controlfile(DataDir, progname, &crc_ok); |
||||
if (!crc_ok) |
||||
{ |
||||
fprintf(stderr, _("%s: pg_control CRC value is incorrect.\n"), progname); |
||||
exit(1); |
||||
} |
||||
|
||||
if (ControlFile->state != DB_SHUTDOWNED && |
||||
ControlFile->state != DB_SHUTDOWNED_IN_RECOVERY) |
||||
{ |
||||
fprintf(stderr, _("%s: cluster must be shut down to verify checksums.\n"), progname); |
||||
exit(1); |
||||
} |
||||
|
||||
if (ControlFile->data_checksum_version == 0 && !force) |
||||
{ |
||||
fprintf(stderr, _("%s: data checksums are not enabled in cluster.\n"), progname); |
||||
exit(1); |
||||
} |
||||
|
||||
/* Scan all files */ |
||||
scan_directory(DataDir, "global"); |
||||
scan_directory(DataDir, "base"); |
||||
scan_directory(DataDir, "pg_tblspc"); |
||||
|
||||
printf(_("Checksum scan completed\n")); |
||||
printf(_("Data checksum version: %d\n"), ControlFile->data_checksum_version); |
||||
printf(_("Files scanned: %" INT64_MODIFIER "d\n"), files); |
||||
printf(_("Blocks scanned: %" INT64_MODIFIER "d\n"), blocks); |
||||
if (ControlFile->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_VERSION) |
||||
printf(_("Blocks left in progress: %" INT64_MODIFIER "d\n"), badblocks); |
||||
else |
||||
printf(_("Bad checksums: %" INT64_MODIFIER "d\n"), badblocks); |
||||
|
||||
if (badblocks > 0) |
||||
return 1; |
||||
|
||||
return 0; |
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* checksumhelper.h |
||||
* header file for checksum helper background worker |
||||
* |
||||
* |
||||
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* src/include/postmaster/checksumhelper.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef CHECKSUMHELPER_H |
||||
#define CHECKSUMHELPER_H |
||||
|
||||
/* Shared memory */ |
||||
extern Size ChecksumHelperShmemSize(void); |
||||
extern void ChecksumHelperShmemInit(void); |
||||
|
||||
/* Start the background processes for enabling checksums */ |
||||
bool StartChecksumHelperLauncher(int cost_delay, int cost_limit); |
||||
|
||||
/* Shutdown the background processes, if any */ |
||||
void ShutdownChecksumHelperIfRunning(void); |
||||
|
||||
/* Background worker entrypoints */ |
||||
void ChecksumHelperLauncherMain(Datum arg); |
||||
void ChecksumHelperWorkerMain(Datum arg); |
||||
|
||||
#endif /* CHECKSUMHELPER_H */ |
||||
@ -0,0 +1,2 @@ |
||||
# Generated by test suite |
||||
/tmp_check/ |
||||
@ -0,0 +1,24 @@ |
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Makefile for src/test/checksum
|
||||
#
|
||||
# Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
|
||||
# Portions Copyright (c) 1994, Regents of the University of California
|
||||
#
|
||||
# src/test/checksum/Makefile
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
subdir = src/test/checksum
|
||||
top_builddir = ../../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
|
||||
check: |
||||
$(prove_check)
|
||||
|
||||
installcheck: |
||||
$(prove_installcheck)
|
||||
|
||||
clean distclean maintainer-clean: |
||||
rm -rf tmp_check
|
||||
|
||||
@ -0,0 +1,22 @@ |
||||
src/test/checksum/README |
||||
|
||||
Regression tests for data checksums |
||||
=================================== |
||||
|
||||
This directory contains a test suite for enabling data checksums |
||||
in a running cluster with streaming replication. |
||||
|
||||
Running the tests |
||||
================= |
||||
|
||||
make check |
||||
|
||||
or |
||||
|
||||
make installcheck |
||||
|
||||
NOTE: This creates a temporary installation (in the case of "check"), |
||||
with multiple nodes, be they master or standby(s) for the purpose of |
||||
the tests. |
||||
|
||||
NOTE: This requires the --enable-tap-tests argument to configure. |
||||
@ -0,0 +1,101 @@ |
||||
# Test suite for testing enabling data checksums with streaming replication |
||||
use strict; |
||||
use warnings; |
||||
use PostgresNode; |
||||
use TestLib; |
||||
use Test::More tests => 10; |
||||
|
||||
my $MAX_TRIES = 30; |
||||
|
||||
# Initialize master node |
||||
my $node_master = get_new_node('master'); |
||||
$node_master->init(allows_streaming => 1); |
||||
$node_master->start; |
||||
my $backup_name = 'my_backup'; |
||||
|
||||
# Take backup |
||||
$node_master->backup($backup_name); |
||||
|
||||
# Create streaming standby linking to master |
||||
my $node_standby_1 = get_new_node('standby_1'); |
||||
$node_standby_1->init_from_backup($node_master, $backup_name, |
||||
has_streaming => 1); |
||||
$node_standby_1->start; |
||||
|
||||
# Create some content on master to have un-checksummed data in the cluster |
||||
$node_master->safe_psql('postgres', |
||||
"CREATE TABLE t AS SELECT generate_series(1,10000) AS a;"); |
||||
|
||||
# Wait for standbys to catch up |
||||
$node_master->wait_for_catchup($node_standby_1, 'replay', |
||||
$node_master->lsn('insert')); |
||||
|
||||
# Check that checksums are turned off |
||||
my $result = $node_master->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
is($result, "off", 'ensure checksums are turned off on master'); |
||||
|
||||
$result = $node_standby_1->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
is($result, "off", 'ensure checksums are turned off on standby_1'); |
||||
|
||||
# Enable checksums for the cluster |
||||
$node_master->safe_psql('postgres', "SELECT pg_enable_data_checksums();"); |
||||
|
||||
# Ensure that the master has switched to inprogress immediately |
||||
$result = $node_master->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
is($result, "inprogress", 'ensure checksums are in progress on master'); |
||||
|
||||
# Wait for checksum enable to be replayed |
||||
$node_master->wait_for_catchup($node_standby_1, 'replay'); |
||||
|
||||
# Ensure that the standby has switched to inprogress |
||||
$result = $node_standby_1->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
is($result, "inprogress", 'ensure checksums are in progress on standby_1'); |
||||
|
||||
# Insert some more data which should be checksummed on INSERT |
||||
$node_master->safe_psql('postgres', |
||||
"INSERT INTO t VALUES (generate_series(1,10000));"); |
||||
|
||||
# Wait for checksums enabled on the master |
||||
for (my $i = 0; $i < $MAX_TRIES; $i++) |
||||
{ |
||||
$result = $node_master->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
last if ($result eq 'on'); |
||||
sleep(1); |
||||
} |
||||
is ($result, "on", 'ensure checksums are enabled on master'); |
||||
|
||||
# Wait for checksums enabled on the standby |
||||
for (my $i = 0; $i < $MAX_TRIES; $i++) |
||||
{ |
||||
$result = $node_standby_1->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
last if ($result eq 'on'); |
||||
sleep(1); |
||||
} |
||||
is ($result, "on", 'ensure checksums are enabled on standby'); |
||||
|
||||
$result = $node_master->safe_psql('postgres', "SELECT count(a) FROM t"); |
||||
is ($result, "20000", 'ensure we can safely read all data with checksums'); |
||||
|
||||
# Disable checksums and ensure it's propagated to standby and that we can |
||||
# still read all data |
||||
$node_master->safe_psql('postgres', "SELECT pg_disable_data_checksums();"); |
||||
$result = $node_master->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
is($result, "off", 'ensure checksums are in progress on master'); |
||||
|
||||
# Wait for checksum disable to be replayed |
||||
$node_master->wait_for_catchup($node_standby_1, 'replay'); |
||||
|
||||
# Ensure that the standby has switched to off |
||||
$result = $node_standby_1->safe_psql('postgres', |
||||
"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"); |
||||
is($result, "off", 'ensure checksums are in progress on standby_1'); |
||||
|
||||
$result = $node_master->safe_psql('postgres', "SELECT count(a) FROM t"); |
||||
is ($result, "20000", 'ensure we can safely read all data without checksums'); |
||||
@ -0,0 +1,27 @@ |
||||
Parsed test spec with 2 sessions |
||||
|
||||
starting permutation: c_verify_checksums_off r_seqread c_enable_checksums c_verify_checksums_inprogress c_disable_checksums c_wait_checksums_off |
||||
step c_verify_checksums_off: SELECT setting = 'off' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; |
||||
?column? |
||||
|
||||
t |
||||
step r_seqread: SELECT * FROM reader_loop(); |
||||
reader_loop |
||||
|
||||
t |
||||
step c_enable_checksums: SELECT pg_enable_data_checksums(1000); |
||||
pg_enable_data_checksums |
||||
|
||||
|
||||
step c_verify_checksums_inprogress: SELECT setting = 'inprogress' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; |
||||
?column? |
||||
|
||||
t |
||||
step c_disable_checksums: SELECT pg_disable_data_checksums(); |
||||
pg_disable_data_checksums |
||||
|
||||
|
||||
step c_wait_checksums_off: SELECT test_checksums_off(); |
||||
test_checksums_off |
||||
|
||||
t |
||||
@ -0,0 +1,27 @@ |
||||
Parsed test spec with 3 sessions |
||||
|
||||
starting permutation: c_verify_checksums_off w_insert100k r_seqread c_enable_checksums c_wait_for_checksums c_verify_checksums_on |
||||
step c_verify_checksums_off: SELECT setting = 'off' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; |
||||
?column? |
||||
|
||||
t |
||||
step w_insert100k: SELECT insert_1k(100); |
||||
insert_1k |
||||
|
||||
t |
||||
step r_seqread: SELECT * FROM reader_loop(); |
||||
reader_loop |
||||
|
||||
t |
||||
step c_enable_checksums: SELECT pg_enable_data_checksums(); |
||||
pg_enable_data_checksums |
||||
|
||||
|
||||
step c_wait_for_checksums: SELECT test_checksums_on(); |
||||
test_checksums_on |
||||
|
||||
t |
||||
step c_verify_checksums_on: SELECT setting = 'on' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; |
||||
?column? |
||||
|
||||
t |
||||
@ -0,0 +1,47 @@ |
||||
setup |
||||
{ |
||||
CREATE TABLE t1 (a serial, b integer, c text); |
||||
INSERT INTO t1 (b, c) VALUES (generate_series(1,10000), 'starting values'); |
||||
|
||||
CREATE OR REPLACE FUNCTION test_checksums_off() RETURNS boolean AS $$ |
||||
DECLARE |
||||
enabled boolean; |
||||
BEGIN |
||||
PERFORM pg_sleep(1); |
||||
SELECT setting = 'off' INTO enabled FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; |
||||
RETURN enabled; |
||||
END; |
||||
$$ LANGUAGE plpgsql; |
||||
|
||||
CREATE OR REPLACE FUNCTION reader_loop() RETURNS boolean AS $$ |
||||
DECLARE |
||||
counter integer; |
||||
enabled boolean; |
||||
BEGIN |
||||
FOR counter IN 1..100 LOOP |
||||
PERFORM count(a) FROM t1; |
||||
END LOOP; |
||||
RETURN True; |
||||
END; |
||||
$$ LANGUAGE plpgsql; |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
DROP FUNCTION reader_loop(); |
||||
DROP FUNCTION test_checksums_off(); |
||||
|
||||
DROP TABLE t1; |
||||
} |
||||
|
||||
session "reader" |
||||
step "r_seqread" { SELECT * FROM reader_loop(); } |
||||
|
||||
session "checksums" |
||||
step "c_verify_checksums_off" { SELECT setting = 'off' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; } |
||||
step "c_enable_checksums" { SELECT pg_enable_data_checksums(1000); } |
||||
step "c_disable_checksums" { SELECT pg_disable_data_checksums(); } |
||||
step "c_verify_checksums_inprogress" { SELECT setting = 'inprogress' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; } |
||||
step "c_wait_checksums_off" { SELECT test_checksums_off(); } |
||||
|
||||
permutation "c_verify_checksums_off" "r_seqread" "c_enable_checksums" "c_verify_checksums_inprogress" "c_disable_checksums" "c_wait_checksums_off" |
||||
@ -0,0 +1,70 @@ |
||||
setup |
||||
{ |
||||
CREATE TABLE t1 (a serial, b integer, c text); |
||||
INSERT INTO t1 (b, c) VALUES (generate_series(1,10000), 'starting values'); |
||||
|
||||
CREATE OR REPLACE FUNCTION insert_1k(iterations int) RETURNS boolean AS $$ |
||||
DECLARE |
||||
counter integer; |
||||
BEGIN |
||||
FOR counter IN 1..$1 LOOP |
||||
INSERT INTO t1 (b, c) VALUES ( |
||||
generate_series(1, 1000), |
||||
array_to_string(array(select chr(97 + (random() * 25)::int) from generate_series(1,250)), '') |
||||
); |
||||
PERFORM pg_sleep(0.1); |
||||
END LOOP; |
||||
RETURN True; |
||||
END; |
||||
$$ LANGUAGE plpgsql; |
||||
|
||||
CREATE OR REPLACE FUNCTION test_checksums_on() RETURNS boolean AS $$ |
||||
DECLARE |
||||
enabled boolean; |
||||
BEGIN |
||||
LOOP |
||||
SELECT setting = 'on' INTO enabled FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; |
||||
IF enabled THEN |
||||
EXIT; |
||||
END IF; |
||||
PERFORM pg_sleep(1); |
||||
END LOOP; |
||||
RETURN enabled; |
||||
END; |
||||
$$ LANGUAGE plpgsql; |
||||
|
||||
CREATE OR REPLACE FUNCTION reader_loop() RETURNS boolean AS $$ |
||||
DECLARE |
||||
counter integer; |
||||
BEGIN |
||||
FOR counter IN 1..30 LOOP |
||||
PERFORM count(a) FROM t1; |
||||
PERFORM pg_sleep(0.2); |
||||
END LOOP; |
||||
RETURN True; |
||||
END; |
||||
$$ LANGUAGE plpgsql; |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
DROP FUNCTION reader_loop(); |
||||
DROP FUNCTION test_checksums_on(); |
||||
DROP FUNCTION insert_1k(int); |
||||
|
||||
DROP TABLE t1; |
||||
} |
||||
|
||||
session "writer" |
||||
step "w_insert100k" { SELECT insert_1k(100); } |
||||
|
||||
session "reader" |
||||
step "r_seqread" { SELECT * FROM reader_loop(); } |
||||
|
||||
session "checksums" |
||||
step "c_verify_checksums_off" { SELECT setting = 'off' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; } |
||||
step "c_enable_checksums" { SELECT pg_enable_data_checksums(); } |
||||
step "c_wait_for_checksums" { SELECT test_checksums_on(); } |
||||
step "c_verify_checksums_on" { SELECT setting = 'on' FROM pg_catalog.pg_settings WHERE name = 'data_checksums'; } |
||||
|
||||
permutation "c_verify_checksums_off" "w_insert100k" "r_seqread" "c_enable_checksums" "c_wait_for_checksums" "c_verify_checksums_on" |
||||
Loading…
Reference in new issue