logical decoding and replication of sequences

Started by Tomas Vondraalmost 5 years ago129 messageshackers
Jump to latest
#1Tomas Vondra
tomas.vondra@2ndquadrant.com

Hi,

One of the existing limitations of logical decoding / replication is
that it does no care about sequences. The annoying consequence is that
after a failover to logical replica, all the table data may be
replicated but the sequences are still at the initial values, requiring
some custom solution that moves the sequences forward enough to prevent
duplicities.

There have been attempts to address this in the past, most recently [1]/messages/by-id/1710ed7e13b.cd7177461430746.3372264562543607781@highgo.ca,
but none of them got in due to various issues.

This is an attempt, based on [1]/messages/by-id/1710ed7e13b.cd7177461430746.3372264562543607781@highgo.ca (but with many significant parts added
or reworked), aiming to deal with this. The primary purpose of sharing
it is getting feedback and opinions on the design decisions. It's still
a WIP - it works fine AFAICS, but some of the bits may be a bit hackish.

The overall goal is to have the same sequence data on the primary and
logical replica, or something sufficiently close to that, so that the
replica after a failover does not generate duplicate values.

This patch does a couple basic things:

1) extends the logical decoding to handle sequences. It adds a new
callback, similarly to what we have for messages. There's a bit of
complexity with transactional and non-transactional behavior, more
about that later

2) extends test_decoding to support this new callback, printing the
sequence increments (the decoded WAL records)

3) extends built-in replication to support sequences, so publications
may contain both tables and sequences, etc., sequences data sync
when creating subscriptions, etc.

transactional vs. non-transactional
-----------------------------------

The first part (extending logical decoding) is simple in principle. We
simply decode the sequence updates, but then comes a challenge - should
we just treat it transactionally and stash it in reorder buffer, or
just pass it to the output plugin right-away?

For messages, this can be specified as a flag when adding the message,
so the user can decide depending on the message purpose. For sequences,
all we do is nextval() and it depends on the context in which it's used,
we can't just pick one of those approaches.

Consider this, for example:

CREATE SEQUENCE s;
BEGIN;
SELECT nextval('s') FROM generate_series(1,1000) s(i);
ROLLBACK;

If we handle this "transactionally", we'd stash the "nextval" increment
into the transaction, and then discard it due to the rollback, so the
output plugin (and replica) would never get it. So this is an argument
for non-transactional behavior.

On the other hand, consider this:

CREATE SEQUENCE s;
BEGIN;
ALTER SEQUENCE s RESTART WITH 2000;
SELECT nextval('s') FROM generate_series(1,1000) s(i);
ROLLBACK;

In this case the ALTER creates a new relfilenode, and the ROLLBACK does
discard it including the effects of the nextval calls. So here we should
treat it transactionally, stash the increment(s) in the transaction and
just discard it all on rollback.

A somewhat similar example is this

BEGIN;
CREATE SEQUENCE s;
SELECT nextval('s') FROM generate_series(1,1000) s(i);
COMMIT;

Again - the decoded nextval needs to be handled transactionally, because
otherwise it's going to be very difficult for custom plugins to combine
this with DDL replication.

So the patch does a fairly simple thing:

1) By default, sequences are treated non-transactionally, i.e. sent to
the output plugin right away.

2) We track sequences created in running (sub)transactions, and those
are handled transactionally. This includes ALTER SEQUENCE cases,
which create a new relfilenode, which is used as an identifier.

It's a bit more complex, because of cases like this:

BEGIN;
CREATE SEQUENCE s;
SAVEPOINT a;
SELECT nextval('s') FROM generate_series(1,1000) s(i);
ROLLBACK TO a;
COMMIT;

because we must not discard the nextval changes - this is handled by
always stashing the nextval changes to the subxact where the sequence
relfilenode was created.

The tracking is a bit cumbersome - there's a hash table with relfilenode
mapped to XID in which it was created. AFAIK that works, but might be
an issue with many sequences created in running transactions. Not sure.

detecting sequence creation
---------------------------

Detection that a sequence (or rather the relfilenode) was created is
done by adding a "created" flag into the xl_seq_rec, and setting it to
"true" in the first WAL record after the creation. There might be some
other way, but this seemed simple enough.

applying the sequence (ResetSequence2)
--------------------------------------

The decoding pretty much just extracts log_value, log_cnt and is_called
from the sequence, and passes them to the output plugin. On the replica
we extract those from the message, and write them to the local sequence
using a new ResetSequence2 function.

It's possible we don't really need log_cnt and is_called. After all,
log_cnt is zero most of the time anyway, and the worst thing that could
happen if we ignore it is we skip a couple values (which seems fine).

syncing sequences in a subscription
-----------------------------------

After creating a subscription, the sequences get syncronized just like
tables. This part ia a bit hacked together, and there's definitely room
for improvement - e.g. a new bgworker is started for each sequence, as
we simply treat both tabels and sequences as "relation". But all we need
to do for sequences is copying the (last_value, log_cnt, is_called) and
calling ResetSequence2, so maybe we could sync all sequences in a single
worker, or something like that.

new "sequence" publication action
---------------------------------

The publications now have a new "sequence" publication action, which is
enabled by default. This determines whether the publication decodes
sequences or what.

FOR ALL SEQUENCES
-----------------

It should be possible to create FOR ALL SEQUENCES publications, just
like we have FOR ALL TABLES. But this produces shift/reduce conflicts
in the grammar, and I didn't bother dealing with that. So for now it's
required to do ALTER PUBLICATION ... [ADD | DROP] SEQUENCE ...

no streaming support yet
------------------------

There's no supoprt for streaming of in-progress transactions yet, but
should be trivial to add.

GetCurrentTransactionId() in nextval
------------------------------------

There's a bit annoying behavior of nextval() - if you do this:

