From b42e4622746d7ce9515bb3fb90fa0e875ecde21a Mon Sep 17 00:00:00 2001 From: Craig Ringer Date: Wed, 21 Dec 2016 11:21:46 +0800 Subject: [PATCH 3/6] Make snapshot export on logical slot creation optional Allow logical decoding slot creation via the walsender protocol's CREATE_REPLICATION_SLOT command to optionally suppress exporting of a snapshot when the WITHOUT_SNAPSHOT option is passed. This means that when we allow creation of replication slots on standbys, which cannot export snapshots, we don't have to silently omit the snapshot creation. It also allows clients like pg_recvlogical, which neither need nor can use the exported snapshot, to suppress its creation. Since snapshot exporting can fail this improves reliability. --- doc/src/sgml/logicaldecoding.sgml | 13 ++++++++++--- doc/src/sgml/protocol.sgml | 17 +++++++++++++++-- src/backend/replication/repl_gram.y | 13 ++++++++++--- src/backend/replication/repl_scanner.l | 1 + src/backend/replication/walsender.c | 9 ++++++++- src/bin/pg_basebackup/streamutil.c | 5 +++++ src/include/nodes/replnodes.h | 1 + 7 files changed, 50 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml index 484915d..c0b6987 100644 --- a/doc/src/sgml/logicaldecoding.sgml +++ b/doc/src/sgml/logicaldecoding.sgml @@ -268,11 +268,12 @@ $ pg_recvlogical -d postgres --slot test --drop-slot - + Exported Snapshots - When a new replication slot is created using the streaming replication interface, - a snapshot is exported + When a new replication + slot is created using the streaming replication interface, a snapshot + is exported (see ), which will show exactly the state of the database after which all changes will be included in the change stream. This can be used to create a new replica by @@ -282,6 +283,12 @@ $ pg_recvlogical -d postgres --slot test --drop-slot database's state at that point in time, which afterwards can be updated using the slot's contents without losing any changes. + + Creation of a snapshot is not always possible - in particular, it will + fail when connected to a hot standby. Applications that do not require + snapshot export may suppress it with the WITHOUT_SNAPSHOT + option. + diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 5f89db5..a5b156c 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1433,8 +1433,8 @@ The commands accepted in walsender mode are: - - CREATE_REPLICATION_SLOT slot_name [ TEMPORARY ] { PHYSICAL [ RESERVE_WAL ] | LOGICAL output_plugin } + + CREATE_REPLICATION_SLOT slot_name [ TEMPORARY ] { PHYSICAL [ RESERVE_WAL ] | LOGICAL output_plugin [WITHOUT_SNAPSHOT] } CREATE_REPLICATION_SLOT @@ -1485,6 +1485,19 @@ The commands accepted in walsender mode are: + + + WITHOUT_SNAPSHOT + + + By default, logical replication slot creation exports a snapshot for + use in initialization; see . + Because not all clients need an exported snapshot its creation can + be suppressed with WITHOUT_SNAPSHOT. + + + + diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y index d962c76..b2786cc 100644 --- a/src/backend/replication/repl_gram.y +++ b/src/backend/replication/repl_gram.y @@ -78,6 +78,7 @@ Node *replication_parse_result; %token K_SLOT %token K_RESERVE_WAL %token K_TEMPORARY +%token K_WITHOUT_SNAPSHOT %type command %type base_backup start_replication start_logical_replication @@ -90,7 +91,7 @@ Node *replication_parse_result; %type plugin_opt_elem %type plugin_opt_arg %type opt_slot -%type opt_reserve_wal opt_temporary +%type opt_reserve_wal opt_temporary opt_without_snapshot %% @@ -194,8 +195,8 @@ create_replication_slot: cmd->reserve_wal = $5; $$ = (Node *) cmd; } - /* CREATE_REPLICATION_SLOT slot TEMPORARY LOGICAL plugin */ - | K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_LOGICAL IDENT + /* CREATE_REPLICATION_SLOT slot TEMPORARY LOGICAL plugin WITHOUT_SNAPSHOT */ + | K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_LOGICAL IDENT opt_without_snapshot { CreateReplicationSlotCmd *cmd; cmd = makeNode(CreateReplicationSlotCmd); @@ -203,6 +204,7 @@ create_replication_slot: cmd->slotname = $2; cmd->temporary = $3; cmd->plugin = $5; + cmd->without_snapshot = $6; $$ = (Node *) cmd; } ; @@ -283,6 +285,11 @@ opt_temporary: | /* EMPTY */ { $$ = false; } ; +opt_without_snapshot: + K_WITHOUT_SNAPSHOT { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + opt_slot: K_SLOT IDENT { $$ = $2; } diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l index a3b5f92..6b683aa 100644 --- a/src/backend/replication/repl_scanner.l +++ b/src/backend/replication/repl_scanner.l @@ -96,6 +96,7 @@ DROP_REPLICATION_SLOT { return K_DROP_REPLICATION_SLOT; } TIMELINE_HISTORY { return K_TIMELINE_HISTORY; } PHYSICAL { return K_PHYSICAL; } RESERVE_WAL { return K_RESERVE_WAL; } +WITHOUT_SNAPSHOT { return K_WITHOUT_SNAPSHOT; } LOGICAL { return K_LOGICAL; } SLOT { return K_SLOT; } TEMPORARY { return K_TEMPORARY; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 93c2816..a1d1c0c 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -849,7 +849,14 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) * Export a plain (not of the snapbuild.c type) snapshot to the user * that can be imported into another session. */ - snapshot_name = SnapBuildExportSnapshot(ctx->snapshot_builder); + if (cmd->without_snapshot) + snapshot_name = ""; + else if (RecoveryInProgress()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot export a snapshot from a standby"))); + else + snapshot_name = SnapBuildExportSnapshot(ctx->snapshot_builder); /* don't need the decoding context anymore */ FreeDecodingContext(ctx); diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c index 31290d3..3172424d 100644 --- a/src/bin/pg_basebackup/streamutil.c +++ b/src/bin/pg_basebackup/streamutil.c @@ -345,8 +345,13 @@ CreateReplicationSlot(PGconn *conn, const char *slot_name, const char *plugin, appendPQExpBuffer(query, "CREATE_REPLICATION_SLOT \"%s\" PHYSICAL", slot_name); else + { appendPQExpBuffer(query, "CREATE_REPLICATION_SLOT \"%s\" LOGICAL \"%s\"", slot_name, plugin); + if (PQserverVersion(conn) >= 100000) + /* pg_recvlogical doesn't use an exported snapshot, so suppress */ + appendPQExpBuffer(query, " WITHOUT_SNAPSHOT"); + } res = PQexec(conn, query->data); if (PQresultStatus(res) != PGRES_TUPLES_OK) diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h index f27354f..0ce21b9 100644 --- a/src/include/nodes/replnodes.h +++ b/src/include/nodes/replnodes.h @@ -57,6 +57,7 @@ typedef struct CreateReplicationSlotCmd char *plugin; bool temporary; bool reserve_wal; + bool without_snapshot; } CreateReplicationSlotCmd; -- 2.5.5