Skip to content

Commit 7c03078

Browse files
Add pg_recvlogical —-endpos=LSN
Allow pg_recvlogical to specify an ending LSN, complementing the existing -—startpos=LSN option. Craig Ringer, reviewed by Euler Taveira and Naoki Okano
1 parent 698127a commit 7c03078

File tree

2 files changed

+164
-15
lines changed

2 files changed

+164
-15
lines changed

doc/src/sgml/ref/pg_recvlogical.sgml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ PostgreSQL documentation
3838
constraints as <xref linkend="app-pgreceivexlog">, plus those for logical
3939
replication (see <xref linkend="logicaldecoding">).
4040
</para>
41+
42+
<para>
43+
<command>pg_recvlogical</> has no equivalent to the logical decoding
44+
SQL interface's peek and get modes. It sends replay confirmations for
45+
data lazily as it receives it and on clean exit. To examine pending data on
46+
a slot without consuming it, use
47+
<link linkend="functions-replication"><function>pg_logical_slot_peek_changes</></>.
48+
</para>
4149
</refsect1>
4250

4351
<refsect1>
@@ -154,6 +162,32 @@ PostgreSQL documentation
154162
</listitem>
155163
</varlistentry>
156164

165+
<varlistentry>
166+
<term><option>-E <replaceable>lsn</replaceable></option></term>
167+
<term><option>--endpos=<replaceable>lsn</replaceable></option></term>
168+
<listitem>
169+
<para>
170+
In <option>--start</option> mode, automatically stop replication
171+
and exit with normal exit status 0 when receiving reaches the
172+
specified LSN. If specified when not in <option>--start</option>
173+
mode, an error is raised.
174+
</para>
175+
176+
<para>
177+
If there's a record with LSN exactly equal to <replaceable>lsn</>,
178+
the record will be output.
179+
</para>
180+
181+
<para>
182+
The <option>--endpos</option> option is not aware of transaction
183+
boundaries and may truncate output partway through a transaction.
184+
Any partially output transaction will not be consumed and will be
185+
replayed again when the slot is next read from. Individual messages
186+
are never truncated.
187+
</para>
188+
</listitem>
189+
</varlistentry>
190+
157191
<varlistentry>
158192
<term><option>--if-not-exists</option></term>
159193
<listitem>

src/bin/pg_basebackup/pg_recvlogical.c

Lines changed: 130 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ static int noloop = 0;
4040
static int standby_message_timeout = 10 * 1000; /* 10 sec = default */
4141
static int fsync_interval = 10 * 1000; /* 10 sec = default */
4242
static XLogRecPtr startpos = InvalidXLogRecPtr;
43+
static XLogRecPtr endpos = InvalidXLogRecPtr;
4344
static bool do_create_slot = false;
4445
static bool slot_exists_ok = false;
4546
static bool do_start_slot = false;
@@ -63,6 +64,9 @@ static XLogRecPtr output_fsync_lsn = InvalidXLogRecPtr;
6364
static void usage(void);
6465
static void StreamLogicalLog(void);
6566
static void disconnect_and_exit(int code);
67+
static bool flushAndSendFeedback(PGconn *conn, TimestampTz *now);
68+
static void prepareToTerminate(PGconn *conn, XLogRecPtr endpos,
69+
bool keepalive, XLogRecPtr lsn);
6670