BEGIN;
CREATE SEQUENCE s;
SAVEPOINT a;
SELECT nextval('s') FROM generate_series(1,100) s(i);
COMMIT;

then the WAL record for nextval (right after the savepoint) will have
XID 0 (easy to see in pg_waldump). That's kinda strange, and it causes
problems in DecodeSequence() when calling

SnapBuildProcessChange(builder, xid, buf->origptr)

for transactional changes, because that expects a valid XID. Fixing
this required adding GetCurrentTransactionId() to nextval() and two
other functions, which were only doing

if (RelationNeedsWAL(seqrel))
GetTopTransactionId();

so far. I'm not sure if this has some particularly bad consequences.

regards

[1]: /messages/by-id/1710ed7e13b.cd7177461430746.3372264562543607781@highgo.ca
/messages/by-id/1710ed7e13b.cd7177461430746.3372264562543607781@highgo.ca

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

sequence-decoding-20210608.patchtext/x-patch; charset=UTF-8; name=sequence-decoding-20210608.patchDownload+2370-46
#2Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#1)
Re: logical decoding and replication of sequences

Hi,

Seems the cfbot was not entirely happy with the patch on some platforms,
so here's a fixed version. There was a bogus call to ensure_transaction
function (which does not exist at all) and a silly bug in transaction
management in apply_handle_sequence.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0001-Logical-decoding-replication-of-sequences-20210613.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-replication-of-sequences-20210613.patchDownload+2391-47
#3Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#2)
Re: logical decoding and replication of sequences

A rebased patch, addressing a minor bitrot due to 4daa140a2f5.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#4Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#3)
Re: logical decoding and replication of sequences

On 6/23/21 4:14 PM, Tomas Vondra wrote:

A rebased patch, addressing a minor bitrot due to 4daa140a2f5.

Meh, forgot to attach the patch as usual, of course ...

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0001-Logical-decoding-replication-of-sequences-20210623.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-replication-of-sequences-20210623.patchDownload+2389-47
#5vignesh C
vignesh21@gmail.com
In reply to: Tomas Vondra (#4)
Re: logical decoding and replication of sequences

On Wed, Jun 23, 2021 at 7:55 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

On 6/23/21 4:14 PM, Tomas Vondra wrote:

A rebased patch, addressing a minor bitrot due to 4daa140a2f5.

Meh, forgot to attach the patch as usual, of course ...

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Regards,
Vignesh

#6Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#1)
Re: logical decoding and replication of sequences

On 08.06.21 00:28, Tomas Vondra wrote:

new "sequence" publication action
---------------------------------

The publications now have a new "sequence" publication action, which is
enabled by default. This determines whether the publication decodes
sequences or what.

FOR ALL SEQUENCES
-----------------

It should be possible to create FOR ALL SEQUENCES publications, just
like we have FOR ALL TABLES. But this produces shift/reduce conflicts
in the grammar, and I didn't bother dealing with that. So for now it's
required to do ALTER PUBLICATION ... [ADD | DROP] SEQUENCE ...

I have been thinking about these DDL-level issues a bit. The most
common use case will be to have a bunch of tables with implicit
sequences, and you just want to replicate them from here to there
without too much effort. So ideally an implicit sequence should be
replicated by default if the table is part of a publication (unless
sequences are turned off by the publication option).

We already have support for things like that in
GetPublicationRelations(), where a partitioned table is expanded to
include the actual partitions. I think that logic could be reused. So
in general I would have GetPublicationRelations() include sequences and
don't have GetPublicationSequenceRelations() at all. Then sequences
could also be sent by pg_publication_tables(), maybe add a relkind
column. And then you also don't need so much duplicate DDL code, if you
just consider everything as a relation. For example, there doesn't seem
to be an actual need to have fetch_sequence_list() and subsequent
processing on the subscriber side. It does the same thing as
fetch_table_list(), so it might as well just all be one thing.

We do, however, probably need some checking that we don't replicate
tables to sequences or vice versa.

We probably also don't need a separate FOR ALL SEQUENCES option. What
users really want is a "for everything" option. We could think about
renaming or alternative syntax, but in principle I think FOR ALL TABLES
should include sequences by default.

Tests under src/test/subscription/ are needed.

I'm not sure why test_decoding needs a skip-sequences option. The
source code says it's for backward compatibility, but I don't see why we
need that.

#7Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#6)
Re: logical decoding and replication of sequences

On 7/20/21 5:30 PM, Peter Eisentraut wrote:

On 08.06.21 00:28, Tomas Vondra wrote:

new "sequence" publication action
---------------------------------

The publications now have a new "sequence" publication action, which is
enabled by default. This determines whether the publication decodes
sequences or what.

FOR ALL SEQUENCES
-----------------

It should be possible to create FOR ALL SEQUENCES publications, just
like we have FOR ALL TABLES. But this produces shift/reduce conflicts
in the grammar, and I didn't bother dealing with that. So for now it's
required to do ALTER PUBLICATION ... [ADD | DROP] SEQUENCE ...

I have been thinking about these DDL-level issues a bit.  The most
common use case will be to have a bunch of tables with implicit
sequences, and you just want to replicate them from here to there
without too much effort.  So ideally an implicit sequence should be
replicated by default if the table is part of a publication (unless
sequences are turned off by the publication option).

Agreed, that seems like a reasonable approach.

We already have support for things like that in
GetPublicationRelations(), where a partitioned table is expanded to
include the actual partitions.  I think that logic could be reused.  So
in general I would have GetPublicationRelations() include sequences and
don't have GetPublicationSequenceRelations() at all.  Then sequences
could also be sent by pg_publication_tables(), maybe add a relkind
column.  And then you also don't need so much duplicate DDL code, if you
just consider everything as a relation.  For example, there doesn't seem
to be an actual need to have fetch_sequence_list() and subsequent
processing on the subscriber side.  It does the same thing as
fetch_table_list(), so it might as well just all be one thing.

Not sure. I agree with replicating implicit sequences by default,
without having to add them to the publication. But I think we should
allow adding other sequences too, and I think some of this code and
differentiation from tables will be needed.

FWIW I'm not claiming there are no duplicate parts - I've mostly
copy-pasted the table-handling code for sequences, and I'll look into
reusing some of it.

We do, however, probably need some checking that we don't replicate
tables to sequences or vice versa.

True. I haven't tried doing such silly things yet.

We probably also don't need a separate FOR ALL SEQUENCES option.  What
users really want is a "for everything" option.  We could think about
renaming or alternative syntax, but in principle I think FOR ALL TABLES
should include sequences by default.

Tests under src/test/subscription/ are needed.

Yeah, true. At the moment there are just tests in test_decoding, mostly
because the previous patch versions did not add support for sequences to
built-in replication. Will fix.

I'm not sure why test_decoding needs a skip-sequences option.  The
source code says it's for backward compatibility, but I don't see why we
need that.

Hmmm, I'm a bit baffled by skip-sequences true. I think Cary added it to
limit chances in test_decoding tests, while the misleading comment about
backwards compatibility comes from me.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: vignesh C (#5)
Re: logical decoding and replication of sequences

Here's a rebased version of the patch, no other changes.

I think the crucial aspect of this that needs discussion/feedback the
most is the transactional vs. non-transactional behavior. All the other
questions are less important / cosmetic.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0001-Logical-decoding-replication-of-sequences-20210720.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-replication-of-sequences-20210720.patchDownload+2389-47
#9Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#8)
Re: logical decoding and replication of sequences

Hi,

Here's a an updated version of this patch - rebased to current master,
and fixing some of the issues raised in Peter's review.

Mainly, this adds a TAP test to src/test/subscription, focusing on
testing the various situations with transactional and non-transactional
behavior (with subtransactions and various ROLLBACK versions).

This new TAP test however uncovered an issue with wait_for_catchup(),
because that uses pg_current_wal_lsn() to wait for replication of all
the changes. But that does not work when the sequence gets advanced in a
transaction that is then aborted, e.g. like this:

BEGIN;
SELECT nextval('s') FROM generate_series(1,100);
ROLLBACK;

The root cause is that pg_current_wal_lsn() uses the LogwrtResult.Write,
which is updated by XLogFlush() - but only in RecordTransactionCommit.
Which makes sense, because only the committed stuff is "visible". But
the non-transactional behavior changes this, because now some of the
changes from aborted transactions may need to be replicated. Which means
the wait_for_catchup() ends up not waiting for the sequence change.

One option would be adding XLogFlush() to RecordTransactionAbort(), but
my guess is we don't do that intentionally (even though aborts should be
fairly rare in most workloads).

I'm not entirely sure changing this (replicating changes from aborted
xacts) is a good idea. Maybe it'd be better to replicate this "lazily" -
instead of replicating the advances right away, we might remember which
sequences were advanced in the transaction, and then replicate current
state for those sequences at commit time.

The idea is that if an increment is "invisible" we probably don't need
to replicate it, it's fine to replicate the next "visible" increment. So
for example given

BEGIN;
SELECT nextval('s');
ROLLBACK;

BEGIN;
SELECT nextval('s');
COMMIT;

we don't need to replicate the first change, but we need to replicate
the second one.

The trouble is we don't decode individual sequence advances, just those
that update the sequence tuple (so every 32 values or so). So we'd need
to remeber the first increment, in a way that is (a) persistent across
restarts and (b) shared by all backends.

The other challenge seems to be ordering of the changes - at the moment
we have no issues with this, because increments on the same sequence are
replicated immediately, in the WAL order. But postponing them to commit
time would affect this order.

I've also briefly looked at the code duplication - there's a couple
functions in the patch that I shamelessly copy-pasted and tweaked to
handle sequences instead of tables:

publicationcmds.c
-----------------

1) OpenTableList/CloseTableList - > OpenSequenceList/CloseSequenceList

Trivial differences, trivial to get rid of - the only difference is
pretty much just table_open vs. relation open.

2) AlterPublicationTables -> AlterPublicationSequences

This is a bit more complicated, because the tables also handle
inheritance (which I think does not apply to sequences). Other than
that, it's calling the functions from (1).

subscriptioncmds.c
------------------

1) fetch_table_list, fetch_sequence_list

Minimal differences, essentially just the catalog name.

2) AlterSubscription_refresh

A lot of duplication, but the code handling tables and sequences is
almost exactly the same and can be reused fairly easily (moved to a
separate function, called for tables and then sequences).

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0001-Logical-decoding-replication-of-sequences-20210729.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-replication-of-sequences-20210729.patchDownload+2578-35
#10Robert Haas
robertmhaas@gmail.com
In reply to: Tomas Vondra (#8)
Re: logical decoding and replication of sequences

On Tue, Jul 20, 2021 at 5:41 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

I think the crucial aspect of this that needs discussion/feedback the
most is the transactional vs. non-transactional behavior. All the other
questions are less important / cosmetic.

Yeah, it seems really tricky to me to get this right. The hard part
is, I think, mostly figuring out what the right behavior really is.

DDL in PostgreSQL is transactional. Non-DDL operations on sequences
are non-transactional. If a given transaction does only one of those
things, it seems clear enough what to do, but when the same
(sub)transaction does both, it gets messy. I'd be tempted to think
about something like:

1. When a transaction performs only non-transactional operations on
sequences, they are emitted immediately.

2. If a transaction performs transactional operations on sequences,
the decoded operations acquire a dependency on the transaction and
cannot be emitted until that transaction is fully decoded. When commit
or abort of that XID is reached, emit the postponed non-transactional
operations at that point.

I think this is similar to what you've designed, but I'm not sure that
it's exactly equivalent. I think in particular that it may be better
to insist that all of these operations are non-transactional and that
the debate is only about when they can be sent, rather than trying to
sort of convert them into an equivalent series of transactional
operations. That approach seems confusing especially in the case where
some (sub)transactions abort.

But this is just my $0.02.

--
Robert Haas
EDB: http://www.enterprisedb.com

#11Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#9)
Re: logical decoding and replication of sequences

On 30.07.21 20:26, Tomas Vondra wrote:

Here's a an updated version of this patch - rebased to current master,
and fixing some of the issues raised in Peter's review.

This patch needs an update, as various conflicts have arisen now.

As was discussed before, it might be better to present a separate patch
for just the logical decoding part for now, since the replication and
DDL stuff has the potential to conflict heavily with other patches being
discussed right now. It looks like cutting this patch in two should be
doable easily.

I looked through the test cases in test_decoding again. It all looks
pretty sensible. If anyone can think of any other tricky or dubious
cases, we can add them there. It's easiest to discuss these things with
concrete test cases rather than in theory.

One slightly curious issue is that this can make sequence values go
backwards, when seen by the logical decoding consumer, like in the test
case:

+ BEGIN
+ sequence: public.test_sequence transactional: 1 created: 1 last_value: 
1, log_cnt: 0 is_called: 0
+ COMMIT
+ sequence: public.test_sequence transactional: 0 created: 0 last_value: 
33, log_cnt: 0 is_called: 1
+ BEGIN
+ sequence: public.test_sequence transactional: 1 created: 1 last_value: 
4, log_cnt: 0 is_called: 1
+ COMMIT
+ sequence: public.test_sequence transactional: 0 created: 0 last_value: 
334, log_cnt: 0 is_called: 1

I suppose that's okay, since it's not really the intention that someone
is concurrently consuming sequence values on the subscriber. Maybe
something for the future. Fixing that would require changing the way
transactional sequence DDL updates these values, so it's not directly
the job of the decoding to address this.

A small thing I found: Maybe the text that test_decoding produces for
sequences can be made to look more consistent with the one for tables.
For example, in

+ BEGIN
+ sequence: public.test_table_a_seq transactional: 1 created: 1 
last_value: 1, log_cnt: 0 is_called: 0
+ sequence: public.test_table_a_seq transactional: 1 created: 0 
last_value: 33, log_cnt: 0 is_called: 1
+ table public.test_table: INSERT: a[integer]:1 b[integer]:100
+ table public.test_table: INSERT: a[integer]:2 b[integer]:200
+ COMMIT

note how the punctuation is different.

#12Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#11)
Re: logical decoding and replication of sequences

Hi,

On 9/23/21 12:27 PM, Peter Eisentraut wrote:

On 30.07.21 20:26, Tomas Vondra wrote:

Here's a an updated version of this patch - rebased to current master,
and fixing some of the issues raised in Peter's review.

This patch needs an update, as various conflicts have arisen now.

As was discussed before, it might be better to present a separate patch
for just the logical decoding part for now, since the replication and
DDL stuff has the potential to conflict heavily with other patches being
discussed right now.  It looks like cutting this patch in two should be
doable easily.

Attached is the rebased patch, split into three parts:

1) basic decoding infrastructure (decoding, reorderbuffer etc.)
2) support for sequences in test_decoding
3) support for sequences in built-in replication (catalogs, syntax, ...)

The last part is the largest one - I'm sure we'll have discussions about
the grammar, adding sequences automatically, etc. But as you said, let's
focus on the first part, which deals with the required decoding stuff.

I've added a couple comments, explaining how we track sequences, why we
need the XID in nextval() etc. I've also added streaming support.

I looked through the test cases in test_decoding again.  It all looks
pretty sensible.  If anyone can think of any other tricky or dubious
cases, we can add them there.  It's easiest to discuss these things with
concrete test cases rather than in theory.

One slightly curious issue is that this can make sequence values go
backwards, when seen by the logical decoding consumer, like in the test
case:

+ BEGIN
+ sequence: public.test_sequence transactional: 1 created: 1 last_value: 
1, log_cnt: 0 is_called: 0
+ COMMIT
+ sequence: public.test_sequence transactional: 0 created: 0 last_value: 
33, log_cnt: 0 is_called: 1
+ BEGIN
+ sequence: public.test_sequence transactional: 1 created: 1 last_value: 
4, log_cnt: 0 is_called: 1
+ COMMIT
+ sequence: public.test_sequence transactional: 0 created: 0 last_value: 
334, log_cnt: 0 is_called: 1

I suppose that's okay, since it's not really the intention that someone
is concurrently consuming sequence values on the subscriber.  Maybe
something for the future.  Fixing that would require changing the way
transactional sequence DDL updates these values, so it's not directly
the job of the decoding to address this.

Yeah, that's due to how ALTER SEQUENCE does things, and I agree redoing
that seems well out of scope for this patch. What seems a bit suspicious
is that some of the ALTER SEQUENCE changes have "created: 1" - it's
probably correct, though, because those ALTER SEQUENCE statements can be
rolled-back, so we see it as if a new sequence is created. The flag name
might be a bit confusing, though.

A small thing I found: Maybe the text that test_decoding produces for
sequences can be made to look more consistent with the one for tables.
For example, in

+ BEGIN
+ sequence: public.test_table_a_seq transactional: 1 created: 1 
last_value: 1, log_cnt: 0 is_called: 0
+ sequence: public.test_table_a_seq transactional: 1 created: 0 
last_value: 33, log_cnt: 0 is_called: 1
+ table public.test_table: INSERT: a[integer]:1 b[integer]:100
+ table public.test_table: INSERT: a[integer]:2 b[integer]:200
+ COMMIT

note how the punctuation is different.

I did tweak this a bit, hopefully it's more consistent.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0001-Logical-decoding-of-sequences-20210924.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-of-sequences-20210924.patchDownload+793-9
0002-Add-support-for-decoding-sequences-to-test_-20210924.patchtext/x-patch; charset=UTF-8; name=0002-Add-support-for-decoding-sequences-to-test_-20210924.patchDownload+517-2
0003-Add-support-for-decoding-sequences-to-built-20210924.patchtext/x-patch; charset=UTF-8; name=0003-Add-support-for-decoding-sequences-to-built-20210924.patchDownload+1451-26
#13Hannu Krosing
hannu@tm.ee
In reply to: Tomas Vondra (#12)
Re: logical decoding and replication of sequences

Just a note for some design decisions

1) By default, sequences are treated non-transactionally, i.e. sent to the output plugin right away.

If our aim is just to make sure that all user-visible data in
*transactional* tables is consistent with sequence state then one
very much simplified approach to this could be to track the results of
nextval() calls in a transaction at COMMIT put the latest sequence
value in WAL (or just track the sequences affected and put the latest
sequence state in WAL at commit which needs extra read of sequence but
protects against race conditions with parallel transactions which get
rolled back later)

This avoids sending redundant changes for multiple nextval() calls
(like loading a million-row table with sequence-generated id column)

And one can argue that we can safely ignore anything in ROLLBACKED
sequences. This is assuming that even if we did advance the sequence
paste the last value sent by the latest COMMITTED transaction it does
not matter for database consistency.

It can matter if customers just call nextval() in rolled-back
transactions and somehow expect these values to be replicated based on
reasoning along "sequences are not transactional - so rollbacks should
not matter" .

Or we may get away with most in-detail sequence tracking on the source
if we just keep track of the xmin of the sequence and send the
sequence info over at commit if it == current_transaction_id ?

-----
Hannu Krosing
Google Cloud - We have a long list of planned contributions and we are hiring.
Contact me if interested.

On Fri, Sep 24, 2021 at 9:16 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

Show quoted text

Hi,

On 9/23/21 12:27 PM, Peter Eisentraut wrote:

On 30.07.21 20:26, Tomas Vondra wrote:

Here's a an updated version of this patch - rebased to current master,
and fixing some of the issues raised in Peter's review.

This patch needs an update, as various conflicts have arisen now.

As was discussed before, it might be better to present a separate patch
for just the logical decoding part for now, since the replication and
DDL stuff has the potential to conflict heavily with other patches being
discussed right now. It looks like cutting this patch in two should be
doable easily.

Attached is the rebased patch, split into three parts:

1) basic decoding infrastructure (decoding, reorderbuffer etc.)
2) support for sequences in test_decoding
3) support for sequences in built-in replication (catalogs, syntax, ...)

The last part is the largest one - I'm sure we'll have discussions about
the grammar, adding sequences automatically, etc. But as you said, let's
focus on the first part, which deals with the required decoding stuff.

I've added a couple comments, explaining how we track sequences, why we
need the XID in nextval() etc. I've also added streaming support.

I looked through the test cases in test_decoding again. It all looks
pretty sensible. If anyone can think of any other tricky or dubious
cases, we can add them there. It's easiest to discuss these things with
concrete test cases rather than in theory.

One slightly curious issue is that this can make sequence values go
backwards, when seen by the logical decoding consumer, like in the test
case:

+ BEGIN
+ sequence: public.test_sequence transactional: 1 created: 1 last_value:
1, log_cnt: 0 is_called: 0
+ COMMIT
+ sequence: public.test_sequence transactional: 0 created: 0 last_value:
33, log_cnt: 0 is_called: 1
+ BEGIN
+ sequence: public.test_sequence transactional: 1 created: 1 last_value:
4, log_cnt: 0 is_called: 1
+ COMMIT
+ sequence: public.test_sequence transactional: 0 created: 0 last_value:
334, log_cnt: 0 is_called: 1

I suppose that's okay, since it's not really the intention that someone
is concurrently consuming sequence values on the subscriber. Maybe
something for the future. Fixing that would require changing the way
transactional sequence DDL updates these values, so it's not directly
the job of the decoding to address this.

Yeah, that's due to how ALTER SEQUENCE does things, and I agree redoing
that seems well out of scope for this patch. What seems a bit suspicious
is that some of the ALTER SEQUENCE changes have "created: 1" - it's
probably correct, though, because those ALTER SEQUENCE statements can be
rolled-back, so we see it as if a new sequence is created. The flag name
might be a bit confusing, though.

A small thing I found: Maybe the text that test_decoding produces for
sequences can be made to look more consistent with the one for tables.
For example, in

+ BEGIN
+ sequence: public.test_table_a_seq transactional: 1 created: 1
last_value: 1, log_cnt: 0 is_called: 0
+ sequence: public.test_table_a_seq transactional: 1 created: 0
last_value: 33, log_cnt: 0 is_called: 1
+ table public.test_table: INSERT: a[integer]:1 b[integer]:100
+ table public.test_table: INSERT: a[integer]:2 b[integer]:200
+ COMMIT

note how the punctuation is different.

I did tweak this a bit, hopefully it's more consistent.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#14Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Hannu Krosing (#13)
Re: logical decoding and replication of sequences

On 9/25/21 22:05, Hannu Krosing wrote:

Just a note for some design decisions

1) By default, sequences are treated non-transactionally, i.e. sent to the output plugin right away.

If our aim is just to make sure that all user-visible data in
*transactional* tables is consistent with sequence state then one
very much simplified approach to this could be to track the results of
nextval() calls in a transaction at COMMIT put the latest sequence
value in WAL (or just track the sequences affected and put the latest
sequence state in WAL at commit which needs extra read of sequence but
protects against race conditions with parallel transactions which get
rolled back later)

Not sure. TBH I feel rather uneasy about adding more stuff in COMMIT.

This avoids sending redundant changes for multiple nextval() calls
(like loading a million-row table with sequence-generated id column)

Yeah, it'd be nice to have to optimize this a bit, somehow. But I'd bet
it's a negligible amount of data / changes, compared to the table.

And one can argue that we can safely ignore anything in ROLLBACKED
sequences. This is assuming that even if we did advance the sequence
paste the last value sent by the latest COMMITTED transaction it does
not matter for database consistency.

I don't think we can ignore aborted (ROLLBACK) transactions, in the
sense that you can't just discard the increments. Imagine you have this
sequence of transactions:

BEGIN;
SELECT nextval('s'); -- allocates new chunk of values
ROLLBACK;

BEGIN;
SELECT nextval('s'); -- returns one of the cached values
COMMIT;

If you ignore the aborted transaction, then the sequence increment won't
be replicated -- but that's wrong, because user now has a visible
sequence value from that chunk.

So I guess we'd have to maintain a cache of sequences incremented in the
current session, do nothing in aborted transactions (i.e. keep the
contents but don't log anything) and log/reset at commit.

I wonder if multiple sessions make this even more problematic (e.g. due
to session just disconnecting mid transansaction, without writing the
abort record at all). But AFAICS that's not an issue, because the other
session has a separate cache for the sequence.

It can matter if customers just call nextval() in rolled-back
transactions and somehow expect these values to be replicated based on
reasoning along "sequences are not transactional - so rollbacks should
not matter" .

I don't think we guarantee anything for data in transactions that did
not commit, so this seems like a non-issue. I.e. we don't need to go out
of our way to guarantee something we never promised.

Or we may get away with most in-detail sequence tracking on the source
if we just keep track of the xmin of the sequence and send the
sequence info over at commit if it == current_transaction_id ?

Not sure I understand this proposal. Can you explain?

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#15Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#14)
Re: logical decoding and replication of sequences

Hi,

I've spent a bit of time exploring the alternative approach outlined by
Hannu, i.e. tracking sequences accessed by the transaction, and logging
the final state just once at COMMIT. Attached is an experimental version
of the patch series doing that - 0001 does the original approach
(decoding the sequence updates from WAL) and then 0002 reworks it to
this alternative solution. The 0003 and 0004 stay mostly the same,
except for minor fixes. Some of the tests in 0003/0004 fail, because
0002 changes the semantics in various ways (more about that later).

The original approach (0001) may seem complex at first, but in principle
it just decodes changes to the sequence relation, and either stashes
them into transaction (just like other changes) or applies them right
away. I'd say that's the most complicated part - deciding whether the
change is transactional or not.

0002 reworks that so that it doesn't decode the existing WAL records,
but tracks sequences which have been modified (updated on-disk state)
and then accessed in the current transaction. And then at COMMIT time we
write a new WAL message with info about the sequence.

I realized we already cache sequences for each session - seqhashtab in
sequence.c. It doesn't have any concept of a transaction, but it seems
fairly easy to make that possible. I did this by adding two flags

- needs_log - means the seesion advanced the sequence (on disk)
- touched - true if the current xact called nextval() etc.

The idea is that what matters is updates to on-disk state, so whenever
we do that we set needs_log. But it only matters when the changes are
made visible in a committed transaction. Consider for example this:

BEGIN;
SELECT nextval('s') FROM generate_series(1,10000) s(i);
ROLLBACK;
SELECT nextval('s');

The first nextval() call certainly sets both flags to true, at least for
default sequences caching 32 values. But the values are not confirmed to
the user because of the rollback - this resets 'touched' flag, but
leaves 'needs_log' set to true.

And then the next nextval() - which may easily be just from cache - sets
touched=true again, and logs the sequence state at (implicit) commit.
Which resets both flags again.

The logging/cleanup happens in AtEOXact_Sequences() which gets called
before commit/abort. This walks all cached sequences and writes the
state for those with both flags true (or resets flag for abort).

The cache also keeps info about the last "sequence state" in the
session, which is then used when writing into into WAL.

To write the sequence state into WAL, I've added a new WAL record
xl_logical_sequence to RM_LOGICALMSG_ID, next to the xl_logical_message.
It's a bit arbitrary, maybe it should be part of RM_SEQ_ID, but it does
the trick. I don't think this is the main issue and it's easy enough to
move it elsewhere if needed.

So, that seems fairly straight-forward and it may reduce the number of
replication messages for large transactions. Unfortunately, it's not
much simpler compared to the first approach - the amount of code is
about the same, and there's a bunch of other issues.

The main issue seems to be about ordering. Consider multiple sessions
all advancing the sequence. With the "old" approach this was naturally
ordered - the order in which the increments were written to WAL made
sense. But the sessions may advance the sequences in one order and then
commit in a different order, which mixes the updates. Consider for
example this scenario with two concurrent transactions:

T1: nextval('s') -> allocates values [1,32]
T2: nextval('s') -> allocates values [33,64]
T2: commit -> logs [33,64]
T1: commit -> logs [1,32]

The result is the sequence on the replica diverted because it replayed
the increments in the opposite order.

I can think of two ways to fix this. Firstly, we could "merge" the
increments in some smart way, e.g. by discarding values considered
"stale" (like decrements). But that seems pretty fragile, because the
sequence may be altered in various ways, reset, etc. And it seems more
like transferring responsibility to someone else instead of actually
solving the issue.

The other fix is simply reading the current sequence state from disk at
commit and logging that (instead of the values cached from the last
increment). But I'm rather skeptical about doing such things right
before COMMIT.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0004-Add-support-for-decoding-sequences-to-built-20211118.patchtext/x-patch; charset=UTF-8; name=0004-Add-support-for-decoding-sequences-to-built-20211118.patchDownload+1542-33
0003-Add-support-for-decoding-sequences-to-test_-20211118.patchtext/x-patch; charset=UTF-8; name=0003-Add-support-for-decoding-sequences-to-test_-20211118.patchDownload+517-2
0002-rework-20211118.patchtext/x-patch; charset=UTF-8; name=0002-rework-20211118.patchDownload+232-318
0001-Logical-decoding-of-sequences-20211118.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-of-sequences-20211118.patchDownload+793-9
#16Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#15)
Re: logical decoding and replication of sequences

Hi,

Here's a slightly improved version of the patch, fixing a couple issues
I failed to notice. It also addresses a couple of the issues described
in the last message, although mostly to show what would need to be done.

1) Handle sequences dropped in the xact by calling try_relation_open,
and just doing nothing if not found. Otherwise we'd get a failure in
reorderbuffer, when decoding the change.

2) Fixed nextval_internal() to log the correct last_value (the one we
write into WAL).

3) Reread the sequence state in AtEOXact_Sequences, to deal with the
ordering issue described before. This makes (2) somewhat pointless,
because we just read whatever is on disk at that point. But having both
makes it easier to experiment / see what'd happen.

4) Log the stats in DefineSequence() - Without this we'd not have the
initial sequence state in the WAL, because only nextval/setval etc. do
the logging. The old approach (decoding the sequence tuple) does not
have this issue.

The (3) changes the behavior in a somewhat strange way. Consider this
case with two concurrent transactions:

T1: BEGIN;
T2: BEGIN;
T1: SELECT nextval('s') FROM generate_series(1,100) s(i);
T2: SELECT nextval('s') FROM generate_series(1,100) s(i);
T1: COMMIT;
T2: COMMIT;

The result is that both transactions have used the same sequence, and so
will re-read the state from disk. But at that point the state is exactly
the same, so we'll log the same thing twice.

There's a much deeper issue, though. The current patch only logs the
sequence if the session generated WAL when incrementing the sequence
(which happens every 32 values). But other sessions may already use
values from this range, so consider for example this:

T1: BEGIN;
T1: SELECT nextval('s') FROM generate_series(1,100) s(i);
T2: BEGIN;
T2: SELECT nextval('s');
T2: COMMIT;
T1: ROLLBACK;

Which unfortunately means T2 already used a value, but the increment may
not be logged at that time (or ever). This seems like a fatal issue,
because it means we need to log *all* sequences the transaction touches,
not just those that wrote the increment to WAL. That might still work
for large transactions consuming many sequence values, but it's pretty
inefficient for small OLTP transactions that only need one or two values
from the sequence.

So I think just decoding the sequence tuples is a better solution - for
large transactions (consuming many values from the sequence) it may be
more expensive (i.e. send more records to replica). But I doubt that
matters too much - it's likely negligible compared to other data for
large transactions.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

0004-Add-support-for-decoding-sequences-to-built-20211121.patchtext/x-patch; charset=UTF-8; name=0004-Add-support-for-decoding-sequences-to-built-20211121.patchDownload+1542-33
0003-Add-support-for-decoding-sequences-to-test_-20211121.patchtext/x-patch; charset=UTF-8; name=0003-Add-support-for-decoding-sequences-to-test_-20211121.patchDownload+511-2
0002-rework-20211121.patchtext/x-patch; charset=UTF-8; name=0002-rework-20211121.patchDownload+288-318
0001-Logical-decoding-of-sequences-20211121.patchtext/x-patch; charset=UTF-8; name=0001-Logical-decoding-of-sequences-20211121.patchDownload+793-9
#17Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#16)
Re: logical decoding and replication of sequences

On 22.11.21 01:47, Tomas Vondra wrote:

So I think just decoding the sequence tuples is a better solution - for
large transactions (consuming many values from the sequence) it may be
more expensive (i.e. send more records to replica). But I doubt that
matters too much - it's likely negligible compared to other data for
large transactions.

I agree that the original approach is better. It was worth trying out
this alternative, but it seems quite complicated. I note that a lot of
additional code had to be added around several areas of the code,
whereas the original patch really just touched the logical decoding
code, as it should. This doesn't prevent anyone from attempting to
optimize things somehow in the future, but for now let's move forward
with the simple approach.

#18Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#17)
Re: logical decoding and replication of sequences

On 22. 11. 2021, at 16:44, Peter Eisentraut <peter.eisentraut@enterprisedb.com> wrote:

On 22.11.21 01:47, Tomas Vondra wrote:

So I think just decoding the sequence tuples is a better solution - for large transactions (consuming many values from the sequence) it may be more expensive (i.e. send more records to replica). But I doubt that matters too much - it's likely negligible compared to other data for large transactions.

I agree that the original approach is better. It was worth trying out this alternative, but it seems quite complicated. I note that a lot of additional code had to be added around several areas of the code, whereas the original patch really just touched the logical decoding code, as it should. This doesn't prevent anyone from attempting to optimize things somehow in the future, but for now let's move forward with the simple approach.

+1

--
Petr Jelinek

#19Andres Freund
andres@anarazel.de
In reply to: Hannu Krosing (#13)
Re: logical decoding and replication of sequences

Hi,

On 2021-09-25 22:05:43 +0200, Hannu Krosing wrote:

If our aim is just to make sure that all user-visible data in
*transactional* tables is consistent with sequence state then one
very much simplified approach to this could be to track the results of
nextval() calls in a transaction at COMMIT put the latest sequence
value in WAL (or just track the sequences affected and put the latest
sequence state in WAL at commit which needs extra read of sequence but
protects against race conditions with parallel transactions which get
rolled back later)

I think this is a bad idea. It's architecturally more complicated and prevents
use cases because sequence values aren't guaranteed to be as new as on the
original system. You'd need to track all sequence use somehow *even if there
is no relevant WAL generated* in a transaction. There's simply no evidence of
sequence use in a transaction if that transaction uses a previously logged
sequence value.

Greetings,

Andres Freund

#20Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Andres Freund (#19)
Re: logical decoding and replication of sequences

On 11/23/21 02:01, Andres Freund wrote:

Hi,

On 2021-09-25 22:05:43 +0200, Hannu Krosing wrote:

If our aim is just to make sure that all user-visible data in
*transactional* tables is consistent with sequence state then one
very much simplified approach to this could be to track the results of
nextval() calls in a transaction at COMMIT put the latest sequence
value in WAL (or just track the sequences affected and put the latest
sequence state in WAL at commit which needs extra read of sequence but
protects against race conditions with parallel transactions which get
rolled back later)

I think this is a bad idea. It's architecturally more complicated and prevents
use cases because sequence values aren't guaranteed to be as new as on the
original system. You'd need to track all sequence use somehow *even if there
is no relevant WAL generated* in a transaction. There's simply no evidence of
sequence use in a transaction if that transaction uses a previously logged
sequence value.

Not quite. We already have a cache of all sequences used by a session
(see seqhashtab in sequence.c), and it's not that hard to extend it to
per-transaction tracking. That's what the last two versions do, mostly.

But there are various issues with that approach, described in my last
message(s).

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#21Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#16)
#22Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#21)
#23Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#22)
#24Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#23)
#25Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#24)
#26Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#24)
#27Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#25)
#28Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#27)
#29Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tomas Vondra (#24)
#30Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Alvaro Herrera (#29)
#31Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#27)
#32Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#31)
#33Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#27)
#34Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#33)
#35Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#34)
#36Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#35)
#37Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#36)
#38Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Petr Jelinek (#37)
#39Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#38)
#40Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#39)
#41Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#40)
#42Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#41)
#43Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#42)
#44Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#43)
#45Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#44)
#46Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#45)
#47Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#46)
#48Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#46)
#49Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#47)
#50Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#48)
#51Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#50)
#52Euler Taveira
euler@eulerto.com
In reply to: Tomas Vondra (#51)
#53Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Euler Taveira (#52)
#54Euler Taveira
euler@eulerto.com
In reply to: Tomas Vondra (#53)
#55Peter Eisentraut
peter_e@gmx.net
In reply to: Amit Kapila (#50)
#56Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#42)
#57Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#56)
#58Peter Smith
smithpb2250@gmail.com
In reply to: Amit Kapila (#57)
#59Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#57)
#60Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#56)
#61Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#59)
#62Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#60)
#63Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#62)
#64Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#61)
#65Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#64)
#66Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#65)
#67Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#48)
#68Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#67)
#69Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#63)
#70Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#69)
#71Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#69)
#72Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#68)
#73Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#72)
#74Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#73)
#75Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#74)
#76Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Peter Eisentraut (#75)
#77Peter Eisentraut
peter_e@gmx.net
In reply to: Tomas Vondra (#76)
#78Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#74)
#79Petr Jelinek
petr@2ndquadrant.com
In reply to: Amit Kapila (#78)
#80Amit Kapila
amit.kapila16@gmail.com
In reply to: Petr Jelinek (#79)
#81Petr Jelinek
petr@2ndquadrant.com
In reply to: Amit Kapila (#80)
#82Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Petr Jelinek (#81)
#83Bruce Momjian
bruce@momjian.us
In reply to: Tomas Vondra (#82)
#84Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#82)
#85Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Bruce Momjian (#83)
#86Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#84)
#87Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Tomas Vondra (#84)
#88Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#86)
#89Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Masahiko Sawada (#87)
#90Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#88)
#91Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#90)
#92vignesh C
vignesh21@gmail.com
In reply to: Tomas Vondra (#84)
#93Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#91)
#94Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: vignesh C (#92)
#95Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#90)
#96Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#93)
#97Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#96)
#98Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#97)
#99Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Tomas Vondra (#97)
#100Amit Kapila
amit.kapila16@gmail.com
In reply to: Masahiko Sawada (#99)
#101Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#98)
#102Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#101)
#103Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#101)
#104Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#102)
#105Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#103)
#106Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#104)
#107Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#105)
#108Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Tomas Vondra (#105)
#109Amit Kapila
amit.kapila16@gmail.com
In reply to: Masahiko Sawada (#108)
#110Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#102)
#111Amit Kapila
amit.kapila16@gmail.com
In reply to: Tomas Vondra (#107)
#112Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Kapila (#111)
#113Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Tomas Vondra (#112)
#114Justin Pryzby
pryzby@telsasoft.com
In reply to: Tomas Vondra (#107)
#115Zheng Li
zhengli10@gmail.com
In reply to: Tomas Vondra (#112)
#116Noah Misch
noah@leadboat.com
In reply to: Tomas Vondra (#113)
#117Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Noah Misch (#116)
#118Noah Misch
noah@leadboat.com
In reply to: Tomas Vondra (#117)
#119Thomas Munro
thomas.munro@gmail.com
In reply to: Noah Misch (#118)
#120Thomas Munro
thomas.munro@gmail.com
In reply to: Thomas Munro (#119)
#121Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Thomas Munro (#120)
#122Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Kyotaro Horiguchi (#121)
#123Michael Paquier
michael@paquier.xyz
In reply to: Thomas Munro (#120)
#124Thomas Munro
thomas.munro@gmail.com
In reply to: Michael Paquier (#123)
#125Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#124)
#126Thomas Munro
thomas.munro@gmail.com
In reply to: Robert Haas (#125)
#127Thomas Munro
thomas.munro@gmail.com
In reply to: Kyotaro Horiguchi (#122)
#128Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Thomas Munro (#127)
#129Thomas Munro
thomas.munro@gmail.com
In reply to: Tomas Vondra (#128)