Recovery control functions
Functions to control recovery, to aid PITR and Hot Standby.
pg_is_xlog_replay_paused()
pg_xlog_replay_pause()
pg_xlog_replay_resume()
recovery.conf parameter: pause_at_recovery_target (bool)
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
validate.v3.patchtext/x-patch; charset=UTF-8; name=validate.v3.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 17a1d34..5b90e9e 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -42,7 +42,8 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
- ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]
+ VALIDATE CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
@@ -220,11 +221,27 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
- <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ [ NOT VALID ]</literal></term>
<listitem>
<para>
This form adds a new constraint to a table using the same syntax as
- <xref linkend="SQL-CREATETABLE">.
+ <xref linkend="SQL-CREATETABLE">. Newly added foreign key constraints can
+ also be defined as <literal>NOT VALID</literal> to avoid the
+ potentially lengthy initial check that must otherwise be performed.
+ Constraint checks are skipped at create table time, so
+ <xref linkend="SQL-CREATETABLE"> does not contain this option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>VALIDATE CONSTRAINT</literal></term>
+ <listitem>
+ <para>
+ This form validates a foreign key constraint that was previously created
+ as <literal>NOT VALID</literal>. Constraints already marked valid do not
+ cause an error response.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4c55db7..7cc521d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1835,6 +1835,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ true, /* Is Validated */
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
keycount, /* # attrs in the constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 4dd89e1..a19b139 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -777,6 +777,7 @@ index_create(Oid heapRelationId,
constraintType,
deferrable,
initdeferred,
+ true,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 3a38518..6619eed 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -46,6 +46,7 @@ CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isValidated,
Oid relId,
const int16 *constraintKey,
int constraintNKeys,
@@ -158,6 +159,7 @@ CreateConstraintEntry(const char *constraintName,
values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable);
values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred);
+ values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f3bd565..8041a9b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -254,6 +254,7 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
static void AlterSeqNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid,
const char *newNspName, LOCKMODE lockmode);
+static void ATExecValidateConstraint(Relation rel, const char *constrName);
static int transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids);
static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
@@ -264,9 +265,9 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
int numattrs, int16 *attnums,
Oid *opclasses);
static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
-static void validateForeignKeyConstraint(Constraint *fkconstraint,
+static void validateForeignKeyConstraint(char *conname,
Relation rel, Relation pkrel,
- Oid pkindOid, Oid constraintOid);
+ Oid pkindOid, Oid constraintOid, bool must_use_query);
static void createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
Oid constraintOid, Oid indexOid);
static void ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
@@ -2646,7 +2647,7 @@ AlterTableGetLockLevel(List *cmds)
* though don't change the semantic results from normal data reads and writes.
* Delaying an ALTER TABLE behind currently active writes only delays the point
* where the new strategy begins to take effect, so there is no benefit in waiting.
- * In thise case the minimum restriction applies: we don't currently allow
+ * In these case the minimum restriction applies: we don't currently allow
* concurrent catalog updates.
*/
case AT_SetStatistics:
@@ -2657,6 +2658,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetOptions:
case AT_ResetOptions:
case AT_SetStorage:
+ case AT_ValidateConstraint:
cmd_lockmode = ShareUpdateExclusiveLock;
break;
@@ -2878,6 +2880,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
break;
+ case AT_ValidateConstraint:
case AT_EnableTrig: /* ENABLE TRIGGER variants */
case AT_EnableAlwaysTrig:
case AT_EnableReplicaTrig:
@@ -3042,6 +3045,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
true, lockmode);
break;
+ case AT_ValidateConstraint:
+ ATExecValidateConstraint(rel, cmd->name);
+ break;
case AT_DropConstraint: /* DROP CONSTRAINT */
ATExecDropConstraint(rel, cmd->name, cmd->behavior,
false, false,
@@ -3294,9 +3300,15 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
*/
refrel = heap_open(con->refrelid, ShareRowExclusiveLock);
- validateForeignKeyConstraint(fkconstraint, rel, refrel,
+ validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
con->refindid,
- con->conid);
+ con->conid,
+ false);
+
+ /*
+ * No need to mark the constraint row as validated,
+ * we did that when we inserted the row earlier.
+ */
heap_close(refrel, NoLock);
}
@@ -5422,6 +5434,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
+ !fkconstraint->skip_validation,
RelationGetRelid(rel),
fkattnum,
numfks,
@@ -5451,7 +5464,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
/*
* Tell Phase 3 to check that the constraint is satisfied by existing rows
- * (we can skip this during table creation).
+ * We can skip this during table creation or if requested explicitly
+ * by specifying NOT VALID on an alter table statement.
*/
if (!fkconstraint->skip_validation)
{
@@ -5474,6 +5488,81 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
heap_close(pkrel, NoLock);
}
+/*
+ * ALTER TABLE VALIDATE CONSTRAINT
+ */
+static void
+ATExecValidateConstraint(Relation rel, const char *constrName)
+{
+ Relation conrel;
+ Form_pg_constraint con;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ bool found = false;
+ Oid conid;
+
+ conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+ /*
+ * Find and the target constraint
+ */
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ scan = systable_beginscan(conrel, ConstraintRelidIndexId,
+ true, SnapshotNow, 1, &key);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ con = (Form_pg_constraint) GETSTRUCT(tuple);
+
+ if (strcmp(NameStr(con->conname), constrName) != 0)
+ continue;
+
+ conid = HeapTupleGetOid(tuple);
+ found = true;
+ break;
+ }
+
+ if (found && con->contype == CONSTR_FOREIGN && !con->convalidated)
+ {
+ Relation refrel;
+
+ /*
+ * Triggers are already in place on both tables, so a
+ * concurrent write that alters the result here is not
+ * possible.
+ */
+ refrel = heap_open(con->confrelid, AccessShareLock);
+
+ validateForeignKeyConstraint((char *)constrName, rel, refrel,
+ con->conindid,
+ conid,
+ true); /* must use query */
+
+ /*
+ * Now update the catalog, while we have the door open.
+ */
+ con->convalidated = BoolGetDatum(true);
+ simple_heap_update(conrel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(conrel, tuple);
+
+ heap_close(refrel, NoLock);
+ }
+
+ systable_endscan(scan);
+ heap_close(conrel, RowExclusiveLock);
+
+ if (!found)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign key constraint \"%s\" of relation \"%s\" does not exist",
+ constrName, RelationGetRelationName(rel))));
+ }
+}
/*
* transformColumnNameList - transform list of column names
@@ -5779,11 +5868,12 @@ checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
* Caller must have opened and locked both relations appropriately.
*/
static void
-validateForeignKeyConstraint(Constraint *fkconstraint,
+validateForeignKeyConstraint(char *conname,
Relation rel,
Relation pkrel,
Oid pkindOid,
- Oid constraintOid)
+ Oid constraintOid,
+ bool must_use_query)
{
HeapScanDesc scan;
HeapTuple tuple;
@@ -5794,7 +5884,7 @@ validateForeignKeyConstraint(Constraint *fkconstraint,
*/
MemSet(&trig, 0, sizeof(trig));
trig.tgoid = InvalidOid;
- trig.tgname = fkconstraint->conname;
+ trig.tgname = conname;
trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
trig.tgisinternal = TRUE;
trig.tgconstrrelid = RelationGetRelid(pkrel);
@@ -5811,6 +5901,12 @@ validateForeignKeyConstraint(Constraint *fkconstraint,
if (RI_Initial_Check(&trig, rel, pkrel))
return;
+ if (must_use_query)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("cannot SELECT from primary key of relation \"%s\"",
+ RelationGetRelationName(rel))));
+
/*
* Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
* if that tuple had just been inserted. If any of those fail, it should
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 4802138..8d996a8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -422,6 +422,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
CONSTRAINT_TRIGGER,
stmt->deferrable,
stmt->initdeferred,
+ true,
RelationGetRelid(rel),
NULL, /* no conkey */
0,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 113bede..840cb81 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2377,6 +2377,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ true, /* Is Validated */
InvalidOid, /* not a relation constraint */
NULL,
0,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 660947c..2881211 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -545,7 +545,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
UNTIL UPDATE USER USING
- VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
+ VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
VERBOSE VERSION_P VIEW VOLATILE
WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
@@ -1751,6 +1751,14 @@ alter_table_cmd:
n->def = $2;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
+ | VALIDATE CONSTRAINT name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ValidateConstraint;
+ n->name = $3;
+ $$ = (Node *)n;
+ }
/* ALTER TABLE <name> DROP CONSTRAINT IF EXISTS <name> [RESTRICT|CASCADE] */
| DROP CONSTRAINT IF_P EXISTS name opt_drop_behavior
{
@@ -2711,9 +2719,25 @@ ConstraintElem:
n->fk_matchtype = $9;
n->fk_upd_action = (char) ($10 >> 8);
n->fk_del_action = (char) ($10 & 0xFF);
- n->skip_validation = FALSE;
n->deferrable = ($11 & 1) != 0;
n->initdeferred = ($11 & 2) != 0;
+ n->skip_validation = false;
+ $$ = (Node *)n;
+ }
+ | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
+ opt_column_list key_match key_actions
+ NOT VALID
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_FOREIGN;
+ n->location = @1;
+ n->pktable = $7;
+ n->fk_attrs = $4;
+ n->pk_attrs = $8;
+ n->fk_matchtype = $9;
+ n->fk_upd_action = (char) ($10 >> 8);
+ n->fk_del_action = (char) ($10 & 0xFF);
+ n->skip_validation = true;
$$ = (Node *)n;
}
;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 26dbcac..a7967ab 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -45,6 +45,7 @@ CATALOG(pg_constraint,2606)
char contype; /* constraint type; see codes below */
bool condeferrable; /* deferrable constraint? */
bool condeferred; /* deferred by default? */
+ bool convalidated; /* constraint has been validated? */
/*
* conrelid and conkey are only meaningful if the constraint applies to a
@@ -148,29 +149,30 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 22
+#define Natts_pg_constraint 23
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
#define Anum_pg_constraint_condeferrable 4
#define Anum_pg_constraint_condeferred 5
-#define Anum_pg_constraint_conrelid 6
-#define Anum_pg_constraint_contypid 7
-#define Anum_pg_constraint_conindid 8
-#define Anum_pg_constraint_confrelid 9
-#define Anum_pg_constraint_confupdtype 10
-#define Anum_pg_constraint_confdeltype 11
-#define Anum_pg_constraint_confmatchtype 12
-#define Anum_pg_constraint_conislocal 13
-#define Anum_pg_constraint_coninhcount 14
-#define Anum_pg_constraint_conkey 15
-#define Anum_pg_constraint_confkey 16
-#define Anum_pg_constraint_conpfeqop 17
-#define Anum_pg_constraint_conppeqop 18
-#define Anum_pg_constraint_conffeqop 19
-#define Anum_pg_constraint_conexclop 20
-#define Anum_pg_constraint_conbin 21
-#define Anum_pg_constraint_consrc 22
+#define Anum_pg_constraint_convalidated 6
+#define Anum_pg_constraint_conrelid 7
+#define Anum_pg_constraint_contypid 8
+#define Anum_pg_constraint_conindid 9
+#define Anum_pg_constraint_confrelid 10
+#define Anum_pg_constraint_confupdtype 11
+#define Anum_pg_constraint_confdeltype 12
+#define Anum_pg_constraint_confmatchtype 13
+#define Anum_pg_constraint_conislocal 14
+#define Anum_pg_constraint_coninhcount 15
+#define Anum_pg_constraint_conkey 16
+#define Anum_pg_constraint_confkey 17
+#define Anum_pg_constraint_conpfeqop 18
+#define Anum_pg_constraint_conppeqop 19
+#define Anum_pg_constraint_conffeqop 20
+#define Anum_pg_constraint_conexclop 21
+#define Anum_pg_constraint_conbin 22
+#define Anum_pg_constraint_consrc 23
/* Valid values for contype */
@@ -205,6 +207,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isValidated,
Oid relId,
const int16 *constraintKey,
int constraintNKeys,
@@ -228,6 +231,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
extern void RemoveConstraintById(Oid conId);
extern void RenameConstraintById(Oid conId, const char *newname);
+extern void SetValidatedConstraintById(Oid conId);
extern bool ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
Oid objNamespace, const char *conname);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3d2ae99..5714a54 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1140,6 +1140,7 @@ typedef enum AlterTableType
AT_ReAddIndex, /* internal to commands/tablecmds.c */
AT_AddConstraint, /* add constraint */
AT_AddConstraintRecurse, /* internal to commands/tablecmds.c */
+ AT_ValidateConstraint, /* validate constraint */
AT_ProcessedConstraint, /* pre-processed add constraint (local in
* parser/parse_utilcmd.c) */
AT_DropConstraint, /* drop constraint */
@@ -1181,6 +1182,7 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
Node *transform; /* transformation expr for ALTER TYPE */
DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */
bool missing_ok; /* skip error if missing? */
+ bool validated;
} AlterTableCmd;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 578d3cd..7dad001 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -397,6 +397,7 @@ PG_KEYWORD("user", USER, RESERVED_KEYWORD)
PG_KEYWORD("using", USING, RESERVED_KEYWORD)
PG_KEYWORD("vacuum", VACUUM, UNRESERVED_KEYWORD)
PG_KEYWORD("valid", VALID, UNRESERVED_KEYWORD)
+PG_KEYWORD("validate", VALIDATE, UNRESERVED_KEYWORD)
PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD)
PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3d126bb..db8acff 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -184,6 +184,10 @@ DETAIL: Key (a)=(5) is not present in table "tmp2".
DELETE FROM tmp3 where a=5;
-- Try (and succeed)
ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
+ALTER TABLE tmp3 drop constraint tmpconstr;
+-- Try NOT VALID and then VALIDATE CONSTRAINT
+ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full NOT VALID;
+ALTER TABLE tmp3 validate constraint tmpconstr;
-- Try (and fail) to create constraint from tmp5(a) to tmp4(a) - unique constraint on
-- tmp4 is a,b
ALTER TABLE tmp5 add constraint tmpconstr foreign key(a) references tmp4(a) match full;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 4895768..6aaee1a 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -221,6 +221,11 @@ DELETE FROM tmp3 where a=5;
-- Try (and succeed)
ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
+ALTER TABLE tmp3 drop constraint tmpconstr;
+
+-- Try NOT VALID and then VALIDATE CONSTRAINT
+ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full NOT VALID;
+ALTER TABLE tmp3 validate constraint tmpconstr;
-- Try (and fail) to create constraint from tmp5(a) to tmp4(a) - unique constraint on
-- tmp4 is a,b
On Fri, 2011-01-14 at 11:09 +0000, Simon Riggs wrote:
Functions to control recovery, to aid PITR and Hot Standby.
pg_is_xlog_replay_paused()
pg_xlog_replay_pause()
pg_xlog_replay_resume()recovery.conf parameter: pause_at_recovery_target (bool)
And now with the correct patch.
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
recovery_control.v2.patchtext/x-patch; charset=UTF-8; name=recovery_control.v2.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d177775..55fa70d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14173,6 +14173,63 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
</table>
<para>
+ The functions shown in <xref
+ linkend="functions-recovery-control-table"> control the progress of recovery.
+ These functions may be executed only during recovery.
+ </para>
+
+ <table id="functions-recovery-control-table">
+ <title>Recovery Control Functions</title>
+ <tgroup cols="3">
+ <thead>
+ <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <literal><function>pg_is_xlog_replay_paused()</function></literal>
+ </entry>
+ <entry><type>bool</type></entry>
+ <entry>True if recovery is paused.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <literal><function>pg_xlog_replay_pause()</function></literal>
+ </entry>
+ <entry><type>void</type></entry>
+ <entry>Pauses recovery immediately.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <literal><function>pg_xlog_replay_resume()</function></literal>
+ </entry>
+ <entry><type>void</type></entry>
+ <entry>Restarts recovery if it was paused.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ While recovery is paused no further database changes are applied.
+ If in hot standby, all queries will see the same consistent snapshot
+ of the database, and no query conflicts will be generated.
+ </para>
+
+ <para>
+ If streaming replication is disabled, the paused state may continue
+ indefinitely without problem. While streaming replication is in
+ progress WAL records will continue to be received, which will
+ eventually fill available disk space, depending upon the duration of
+ the pause, the rate of WAL generation and available disk space.
+ </para>
+
+ <para>
The functions shown in <xref linkend="functions-admin-dbsize"> calculate
the disk space usage of database objects.
</para>
diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
index bd9dfd1..b5f9cfa 100644
--- a/doc/src/sgml/recovery-config.sgml
+++ b/doc/src/sgml/recovery-config.sgml
@@ -228,6 +228,31 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</listitem>
</varlistentry>
+ <varlistentry id="pause-at-recovery-target"
+ xreflabel="pause_at_recovery_target">
+ <term><varname>pause_at_recovery_target</varname>
+ (<type>boolean</type>)
+ </term>
+ <indexterm>
+ <primary><varname>pause_at_recovery_target</> recovery parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Specifies whether recovery should pause when the recovery target
+ is reached. The default is true, if a recovery target is set.
+ This is intended to allow queries to be executed against the
+ database to check if this recovery target is the most desirable
+ point for recovery. The paused state can be resumed by using
+ <function>pg_xlog_replay_resume()</> (See
+ <xref linkend="functions-recovery-control-table">), which then
+ causes recovery to end. If this recovery target is not the
+ desired stopping point, then shutdown the server, change the
+ recovery target settings to a later target and restart to
+ continue recovery.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</sect1>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 818a59f..6a8c500 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -184,6 +184,7 @@ static char *recoveryEndCommand = NULL;
static char *archiveCleanupCommand = NULL;
static RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
static bool recoveryTargetInclusive = true;
+static bool recoveryPauseAtTarget = false;
static TransactionId recoveryTargetXid;
static TimestampTz recoveryTargetTime;
@@ -416,6 +417,8 @@ typedef struct XLogCtlData
XLogRecPtr recoveryLastRecPtr;
/* timestamp of last COMMIT/ABORT record replayed (or being replayed) */
TimestampTz recoveryLastXTime;
+ /* Are we requested to pause recovery? */
+ bool recoveryPause;
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
@@ -563,6 +566,9 @@ static void readRecoveryCommandFile(void);
static void exitArchiveRecovery(TimeLineID endTLI,
uint32 endLogId, uint32 endLogSeg);
static bool recoveryStopsHere(XLogRecord *record, bool *includeThis);
+static void recoveryPausesHere(void);
+static bool RecoveryIsPaused(void);
+static void SetRecoveryPause(bool recoveryPause);
static void SetLatestXTime(TimestampTz xtime);
static TimestampTz GetLatestXTime(void);
static void CheckRequiredParameterValues(void);
@@ -5080,6 +5086,15 @@ readRecoveryCommandFile(void)
(errmsg("archive_cleanup_command = '%s'",
archiveCleanupCommand)));
}
+ else if (strcmp(item->name, "pause_at_recovery_target") == 0)
+ {
+ if (!parse_bool(item->value, &recoveryPauseAtTarget))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a Boolean value", "pause_at_recovery_target")));
+ ereport(DEBUG2,
+ (errmsg("pause_at_recovery_target = '%s'", item->value)));
+ }
else if (strcmp(item->name, "recovery_target_timeline") == 0)
{
rtliGiven = true;
@@ -5463,6 +5478,113 @@ recoveryStopsHere(XLogRecord *record, bool *includeThis)
}
/*
+ * Recheck shared recoveryPause by polling.
+ *
+ * XXX It might seem we should do this via a shared Latch, but
+ * currently we only support one shared latch per process and
+ * that is already taken for Startup process. Polling is used
+ * in other places in xlog.c already, so not a concern.
+ */
+static void
+recoveryPausesHere(void)
+{
+ while (RecoveryIsPaused());
+ {
+ pg_usleep(100000L); /* 100 ms */
+ HandleStartupProcInterrupts();
+ };
+}
+
+static bool
+RecoveryIsPaused(void)
+{
+ /* use volatile pointer to prevent code rearrangement */
+ volatile XLogCtlData *xlogctl = XLogCtl;
+ bool recoveryPause;
+
+ SpinLockAcquire(&xlogctl->info_lck);
+ recoveryPause = xlogctl->recoveryPause;
+ SpinLockRelease(&xlogctl->info_lck);
+
+ return recoveryPause;
+}
+
+static void
+SetRecoveryPause(bool recoveryPause)
+{
+ /* use volatile pointer to prevent code rearrangement */
+ volatile XLogCtlData *xlogctl = XLogCtl;
+
+ SpinLockAcquire(&xlogctl->info_lck);
+ xlogctl->recoveryPause = recoveryPause;
+ SpinLockRelease(&xlogctl->info_lck);
+}
+
+/*
+ * pg_xlog_replay_pause - pause recovery now
+ */
+Datum
+pg_xlog_replay_pause(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to control recovery"))));
+
+ if (!RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is not in progress"),
+ errhint("Recovery control functions can only be executed during recovery.")));
+
+ SetRecoveryPause(true);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * pg_xlog_replay_resume - resume recovery now
+ */
+Datum
+pg_xlog_replay_resume(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to control recovery"))));
+
+ if (!RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is not in progress"),
+ errhint("Recovery control functions can only be executed during recovery.")));
+
+ SetRecoveryPause(false);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * pg_is_xlog_replay_paused
+ */
+Datum
+pg_is_xlog_replay_paused(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to control recovery"))));
+
+ if (!RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is not in progress"),
+ errhint("Recovery control functions can only be executed during recovery.")));
+
+ PG_RETURN_BOOL(RecoveryIsPaused());
+}
+
+/*
* Save timestamp of latest processed commit/abort record.
*
* We keep this in XLogCtl, not a simple static variable, so that it can be
@@ -6052,6 +6174,7 @@ StartupXLOG(void)
xlogctl->replayEndRecPtr = ReadRecPtr;
xlogctl->recoveryLastRecPtr = ReadRecPtr;
xlogctl->recoveryLastXTime = 0;
+ xlogctl->recoveryPause = false;
SpinLockRelease(&xlogctl->info_lck);
/* Also ensure XLogReceiptTime has a sane value */
@@ -6100,6 +6223,7 @@ StartupXLOG(void)
{
bool recoveryContinue = true;
bool recoveryApply = true;
+ bool recoveryPause = false;
ErrorContextCallback errcontext;
TimestampTz xtime;
@@ -6146,6 +6270,11 @@ StartupXLOG(void)
*/
if (recoveryStopsHere(record, &recoveryApply))
{
+ if (recoveryPauseAtTarget)
+ {
+ SetRecoveryPause(true);
+ recoveryPausesHere();
+ }
reachedStopPoint = true; /* see below */
recoveryContinue = false;
if (!recoveryApply)
@@ -6172,8 +6301,12 @@ StartupXLOG(void)
*/
SpinLockAcquire(&xlogctl->info_lck);
xlogctl->replayEndRecPtr = EndRecPtr;
+ recoveryPause = xlogctl->recoveryPause;
SpinLockRelease(&xlogctl->info_lck);
+ if (recoveryPause)
+ recoveryPausesHere();
+
/*
* If we are attempting to enter Hot Standby mode, process
* XIDs we see
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index ba64035..6390113 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -275,5 +275,8 @@ extern Datum pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS);
extern Datum pg_xlogfile_name_offset(PG_FUNCTION_ARGS);
extern Datum pg_xlogfile_name(PG_FUNCTION_ARGS);
extern Datum pg_is_in_recovery(PG_FUNCTION_ARGS);
+extern Datum pg_xlog_replay_pause(PG_FUNCTION_ARGS);
+extern Datum pg_xlog_replay_resume(PG_FUNCTION_ARGS);
+extern Datum pg_is_xlog_replay_paused(PG_FUNCTION_ARGS);
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d6ed60a..6089197 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3414,6 +3414,13 @@ DESCR("last xlog replay location");
DATA(insert OID = 3830 ( pg_last_xact_replay_timestamp PGNSP PGUID 12 1 0 0 f f f t f v 0 0 1184 "" _null_ _null_ _null_ _null_ pg_last_xact_replay_timestamp _null_ _null_ _null_ ));
DESCR("timestamp of last replay xact");
+DATA(insert OID = 3071 ( pg_xlog_replay_pause PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2278 "" _null_ _null_ _null_ _null_ pg_xlog_replay_pause _null_ _null_ _null_ ));
+DESCR("pauses xlog replay");
+DATA(insert OID = 3072 ( pg_xlog_replay_resume PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2278 "" _null_ _null_ _null_ _null_ pg_xlog_replay_resume _null_ _null_ _null_ ));
+DESCR("resumes xlog replay, if it was paused");
+DATA(insert OID = 3073 ( pg_is_xlog_replay_paused PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_is_xlog_replay_paused _null_ _null_ _null_ ));
+DESCR("true if xlog replay is paused");
+
DATA(insert OID = 2621 ( pg_reload_conf PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_reload_conf _null_ _null_ _null_ ));
DESCR("reload configuration files");
DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
On Fri, Jan 14, 2011 at 12:15, Simon Riggs <simon@2ndquadrant.com> wrote:
On Fri, 2011-01-14 at 11:09 +0000, Simon Riggs wrote:
Functions to control recovery, to aid PITR and Hot Standby.
pg_is_xlog_replay_paused()
pg_xlog_replay_pause()
pg_xlog_replay_resume()recovery.conf parameter: pause_at_recovery_target (bool)
Awesome, I've been waiting for these! :-)
How hard would it be to have a pg_xlog_replay_until(<xlog location or
timestamp>), to have it resume recovery up to that point and then
pause again?
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On 14.01.2011 13:15, Simon Riggs wrote:
/* + * Recheck shared recoveryPause by polling. + * + * XXX It might seem we should do this via a shared Latch, but + * currently we only support one shared latch per process and + * that is already taken for Startup process. Polling is used + * in other places in xlog.c already, so not a concern. + */
There is no such limitation with latches.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Fri, 2011-01-14 at 12:41 +0100, Magnus Hagander wrote:
On Fri, Jan 14, 2011 at 12:15, Simon Riggs <simon@2ndquadrant.com> wrote:
On Fri, 2011-01-14 at 11:09 +0000, Simon Riggs wrote:
Functions to control recovery, to aid PITR and Hot Standby.
pg_is_xlog_replay_paused()
pg_xlog_replay_pause()
pg_xlog_replay_resume()recovery.conf parameter: pause_at_recovery_target (bool)
Awesome, I've been waiting for these! :-)
How hard would it be to have a pg_xlog_replay_until(<xlog location or
timestamp>), to have it resume recovery up to that point and then
pause again?
You can already do that for timestamps.
What you can't do is dynamically set recovery targets via functions,
since currently that is set via recovery.conf parameters. Which requires
restart.
Jaime has a separate patch about recovery targets as well, which does
some more of what you want.
Some things are straightforward, some things require overhaul of the
recovery.conf mechanisms, which is not the right time to do that, nor
have we even discussed let alone agreed what we would change it to.
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, 2011-01-14 at 13:47 +0200, Heikki Linnakangas wrote:
On 14.01.2011 13:15, Simon Riggs wrote:
/* + * Recheck shared recoveryPause by polling. + * + * XXX It might seem we should do this via a shared Latch, but + * currently we only support one shared latch per process and + * that is already taken for Startup process. Polling is used + * in other places in xlog.c already, so not a concern. + */There is no such limitation with latches.
SIGUSR1 handler can only handle one shared latch
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
On 14.01.2011 14:01, Simon Riggs wrote:
On Fri, 2011-01-14 at 13:47 +0200, Heikki Linnakangas wrote:
On 14.01.2011 13:15, Simon Riggs wrote:
/* + * Recheck shared recoveryPause by polling. + * + * XXX It might seem we should do this via a shared Latch, but + * currently we only support one shared latch per process and + * that is already taken for Startup process. Polling is used + * in other places in xlog.c already, so not a concern. + */There is no such limitation with latches.
SIGUSR1 handler can only handle one shared latch
You can only *wait* for one latch at a time, but you can own more than
that. AFAICS you would never need to wait for the recovery-pause-latch
at the same time as the other latch.
(That you can't wait for more than one latch at a time isn't a
limitation of the SIGUSR1 handler either. The signal handler and the
self-pipe mechanism wouldn't need any changes to support multi-latch
waits. We're just missing a WaitMultipleLatches() function that would
check the is_set flag on multiple latches.)
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Fri, 2011-01-14 at 14:08 +0200, Heikki Linnakangas wrote:
On 14.01.2011 14:01, Simon Riggs wrote:
On Fri, 2011-01-14 at 13:47 +0200, Heikki Linnakangas wrote:
On 14.01.2011 13:15, Simon Riggs wrote:
/* + * Recheck shared recoveryPause by polling. + * + * XXX It might seem we should do this via a shared Latch, but + * currently we only support one shared latch per process and + * that is already taken for Startup process. Polling is used + * in other places in xlog.c already, so not a concern. + */There is no such limitation with latches.
SIGUSR1 handler can only handle one shared latch
You can only *wait* for one latch at a time, but you can own more than
that. AFAICS you would never need to wait for the recovery-pause-latch
at the same time as the other latch.
Yes, I understand.
Trouble is, if you wait on Latch X and other processes send wakeup
assuming you were waiting on Latch Y, then this will erroneously wake
you up.
So a process can have more than one shared latch, BUT other processes
don't know and can't tell which latch you're waiting on. Yes, you can
get around that, but not via the direct support of the latch module, as
currently written.
Polling is fine in that case, and we already do elsewhere, so it wasn't
critical for me to extend latch support to implement this.
(That you can't wait for more than one latch at a time isn't a
limitation of the SIGUSR1 handler either. The signal handler and the
self-pipe mechanism wouldn't need any changes to support multi-latch
waits. We're just missing a WaitMultipleLatches() function that would
check the is_set flag on multiple latches.)
Something for the future.
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
On 14.01.2011 14:18, Simon Riggs wrote:
On Fri, 2011-01-14 at 14:08 +0200, Heikki Linnakangas wrote:
On 14.01.2011 14:01, Simon Riggs wrote:
On Fri, 2011-01-14 at 13:47 +0200, Heikki Linnakangas wrote:
On 14.01.2011 13:15, Simon Riggs wrote:
/* + * Recheck shared recoveryPause by polling. + * + * XXX It might seem we should do this via a shared Latch, but + * currently we only support one shared latch per process and + * that is already taken for Startup process. Polling is used + * in other places in xlog.c already, so not a concern. + */There is no such limitation with latches.
SIGUSR1 handler can only handle one shared latch
You can only *wait* for one latch at a time, but you can own more than
that. AFAICS you would never need to wait for the recovery-pause-latch
at the same time as the other latch.Yes, I understand.
Trouble is, if you wait on Latch X and other processes send wakeup
assuming you were waiting on Latch Y, then this will erroneously wake
you up.
The signal will wake up the process, but WaitLatch will quickly go back
to sleep if it wasn't for the latch we're waiting for. I don't think
that causes any meaningful performance issues.
So that's all handled within the latch code.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Fri, 2011-01-14 at 14:27 +0200, Heikki Linnakangas wrote:
Trouble is, if you wait on Latch X and other processes send wakeup
assuming you were waiting on Latch Y, then this will erroneously wake
you up.The signal will wake up the process, but WaitLatch will quickly go back
to sleep if it wasn't for the latch we're waiting for. I don't think
that causes any meaningful performance issues.
Neither does polling.
So that's all handled within the latch code.
I'll adjust the comment.
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
On Fri, Jan 14, 2011 at 8:15 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
On Fri, 2011-01-14 at 11:09 +0000, Simon Riggs wrote:
Functions to control recovery, to aid PITR and Hot Standby.
pg_is_xlog_replay_paused()
pg_xlog_replay_pause()
pg_xlog_replay_resume()recovery.conf parameter: pause_at_recovery_target (bool)
And now with the correct patch.
IIRC, in last year, you implemented the related function which
advances recovery the specified number of records and pauses.
Why did you drop that from the patch? That's very useful at least
for me to do PITR and debug the recovery code.
+ If in hot standby, all queries will see the same consistent snapshot
+ of the database, and no query conflicts will be generated.
Really? The access exclusive lock taken from the master before
pause of recovery can conflict with a query?
+ <primary><varname>pause_at_recovery_target</> recovery
parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Specifies whether recovery should pause when the recovery target
+ is reached. The default is true, if a recovery target is set.
The default is false, according to the code.
If HS is disabled and pause_at_recovery_target is enabled,
recovery might never end infinitely. This is not desirable.
We should reject such a combination of settings or emit
WARNING?
+ while (RecoveryIsPaused());
+ {
+ pg_usleep(100000L); /* 100 ms */
+ HandleStartupProcInterrupts();
100ms seems too short. What about 1s or bigger?
Or wait on the latch rather than using poll loop.
Regards,
--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
On Fri, Jan 14, 2011 at 9:00 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
How hard would it be to have a pg_xlog_replay_until(<xlog location or
timestamp>), to have it resume recovery up to that point and then
pause again?You can already do that for timestamps.
You mean using recovery_target_time and pause_at_recovery_target?
The problem is that we cannot continue recovery after the pause
by them. If we resume recovery after the pause, recovery ends
immediately.
Regards,
--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
On Sat, 2011-01-15 at 20:11 +0900, Fujii Masao wrote:
On Fri, Jan 14, 2011 at 9:00 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
How hard would it be to have a pg_xlog_replay_until(<xlog location or
timestamp>), to have it resume recovery up to that point and then
pause again?You can already do that for timestamps.
You mean using recovery_target_time and pause_at_recovery_target?
The problem is that we cannot continue recovery after the pause
by them. If we resume recovery after the pause, recovery ends
immediately.
Shutdown while paused, alter parameter, restart.
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
On Sat, 2011-01-15 at 20:00 +0900, Fujii Masao wrote:
On Fri, Jan 14, 2011 at 8:15 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
On Fri, 2011-01-14 at 11:09 +0000, Simon Riggs wrote:
Functions to control recovery, to aid PITR and Hot Standby.
pg_is_xlog_replay_paused()
pg_xlog_replay_pause()
pg_xlog_replay_resume()recovery.conf parameter: pause_at_recovery_target (bool)
And now with the correct patch.
IIRC, in last year, you implemented the related function which
advances recovery the specified number of records and pauses.
Why did you drop that from the patch? That's very useful at least
for me to do PITR and debug the recovery code.
SMoP. It complicated the code and the testing time would have exceeded
the amount of time I had available to spend on this, by a long way.
+ If in hot standby, all queries will see the same consistent snapshot + of the database, and no query conflicts will be generated.Really? The access exclusive lock taken from the master before
pause of recovery can conflict with a query?
No "recovery conflicts" will be generated. i.e. no new conflicts.
Yes, existing locks will interfere with queries, if they exist.
+ <primary><varname>pause_at_recovery_target</> recovery parameter</primary> + </indexterm> + <listitem> + <para> + Specifies whether recovery should pause when the recovery target + is reached. The default is true, if a recovery target is set.The default is false, according to the code.
Thanks. Well spotted.
If HS is disabled and pause_at_recovery_target is enabled,
recovery might never end infinitely. This is not desirable.
We should reject such a combination of settings or emit
WARNING?
I was about to say "but it already does that". Checking the patch it
seems I must have removed that line, though I can't see any reason why I
would have removed it now. Will put it back.
+ while (RecoveryIsPaused()); + { + pg_usleep(100000L); /* 100 ms */ + HandleStartupProcInterrupts();100ms seems too short. What about 1s or bigger?
Or wait on the latch rather than using poll loop.
Yes, time is short.
--
Simon Riggs http://www.2ndQuadrant.com/books/
PostgreSQL Development, 24x7 Support, Training and Services
On Sat, Jan 15, 2011 at 12:17, Simon Riggs <simon@2ndquadrant.com> wrote:
On Sat, 2011-01-15 at 20:11 +0900, Fujii Masao wrote:
On Fri, Jan 14, 2011 at 9:00 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
How hard would it be to have a pg_xlog_replay_until(<xlog location or
timestamp>), to have it resume recovery up to that point and then
pause again?You can already do that for timestamps.
You mean using recovery_target_time and pause_at_recovery_target?
The problem is that we cannot continue recovery after the pause
by them. If we resume recovery after the pause, recovery ends
immediately.Shutdown while paused, alter parameter, restart.
That's something I'd very much like to avoid - being able to say
continue-until using the function would be very nice. Consider for
example doing this from pgadmin.
So I'm back to my original question which is, how much work would this
be? I don't know my way around that part so I can't estimate, and
what's there so far is certainly a lot better than nothing, but if
it's not a huge amount of work it would be a great improvement.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On Sun, Jan 16, 2011 at 11:52 PM, Magnus Hagander <magnus@hagander.net> wrote:
So I'm back to my original question which is, how much work would this
be? I don't know my way around that part so I can't estimate, and
what's there so far is certainly a lot better than nothing, but if
it's not a huge amount of work it would be a great improvement.
I don't think it's a huge amount of work. Though I'm not sure
Simon has time to do that since he would be very busy with
SyncRep.
Regards,
--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center
On Mon, Jan 17, 2011 at 02:53, Fujii Masao <masao.fujii@gmail.com> wrote:
On Sun, Jan 16, 2011 at 11:52 PM, Magnus Hagander <magnus@hagander.net> wrote:
So I'm back to my original question which is, how much work would this
be? I don't know my way around that part so I can't estimate, and
what's there so far is certainly a lot better than nothing, but if
it's not a huge amount of work it would be a great improvement.I don't think it's a huge amount of work. Though I'm not sure
Simon has time to do that since he would be very busy with
SyncRep.
True - and I'd certainly want to see him focus on that, as long as
there is any chance we'll get it in there in time.
I'd add it myself, if I only knew that code enough to be sure of what
I was doing ;) But if somebody who does finds some spare time...
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/