6771
static void
6872
usage(void)
@@ -81,6 +85,7 @@ usage(void)
8185
" time between fsyncs to the output file (default: %d)\n"), (fsync_interval / 1000));
8286
printf(_(" --if-not-exists do not error if slot already exists when creating a slot\n"));
8387
printf(_(" -I, --startpos=LSN where in an existing slot should the streaming start\n"));
88+
printf(_(" -E, --endpos=LSN exit after receiving the specified LSN\n"));
8489
printf(_(" -n, --no-loop do not loop on connection lost\n"));
8590
printf(_(" -o, --option=NAME[=VALUE]\n"
8691
" pass option NAME with optional value VALUE to the\n"
@@ -281,6 +286,7 @@ StreamLogicalLog(void)
281286
int bytes_written;
282287
int64 now;
283288
int hdr_len;
289+
XLogRecPtr cur_record_lsn = InvalidXLogRecPtr;
284290

285291
if (copybuf != NULL)
286292
{
@@ -454,6 +460,7 @@ StreamLogicalLog(void)
454460
int pos;
455461
bool replyRequested;
456462
XLogRecPtr walEnd;
463+
bool endposReached = false;
457464

458465
/*
459466
* Parse the keepalive message, enclosed in the CopyData message.
@@ -476,18 +483,32 @@ StreamLogicalLog(void)
476483
}
477484
replyRequested = copybuf[pos];
478485

479-
/* If the server requested an immediate reply, send one. */
480-
if (replyRequested)
486+
if (endpos != InvalidXLogRecPtr && walEnd >= endpos)
481487
{
482-
/* fsync data, so we send a recent flush pointer */
483-
if (!OutputFsync(now))
484-
goto error;
488+
/*
489+
* If there's nothing to read on the socket until a keepalive
490+
* we know that the server has nothing to send us; and if
491+
* walEnd has passed endpos, we know nothing else can have
492+
* committed before endpos. So we can bail out now.
493+
*/
494+
endposReached = true;
495+
}
485496

486-
now = feGetCurrentTimestamp();
487-
if (!sendFeedback(conn, now, true, false))
497+
/* Send a reply, if necessary */
498+
if (replyRequested || endposReached)
499+
{
500+
if (!flushAndSendFeedback(conn, &now))
488501
goto error;
489502
last_status = now;
490503
}
504+
505+
if (endposReached)
506+
{
507+
prepareToTerminate(conn, endpos, true, InvalidXLogRecPtr);
508+
time_to_abort = true;
509+
break;
510+
}
511+
491512
continue;
492513
}
493514
else if (copybuf[0] != 'w')
@@ -497,7 +518,6 @@ StreamLogicalLog(void)
497518
goto error;
498519
}
499520

500-
501521
/*
502522
* Read the header of the XLogData message, enclosed in the CopyData
503523
* message. We only need the WAL location field (dataStart), the rest
@@ -515,12 +535,23 @@ StreamLogicalLog(void)
515535
}
516536

517537
/* Extract WAL location for this block */
518-
{
519-
XLogRecPtr temp = fe_recvint64(&copybuf[1]);
538+
cur_record_lsn = fe_recvint64(&copybuf[1]);
520539

521-
output_written_lsn = Max(temp, output_written_lsn);
540+
if (endpos != InvalidXLogRecPtr && cur_record_lsn > endpos)
541+
{
542+
/*
543+
* We've read past our endpoint, so prepare to go away being
544+
* cautious about what happens to our output data.
545+
*/
546+
if (!flushAndSendFeedback(conn, &now))
547+
goto error;
548+
prepareToTerminate(conn, endpos, false, cur_record_lsn);
549+
time_to_abort = true;
550+
break;
522551
}
523552

553+
output_written_lsn = Max(cur_record_lsn, output_written_lsn);
554+
524555
bytes_left = r - hdr_len;
525556
bytes_written = 0;
526557

@@ -557,10 +588,29 @@ StreamLogicalLog(void)
557588
strerror(errno));
558589
goto error;
559590
}
591+
592+
if (endpos != InvalidXLogRecPtr && cur_record_lsn == endpos)
593+
{
594+
/* endpos was exactly the record we just processed, we're done */
595+
if (!flushAndSendFeedback(conn, &now))
596+
goto error;
597+
prepareToTerminate(conn, endpos, false, cur_record_lsn);
598+
time_to_abort = true;
599+
break;
600+
}
560601
}
561602

562603
res = PQgetResult(conn);
563-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
604+
if (PQresultStatus(res) == PGRES_COPY_OUT)
605+
{
606+
/*
607+
* We're doing a client-initiated clean exit and have sent CopyDone to
608+
* the server. We've already sent replay confirmation and fsync'd so
609+
* we can just clean up the connection now.
610+
*/
611+
goto error;
612+
}
613+
else if (PQresultStatus(res) != PGRES_COMMAND_OK)
564614
{
565615
fprintf(stderr,
566616
_("%s: unexpected termination of replication stream: %s"),
@@ -638,6 +688,7 @@ main(int argc, char **argv)
638688
{"password", no_argument, NULL, 'W'},
639689
/* replication options */
640690
{"startpos", required_argument, NULL, 'I'},
691+
{"endpos", required_argument, NULL, 'E'},
641692
{"option", required_argument, NULL, 'o'},
642693
{"plugin", required_argument, NULL, 'P'},
643694
{"status-interval", required_argument, NULL, 's'},
@@ -673,7 +724,7 @@ main(int argc, char **argv)
673724
}
674725
}
675726

676-
while ((c = getopt_long(argc, argv, "f:F:nvd:h:p:U:wWI:o:P:s:S:",
727+
while ((c = getopt_long(argc, argv, "f:F:nvd:h:p:U:wWI:E:o:P:s:S:",
677728
long_options, &option_index)) != -1)
678729
{
679730
switch (c)
@@ -733,6 +784,16 @@ main(int argc, char **argv)
733784
}
734785
startpos = ((uint64) hi) << 32 | lo;
735786
break;
787+
case 'E':
788+
if (sscanf(optarg, "%X/%X", &hi, &lo) != 2)
789+
{
790+
fprintf(stderr,
791+
_("%s: could not parse end position \"%s\"\n"),
792+
progname, optarg);
793+
exit(1);
794+
}
795+
endpos = ((uint64) hi) << 32 | lo;
796+
break;
736797
case 'o':
737798
{
738799
char *data = pg_strdup(optarg);
@@ -857,6 +918,16 @@ main(int argc, char **argv)
857918
exit(1);
858919
}
859920

921+
if (endpos != InvalidXLogRecPtr && !do_start_slot)
922+
{
923+
fprintf(stderr,
924+
_("%s: --endpos may only be specified with --start\n"),
925+
progname);
926+
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
927+
progname);
928+
exit(1);
929+
}
930+
860931
#ifndef WIN32
861932
pqsignal(SIGINT, sigint_handler);
862933
pqsignal(SIGHUP, sighup_handler);
@@ -923,8 +994,8 @@ main(int argc, char **argv)
923994
if (time_to_abort)
924995
{
925996
/*
926-
* We've been Ctrl-C'ed. That's not an error, so exit without an
927-
* errorcode.
997+
* We've been Ctrl-C'ed or reached an exit limit condition. That's
998+
* not an error, so exit without an errorcode.
928999
*/
9291000
disconnect_and_exit(0);
9301001
}
@@ -943,3 +1014,47 @@ main(int argc, char **argv)
9431014
}
9441015
}
9451016
}
1017+
1018+
/*
1019+
* Fsync our output data, and send a feedback message to the server. Returns
1020+
* true if successful, false otherwise.
1021+
*
1022+
* If successful, *now is updated to the current timestamp just before sending
1023+
* feedback.
1024+
*/
1025+
static bool
1026+
flushAndSendFeedback(PGconn *conn, TimestampTz *now)
1027+
{
1028+
/* flush data to disk, so that we send a recent flush pointer */
1029+
if (!OutputFsync(*now))
1030+
return false;
1031+
*now = feGetCurrentTimestamp();
1032+
if (!sendFeedback(conn, *now, true, false))
1033+
return false;
1034+
1035+
return true;
1036+
}
1037+
1038+
/*
1039+
* Try to inform the server about of upcoming demise, but don't wait around or
1040+
* retry on failure.
1041+
*/
1042+
static void
1043+
prepareToTerminate(PGconn *conn, XLogRecPtr endpos, bool keepalive, XLogRecPtr lsn)
1044+
{
1045+
(void) PQputCopyEnd(conn, NULL);
1046+
(void) PQflush(conn);
1047+
1048+
if (verbose)
1049+
{
1050+
if (keepalive)
1051+
fprintf(stderr, "%s: endpos %X/%X reached by keepalive\n",
1052+
progname,
1053+
(uint32) (endpos >> 32), (uint32) endpos);
1054+
else
1055+
fprintf(stderr, "%s: endpos %X/%X reached by record at %X/%X\n",
1056+
progname, (uint32) (endpos >> 32), (uint32) (endpos),
1057+
(uint32) (lsn >> 32), (uint32) lsn);
1058+
1059+
}
1060+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy