POC: Cleaning up orphaned files using undo logs

Started by Thomas Munroover 7 years ago360 messageshackers
Jump to latest
#1Thomas Munro
thomas.munro@gmail.com

Hello hackers,

The following sequence creates an orphaned file:

BEGIN;
CREATE TABLE t ();
<kill -9 this backend>

Occasionally there are reports of systems that have managed to produce
a lot of them, perhaps through ENOSPC-induced panics, OOM signals or
buggy/crashing extensions etc. The most recent example I found in the
archives involved 1.7TB of unexpected files and some careful cleanup
work.

Relation files are created eagerly, and rollback is handled by pushing
PendingRelDelete objects onto the pendingDeletes list, to be discarded
on commit or processed on abort. That's effectively a kind of
specialised undo log, but it's in memory only, so it's less persistent
than the effects it is supposed to undo.

Here's a proof-of-concept patch that plugs the gap using the undo log
technology we're developing as part of the zHeap project. Note that
zHeap is not involved here: the SMGR module is acting as a direct
client of the undo machinery. Example:

postgres=# begin;
BEGIN
postgres=# create table t1 ();
CREATE TABLE
postgres=# create table t2 ();
CREATE TABLE

... now we can see that this transaction has some undo data (discard < insert):

postgres=# select logno, discard, insert, xid, pid from pg_stat_undo_logs;
logno | discard | insert | xid | pid
-------+------------------+------------------+-----+-------
0 | 00000000000021EF | 0000000000002241 | 581 | 18454
(1 row)

... and, if the test_undorecord module is installed, we can inspect
the records it holds:

postgres=# call dump_undo_records(0);
NOTICE: 0000000000002224: Storage: CREATE dbid=12655, tsid=1663, relfile=24594
NOTICE: 00000000000021EF: Storage: CREATE dbid=12655, tsid=1663, relfile=24591
CALL

If we COMMIT, the undo data is discarded by advancing the discard
pointer (tail) to match the insert pointer (head). If we ROLLBACK,
either explicitly or automatically by crashing and recovering, then
the files will be unlinked and the insert pointer will be rewound;
either way the undo log eventually finishes up "empty" again (discard
== insert). This is done with a system of per-rmgr-ID record types
and callbacks, similar to redo. The rollback action are either
executed immediately or offloaded to an undo worker process, depending
on simple heuristics.

Of course this isn't free, and the current patch makes table creation
slower. The goal is to make sure that there is no scenario (kill -9,
power cut etc) in which there can be a new relation file on disk, but
not a corresponding undo record that would unlink that file if the
transaction later has to roll back. Currently, that means that we
need to flush the WAL record that will create the undo record that
will unlink the file *before* we create the relation file. I suspect
that could be mitigated quite easily, by deferring file creation in a
backend-local queue until forced by access or commit. I didn't try to
do that in this basic version.

There are probably other ways to solve the specific problem of
orphaned files, but this approach is built on a general reusable
facility and I think it is a nice way to show the undo concepts, and
how they are separate from zheap. Specifically, it demonstrates the
more traditional of the two uses for undo logs: a reliable way to
track actions that must be performed on rollback. (The other use is:
seeing past versions of data, for vacuumless MVCC; that's a topic for
later).

Patches 0001-0006 are development snapshots of material posted on
other threads already[1]/messages/by-id/CAEepm=2EqROYJ_xYz4v5kfr4b0qw_Lq_6Pe8RTEC8rx3upWsSQ@mail.gmail.com[2]/messages/by-id/CAFiTN-sYQ8r8ANjWFYkXVfNxgXyLRfvbX9Ee4SxO9ns-OBBgVA@mail.gmail.com, hacked around by me to make this possible
(see those threads for further developments in those patches including
some major strengthening work, coming soon). The subject of this
thread is 0007, the core of which is just a couple of hundred lines
written by me, based on an idea from Robert Haas.

Personally I think it'd be a good feature to get into PostgreSQL 12,
and I will add it to the CF that is about to start to seek feedback.
It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

Thanks for reading,

[1]: /messages/by-id/CAEepm=2EqROYJ_xYz4v5kfr4b0qw_Lq_6Pe8RTEC8rx3upWsSQ@mail.gmail.com
[2]: /messages/by-id/CAFiTN-sYQ8r8ANjWFYkXVfNxgXyLRfvbX9Ee4SxO9ns-OBBgVA@mail.gmail.com

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

undo-smgr-v1.tgzapplication/x-gzip; name=undo-smgr-v1.tgzDownload+2-6
#2Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#1)
Re: POC: Cleaning up orphaned files using undo logs

While I've been involved in the design discussions for this patch set,
I haven't looked at any of the code personally in a very long time. I
certainly don't claim to be an independent reviewer, and I encourage
others to review this work also. That said, here are some review
comments.

I decided to start with 0005, as that has the user-facing
documentation for this feature. There is a spurious whitespace-only
hunk in monitoring.sgml.

+     <entry>Process ID of the backend currently attached to this undo log
+      for writing.</entry>

or NULL/0/something if none?

+   each undo log that exists.  Undo logs are extents within a contiguous
+   addressing space that have their own head and tail pointers.

This sentence seems to me to have so little detail that it's not going
to help anyone, and it also seems somewhat out-of-place here. I think
it would be better to link to the longer explanation in the new
storage section instead.

+ Each backend that has written undo data is associated with one or more undo

extra space

+<para>
+Undo logs hold data that is used for rolling back and for implementing
+MVCC in access managers that are undo-aware (currently "zheap").  The storage
+format of undo logs is optimized for reusing existing files.
+</para>

I think the mention of zheap should be removed here since the hope is
that the undo stuff can be committed independently of and prior to
zheap.

I think you mean access methods, not access managers. I suggest
making that an xref.

Maybe add a little more detail, e.g.

Undo logs provide a place for access methods to store data that can be
used to perform necessary cleanup operations after a transaction
abort. The data will be retained after a transaction abort until the
access method successfully performs the required cleanup operations.
After a transaction commit, undo data will be retained until the
transaction is all-visible. This makes it possible for access
managers to use undo data to implement MVCC. Since it most cases undo
data is discarded very quickly, the undo system has been optimized to
minimize writes to disk and to reuse existing files efficiently.

+<para>
+Undo data exists in a 64 bit address space broken up into numbered undo logs
+that represent 1TB extents, for efficient management.  The space is further
+broken up into 1MB segment files, for physical storage.  The name of each file
+is the address of of the first byte in the file, with a period inserted after
+the part that indicates the undo log number.
+</para>

I cannot read this section and know what an undo filename is going to
look like. Also, the remarks about efficient management seems like it
might be unclear to someone not already familiar with how this works.
Maybe something like:

Undo data exists in a 64-bit address space divided into 2^34 undo
logs, each with a theoretical capacity of 1TB. The first time a
backend writes undo, it attaches to an existing undo log whose
capacity is not yet exhausted and which is not currently being used by
any other backend; or if no suitable undo log already exists, it
creates a new one. To avoid wasting space, each undo log is further
divided into 1MB segment files, so that segments which are no longer
needed can be removed (possibly recycling the underlying file by
renaming it) and segments which are not yet needed do not need to be
physically created on disk. An undo segment file has a name like
<example>, where <thing> is the undo log number and <thang> is the
segment number.

I think it's good to spell out the part about attaching to undo logs
here, because when people look at pg_undo, the number of files will be
roughly proportional to the number of backends, and we should try to
help them understand - at least in general terms - why that happens.

+<para>
+Just as relations can have one of the three persistence levels permanent,
+unlogged or temporary, the undo data that is generated by modifying them must
+be stored in an undo log of the same persistence level.  This enables the
+undo data to be discarded at appropriate times along with the relations that
+reference it.
+</para>

This is not quite general, because we're not necessarily talking about
modifications to the files. In fact, in this POC, we're explicitly
talking about the cleanup of the files themselves. Also, it's not
technically correct to say that the persistence level has to match.
You could put everything in permanent undo logs. It would just suck.

Moving on to 0003, the developer documentation:

+The undo log subsystem provides a way to store data that is needed for
+a limited time.  Undo data is generated whenever zheap relations are
+modified, but it is only useful until (1) the generating transaction
+is committed or rolled back and (2) there is no snapshot that might
+need it for MVCC purposes.  See src/backend/access/zheap/README for
+more information on zheap.  The undo log subsystem is concerned with

Again, I think this should be rewritten to make it independent of
zheap. We hope that this facility is not only usable by but will
actually be used by other AMs.

+their location within a 64 bit address space.  Unlike redo data, the
+addressing space is internally divided up unto multiple numbered logs.

Except it's not totally unlike; cf. the log and seg arguments to
XLogFileNameById. The xlog division is largely a historical accident
of having to support systems with 32-bit arithmetic and has minimal
consequences in practice, and it's a lot less noticeable now than it
used to be, but it does still kinda exist. I would try to sharpen
this wording a bit to de-emphasize the contrast over whether a log/seg
distinction exists and instead just contrast multiple insertion points
vs. a single one.

+level code (zheap) is largely oblivious to this internal structure and

Another zheap reference.

+eviction provoked by memory pressure, then no disk IO is generated.

I/O?

+Keeping the undo data physically separate from redo data and accessing
+it though the existing shared buffers mechanism allows it to be
+accessed efficiently for MVCC purposes.

And also non-MVCC purposes. I mean, it's not very feasible to do
post-abort cleanup driven solely off the WAL, because the WAL segments
might've been archived or recycled and there's no easy way to access
the bits we want. Saying this is for MVCC purposes specifically seems
misleading.

+shared memory and can be inspected in the pg_stat_undo_logs view. For

Replace "in" with "via" or "through" or something?

+shared memory and can be inspected in the pg_stat_undo_logs view.  For
+each undo log, a set of properties called the undo log's meta-data are
+tracked:

"called the undo log's meta-data" seems a bit awkward.

+* the "discard" pointer; data before this point has been discarded
+* the "insert" pointer: new data will be written here
+* the "end" pointer: a new undo segment file will be needed at this point

why ; for the first and : for the others?

+The three pointers discard, insert and end move strictly forwards
+until the whole undo log has been exhausted.  At all times discard <=
+insert <= end.  When discard == insert, the undo log is empty

I think you should either remove "discard, insert and end" from this
sentence, relying on people to remember the list they just read, or
else punctuate it like this: The three pointers -- discard, insert,
and end -- move...

+logs are held in a fixed-sized pool in shared memory.  The size of
+the array is a multiple of max_connections, and limits the total size of
+transactions.

I think you should elaborate on "limits the total size of transactions."

+The meta-data for all undo logs is written to disk at every
+checkpoint.  It is stored in files under PGDATA/pg_undo/, using the

Even unlogged and temporary undo logs?

+level of the relation being modified and the current value of the GUC

Suggest: the corresponding relation

+suitable undo log must be either found or created.  The system should
+stabilize on one undo log per active writing backend (or more if
+different tablespaces are persistence levels are used).

Won't edge effects drive the number up considerably?

+and they cannot be accessed by other backend including undo workers.

Grammar. Also, begs the question "so how does this work if the undo
workers are frozen out?"

+Responsibility for WAL-logging the contents of the undo log lies with
+client code (ie zheap).  While undolog.c WAL-logs all meta-data

Another zheap reference.

+hard coded to use md.c unconditionally, PostgreSQL 12 routes IO for the undo

Suggest I/O rather than IO.

I'll see if I can find time to actually review some of this code at
some point. Regarding 0006, I can't help but notice that it is
completely devoid of documentation and README updates, which will not
do. Regarding 0007, that's an impressively small patch.

...Robert

#3Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Thomas Munro (#1)
Re: POC: Cleaning up orphaned files using undo logs

Hello Thomas,

On Thu, Nov 1, 2018 at 8:53 AM Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

I looked into the regression failures when the tap-tests are enabled.
It seems that we're not estimating and allocating the shared memory
for rollback-hash tables correctly. I've added a patch to fix the
same.

--
Thanks & Regards,
Kuntal Ghosh
EnterpriseDB: http://www.enterprisedb.com

Attachments:

0001-Fix-shared-memory-size-for-rollback-hash-table.patchapplication/octet-stream; name=0001-Fix-shared-memory-size-for-rollback-hash-table.patchDownload+3-5
#4Dilip Kumar
dilipbalaut@gmail.com
In reply to: Kuntal Ghosh (#3)
Re: POC: Cleaning up orphaned files using undo logs

On Mon, Nov 5, 2018 at 5:13 PM Kuntal Ghosh <kuntalghosh.2007@gmail.com> wrote:

Hello Thomas,

On Thu, Nov 1, 2018 at 8:53 AM Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

I looked into the regression failures when the tap-tests are enabled.
It seems that we're not estimating and allocating the shared memory
for rollback-hash tables correctly. I've added a patch to fix the
same.

I have included your fix in the latest version of the undo-worker patch[1]/messages/by-id/CAFiTN-sYQ8r8ANjWFYkXVfNxgXyLRfvbX9Ee4SxO9ns-OBBgVA@mail.gmail.com

[1]: /messages/by-id/CAFiTN-sYQ8r8ANjWFYkXVfNxgXyLRfvbX9Ee4SxO9ns-OBBgVA@mail.gmail.com

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

#5Thomas Munro
thomas.munro@gmail.com
In reply to: Kuntal Ghosh (#3)
Re: POC: Cleaning up orphaned files using undo logs

On Tue, Nov 6, 2018 at 12:42 AM Kuntal Ghosh <kuntalghosh.2007@gmail.com> wrote:

On Thu, Nov 1, 2018 at 8:53 AM Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

I looked into the regression failures when the tap-tests are enabled.
It seems that we're not estimating and allocating the shared memory
for rollback-hash tables correctly. I've added a patch to fix the
same.

Thanks Kuntal.

--
Thomas Munro
http://www.enterprisedb.com

#6Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Thomas Munro (#5)
Re: POC: Cleaning up orphaned files using undo logs

On Thu, Nov 8, 2018 at 4:03 AM Thomas Munro <thomas.munro@enterprisedb.com> wrote:

On Tue, Nov 6, 2018 at 12:42 AM Kuntal Ghosh <kuntalghosh.2007@gmail.com> wrote:

On Thu, Nov 1, 2018 at 8:53 AM Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

I looked into the regression failures when the tap-tests are enabled.
It seems that we're not estimating and allocating the shared memory
for rollback-hash tables correctly. I've added a patch to fix the
same.

Thanks Kuntal.

Thanks for the patch,

Unfortunately, cfbot complains about these patches and can't apply them for
some reason, so I did this manually to check it out. All of them (including the
fix from Kuntal) were applied without conflicts, but compilation stopped here

undoinsert.c: In function ‘UndoRecordAllocateMulti’:
undoinsert.c:547:18: error: ‘urec’ may be used uninitialized in this
function [-Werror=maybe-uninitialized]
urec->uur_info = 0; /* force recomputation of info bits */
~~~~~~~~~~~~~~~^~~

Could you please post a fixed version of the patch?

#7Thomas Munro
thomas.munro@gmail.com
In reply to: Dmitry Dolgov (#6)
Re: POC: Cleaning up orphaned files using undo logs

On Sat, Dec 1, 2018 at 5:12 AM Dmitry Dolgov <9erthalion6@gmail.com> wrote:

On Thu, Nov 8, 2018 at 4:03 AM Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Tue, Nov 6, 2018 at 12:42 AM Kuntal Ghosh <kuntalghosh.2007@gmail.com> wrote:

On Thu, Nov 1, 2018 at 8:53 AM Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

I looked into the regression failures when the tap-tests are enabled.
It seems that we're not estimating and allocating the shared memory
for rollback-hash tables correctly. I've added a patch to fix the
same.

Thanks Kuntal.

Thanks for the patch,

Unfortunately, cfbot complains about these patches and can't apply them for
some reason, so I did this manually to check it out. All of them (including the
fix from Kuntal) were applied without conflicts, but compilation stopped here

undoinsert.c: In function ‘UndoRecordAllocateMulti’:
undoinsert.c:547:18: error: ‘urec’ may be used uninitialized in this
function [-Werror=maybe-uninitialized]
urec->uur_info = 0; /* force recomputation of info bits */
~~~~~~~~~~~~~~~^~~

Could you please post a fixed version of the patch?

Sorry for my silence... I got stuck on a design problem with the lower
level undo log management code that I'm now close to having figured
out. I'll have a new patch soon.

--
Thomas Munro
http://www.enterprisedb.com

#8Andres Freund
andres@anarazel.de
In reply to: Thomas Munro (#7)
Re: POC: Cleaning up orphaned files using undo logs

Hi,

On 2018-12-03 18:43:04 +1300, Thomas Munro wrote:

On Sat, Dec 1, 2018 at 5:12 AM Dmitry Dolgov <9erthalion6@gmail.com> wrote:

On Thu, Nov 8, 2018 at 4:03 AM Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Tue, Nov 6, 2018 at 12:42 AM Kuntal Ghosh <kuntalghosh.2007@gmail.com> wrote:

On Thu, Nov 1, 2018 at 8:53 AM Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

It passes make check on Unix and Windows, though currently it's
failing some of the TAP tests for reasons I'm looking into (possibly
due to bugs in the lower level patches, not sure).

I looked into the regression failures when the tap-tests are enabled.
It seems that we're not estimating and allocating the shared memory
for rollback-hash tables correctly. I've added a patch to fix the
same.

Thanks Kuntal.

Thanks for the patch,

Unfortunately, cfbot complains about these patches and can't apply them for
some reason, so I did this manually to check it out. All of them (including the
fix from Kuntal) were applied without conflicts, but compilation stopped here

undoinsert.c: In function ‘UndoRecordAllocateMulti’:
undoinsert.c:547:18: error: ‘urec’ may be used uninitialized in this
function [-Werror=maybe-uninitialized]
urec->uur_info = 0; /* force recomputation of info bits */
~~~~~~~~~~~~~~~^~~

Could you please post a fixed version of the patch?

Sorry for my silence... I got stuck on a design problem with the lower
level undo log management code that I'm now close to having figured
out. I'll have a new patch soon.

Given this patch has been in waiting for author for ~two months, I'm
unfortunately going to have to mark it as returned with feedback. Please
resubmit once refreshed.

Greetings,

Andres Freund

#9Thomas Munro
thomas.munro@gmail.com
In reply to: Andres Freund (#8)
Re: POC: Cleaning up orphaned files using undo logs

On Sun, Feb 3, 2019 at 11:09 PM Andres Freund <andres@anarazel.de> wrote:

On 2018-12-03 18:43:04 +1300, Thomas Munro wrote:

Sorry for my silence... I got stuck on a design problem with the lower
level undo log management code that I'm now close to having figured
out. I'll have a new patch soon.

Hello all,

Here's a new WIP version of this patch set. It builds on a fairly
deep stack of patches being developed by several people. As mentioned
before, it's a useful crash-test dummy for a whole stack of technology
we're working on, but it's also aiming to solve a real problem.

It currently fails in one regression test for a well understood
reason, fix on the way (see end), and there are some other stability
problems being worked on.

Here's a quick tour of the observable behaviour, having installed the
pg_buffercache and test_undorecord extensions:

==================

postgres=# begin;
BEGIN
postgres=# create table foo ();
CREATE TABLE

Check if our transaction has generated undo data:

postgres=# select logno, discard, insert, xid, pid from pg_stat_undo_logs ;
logno | discard | insert | xid | pid
-------+------------------+------------------+-----+-------
0 | 0000000000002CD9 | 0000000000002D1A | 476 | 39169
(1 row)

Here, we see that undo log number 0 has some undo data because discard
< insert. We can find out what it says:

postgres=# call dump_undo_records(0);
NOTICE: 0000000000002CD9: Storage: CREATE dbid=12916, tsid=1663,
relfile=16386; xid=476, next xact=0
CALL

The undo record shown there lives in shared buffers, and we can see
that it's in there with pg_buffercache (the new column smgrid 1 means
undo data; 0 is regular relation data):

postgres=# select bufferid, smgrid, relfilenode, relblocknumber,
isdirty, usagecount from pg_buffercache where smgrid = 1;
bufferid | smgrid | relfilenode | relblocknumber | isdirty | usagecount
----------+--------+-------------+----------------+---------+------------
3 | 1 | 0 | 1 | t | 5
(1 row)

Even though that's just a dirty page in shared buffers, if we crash
now and recover, it'll be recreated by a new WAL record that was
flushed *before* creating the relation file. We can see that with
pg_waldump:

rmgr: Storage ... PRECREATE base/12916/16384, blkref #0: smgr 1 rel
1663/0/0 blk 1 FPW
rmgr: Storage ... CREATE base/12916/16384

The PRECREATE record dirtied block 1 of undo log 0. In this case it
happened to include a FPW of the undo log page too, following the
usual rules. FPWs are rare for undo pages because of the
REGBUF_WILL_INIT optimisation that applies to the zeroed out pages
(which is most undo pages, due to the append-mostly access pattern).

Finally, we if commit we see the undo data is discarded by a
background worker, and if we roll back explicitly or crash and run
recovery, the file is unlinked. Here's an example of the crash case:

postgres=# begin;
BEGIN
postgres=# create table foo ();
CREATE TABLE
postgres=# select relfilenode from pg_class where relname = 'foo';
relfilenode
-------------
16395
(1 row)

postgres=# select pg_backend_pid();
pg_backend_pid
----------------
39169
(1 row)

$ kill -9 39169

... server restarts, recovers ...

$ ls pgdata/base/12916/16395
pgdata/base/12916/16395

It's still there, though it's been truncated by an undo worker (see
end of email). And finally, after the next checkpoint:

$ ls pgdata/base/12916/16395
ls: pgdata/base/12916/16395: No such file or directory

That's the end of the quick tour.

Most of these patches should probably be discussed in other threads,
but I'm posting a snapshot of the full stack here anyway. Here's a
patch-by-patch summary:

=== 0001 "Refactor the fsync mechanism to support future SMGR
implementations." ===

The 0001 patch has its own CF thread
https://commitfest.postgresql.org/22/1829/ and is from Shawn Debnath
(based on earlier work by me), but I'm including a copy here for
convenience/cfbot.

=== 0002 "Add SmgrId to smgropen() and BufferTag." ===

This is new, and is based on the discussion from another recent
thread[1]/messages/by-id/CA+hUKG+DE0mmiBZMtZyvwWtgv1sZCniSVhXYsXkvJ_Wo+83vvw@mail.gmail.com about how we should identify buffers belonging to different
storage managers. In earlier versions of the patch-set I had used a
special reserved DB OID for undo data. Tom Lane didn't like that idea
much, and Anton Shyrabokau (via Shawn Debnath) suggested making
ForkNumber narrower so we can add a new field to BufferTag, and Andres
Freund +1'd my proposal to add the extra value as a parameter to
smgropen(). So, here is a patch that tries those ideas.

Another way to do this would be to widen RelFileNode instead, to avoid
having to pass around the SMGR ID separately in various places.
Looking at the number of places that have to chance, you can probably
see why we wanted to use a magic DB OID instead, and I'm not entirely
convinced that it wasn't better that way, or that I've found all the
places that need to carry an smgrid alongside a RelFileNode.

Archeological note: smgropen() was like that ~15 years ago before
commit 87bd9563, but buffer tags didn't include the SMGR ID.

I decided to call md.c's ID "SMGR_RELATION", describing what it really
holds -- regular relations -- rather than perpetuating the doubly
anachronistic "magnetic disk" name.

While here, I resurrected the ancient notion of a per-SMGR 'open'
routine, so that a small amount of md.c-specific stuff could be kicked
out of smgr.c and future implementations can do their own thing here
too.

While doing that work I realised that at least pg_rewind needs to
learn about how different storage managers map blocks to files, so
that's a new TODO item requiring more thought. I wonder what other
places know how to map { RelFileNode, ForkNumber, BlockNumber } to a
path + offset, and I wonder what to think about the fact that some of
them may be non-backend code...

=== 0003 "Add undo log manager." ===

This and the next couple of patches live in CF thread
https://commitfest.postgresql.org/22/1649/ but here's a much newer
snapshot that hasn't been posted there yet.

Manages a set of undo logs in shared memory, manages undo segment
files, tracks discard, insert, end pointers visible in
pg_stat_undo_logs. With this patch you can allocate and discard space
in undo logs using the UndoRecPtr type to refer to addresses, but
there is no access to the data yet. Improvements since the last
version are not requiring DSM segments, proper FPW support and reduced
WAL traffic. Previously there were extra per-xact and per-checkpoint
records requiring retry-loops in code that inserted undo data.

=== 0004 "Provide access to undo log data via the buffer manager." ===

Provide SMGR_UNDO. While the 0003 patch deals with allocating and
discarding undo address space and makes sure that backing files exist,
this patch lets you read and write buffered data in them.

=== 0005 "Allow WAL record data on first modification after a checkpoint." ===

Provide a way for data to be attached to a WAL-registered block that
is only included if this turns out to be the first WAL record that
touches the block after a checkpoint. This is a bit like FPW images,
except that it's arbitrary extra data and happens even if FPW is off.
This is used to capture a copy of the (tiny) undo log meta-data
(primary the insertion pointer) to fix a consistency problem when
recovering from an online checkpoint.

=== 0006 + 0007 "Provide interfaces to store and fetch undo records." ===

This is a snapshot of work by my colleagues Dilip, Rafia and others
based on earlier prototyping by Robert. While the earlier patches
give you buffered binary undo data, this patch introduces the concept
of high level undo records that can be inserted, and read back given
an UndoRecPtr. This is a version presented on another thread already;
here it's lightly changed due to rebasing by me.

Undo-aware modules should design a set of undo record types, and
insert exactly the same ones at do and undo time.

The 0007 patch is fixups from me to bring that code into line with
changes to the lower level patches. Future versions will be squashed
and tidied up; still working on that.

=== 0008 + 0009 "Undo worker and transaction rollback" ===

This has a CF thread at https://commitfest.postgresql.org/22/1828/ and
again this is a snapshot of work from Dilip, Rafia and others, with a
fixup from me. Still working on coordinating that for the next
version.

This provides a way for RMGR modules to register a callback function
that will receive all the undo records they inserted during a given
[sub]transaction if it rolls back. It also provides a system of
background workers that can execute those undo records in case the
rollback happens after crash recovery, or in case the work can be
usefully pushed into the background during a regular online rollback.
This is a complex topic and I'm not attempting to explain it here.

There are a few known problems with this and Dilip is working on a
more sophisticated worker management system, but I'll let him write
about that, over in that other thread.

I think it'd probably be a good idea to split this patch into two or
three; the RMGR undo support, the xact.c integration and the worker
machinery. But maybe that's just me.

Archeological note: XXXX_undo() callback functions registered via
rmgrlist.h a bit like this originally appeared in the work by Vadim
Mikheev (author of WAL) in commit b58c0411bad4, but that was
apparently never completed once people figured out that you can make a
force, steal, redo, no-undo database work (curiously I saw a slide
from a university lecture somewhere saying that would be impossible).
The stub functions were removed from the tree in 4c8495a1. Our new
work differs from Vadim's original vision by putting undo data in a
separate place from the WAL, and accessing it via shared buffers. I
guess that might be because Vadim planned to use undo for rollback
only, not for MVCC (but I might be wrong about that). That difference
might explains why eg Vadim's function heap_undo() took an XLogRecord,
whereas our proposal takes a different type. Our proposal also passes
more than one records at a time to the undo handler; in future this
will allow us to collect up all undo records relating to a page of
(eg) zheap, and process them together for mechanical sympathy.

=== 0010 "Add developer documentation for the undo log storage subsystem." ===

Updated based on Robert's review up-thread. No coverage of background
workers yet -- that is under development.

=== 0011 "Add user-facing documentation for undo logs." ===

Updated based on Robert's review up-thread.

=== 0012 "Add test_undorecord test module." ===

Provides quick and dirty dump_undo_records() procedure for testing.

=== 0013 "Use undo-based rollback to clean up files on abort." ===

Finally, this is the actual feature that this CF item is about. The
main improvement here is that the previous version unlinked files
immediately when executing undo actions, which broke the protocol
established by commit 6cc4451b, namely that you can't reuse a
relfilenode until after the next checkpoint, and the existence of an
(empty) first relation segment in the filesystem is the only thing
preventing that. That is fixed in this version (but see problem 2
below).

Known problems:

1. A couple of tests fail with "ERROR: buffer is pinned in
InvalidateBuffer". That's because ROLLBACK TO SAVEPOINT is executing
the undo actions that drop the buffers for a newly created table
before the subtransaction has been cleaned up. Amit is working on a
solution to that. More soon.

2. The are two levels of deferment of file unlinking in current
PostgreSQL. First, when you create a new relation, it is pushed on
pendingDeletes; this patch-set replaces that in-memory list with
persistent undo records as discussed. There is a second level of
deferment: we unlink all the segments of the file except the first
one, which we truncate, and then finally the zero-length file is
unlinked after the next checkpoint; this is an important part of
PostgreSQL's protocol for not reusing relfilenodes too soon. That
means that there is still a very narrow window after the checkpoint is
logged but before we've unlinked that file where you could still crash
and leak a zero-length file. I've thought about a couple of solutions
to close that window, including a file renaming scheme where .zombie
files get cleaned up on crash, but that seemed like something that
could be improved later.

There is something else that goes wrong under parallel make check,
which I must have introduced recently but haven't tracked down yet. I
wanted to post a snapshot version for discussion anyway. More soon.

This code is available at https://github.com/EnterpriseDB/zheap/tree/undo.

[1]: /messages/by-id/CA+hUKG+DE0mmiBZMtZyvwWtgv1sZCniSVhXYsXkvJ_Wo+83vvw@mail.gmail.com

--
Thomas Munro
https://enterprisedb.com

Attachments:

undo-smgr-v2.tgzapplication/x-gzip; name=undo-smgr-v2.tgzDownload+3-0
#10Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#9)
Re: POC: Cleaning up orphaned files using undo logs

On Tue, Mar 12, 2019 at 6:51 PM Thomas Munro <thomas.munro@gmail.com> wrote:

On Sun, Feb 3, 2019 at 11:09 PM Andres Freund <andres@anarazel.de> wrote:

On 2018-12-03 18:43:04 +1300, Thomas Munro wrote:

Sorry for my silence... I got stuck on a design problem with the lower
level undo log management code that I'm now close to having figured
out. I'll have a new patch soon.

Hello all,

Here's a new WIP version of this patch set. It builds on a fairly
deep stack of patches being developed by several people. As mentioned
before, it's a useful crash-test dummy for a whole stack of technology
we're working on, but it's also aiming to solve a real problem.

It currently fails in one regression test for a well understood
reason, fix on the way (see end), and there are some other stability
problems being worked on.

Here's a quick tour of the observable behaviour, having installed the
pg_buffercache and test_undorecord extensions:

==================

postgres=# begin;
BEGIN
postgres=# create table foo ();
CREATE TABLE

Check if our transaction has generated undo data:

postgres=# select logno, discard, insert, xid, pid from pg_stat_undo_logs ;
logno | discard | insert | xid | pid
-------+------------------+------------------+-----+-------
0 | 0000000000002CD9 | 0000000000002D1A | 476 | 39169
(1 row)

Here, we see that undo log number 0 has some undo data because discard
< insert. We can find out what it says:

postgres=# call dump_undo_records(0);
NOTICE: 0000000000002CD9: Storage: CREATE dbid=12916, tsid=1663,
relfile=16386; xid=476, next xact=0
CALL

The undo record shown there lives in shared buffers, and we can see
that it's in there with pg_buffercache (the new column smgrid 1 means
undo data; 0 is regular relation data):

postgres=# select bufferid, smgrid, relfilenode, relblocknumber,
isdirty, usagecount from pg_buffercache where smgrid = 1;
bufferid | smgrid | relfilenode | relblocknumber | isdirty | usagecount
----------+--------+-------------+----------------+---------+------------
3 | 1 | 0 | 1 | t | 5
(1 row)

Even though that's just a dirty page in shared buffers, if we crash
now and recover, it'll be recreated by a new WAL record that was
flushed *before* creating the relation file. We can see that with
pg_waldump:

rmgr: Storage ... PRECREATE base/12916/16384, blkref #0: smgr 1 rel
1663/0/0 blk 1 FPW
rmgr: Storage ... CREATE base/12916/16384

The PRECREATE record dirtied block 1 of undo log 0. In this case it
happened to include a FPW of the undo log page too, following the
usual rules. FPWs are rare for undo pages because of the
REGBUF_WILL_INIT optimisation that applies to the zeroed out pages
(which is most undo pages, due to the append-mostly access pattern).

Finally, we if commit we see the undo data is discarded by a
background worker, and if we roll back explicitly or crash and run
recovery, the file is unlinked. Here's an example of the crash case:

postgres=# begin;
BEGIN
postgres=# create table foo ();
CREATE TABLE
postgres=# select relfilenode from pg_class where relname = 'foo';
relfilenode
-------------
16395
(1 row)

postgres=# select pg_backend_pid();
pg_backend_pid
----------------
39169
(1 row)

$ kill -9 39169

... server restarts, recovers ...

$ ls pgdata/base/12916/16395
pgdata/base/12916/16395

It's still there, though it's been truncated by an undo worker (see
end of email). And finally, after the next checkpoint:

$ ls pgdata/base/12916/16395
ls: pgdata/base/12916/16395: No such file or directory

That's the end of the quick tour.

Most of these patches should probably be discussed in other threads,
but I'm posting a snapshot of the full stack here anyway. Here's a
patch-by-patch summary:

=== 0001 "Refactor the fsync mechanism to support future SMGR
implementations." ===

The 0001 patch has its own CF thread
https://commitfest.postgresql.org/22/1829/ and is from Shawn Debnath
(based on earlier work by me), but I'm including a copy here for
convenience/cfbot.

=== 0002 "Add SmgrId to smgropen() and BufferTag." ===

This is new, and is based on the discussion from another recent
thread[1] about how we should identify buffers belonging to different
storage managers. In earlier versions of the patch-set I had used a
special reserved DB OID for undo data. Tom Lane didn't like that idea
much, and Anton Shyrabokau (via Shawn Debnath) suggested making
ForkNumber narrower so we can add a new field to BufferTag, and Andres
Freund +1'd my proposal to add the extra value as a parameter to
smgropen(). So, here is a patch that tries those ideas.

Another way to do this would be to widen RelFileNode instead, to avoid
having to pass around the SMGR ID separately in various places.
Looking at the number of places that have to chance, you can probably
see why we wanted to use a magic DB OID instead, and I'm not entirely
convinced that it wasn't better that way, or that I've found all the
places that need to carry an smgrid alongside a RelFileNode.

Archeological note: smgropen() was like that ~15 years ago before
commit 87bd9563, but buffer tags didn't include the SMGR ID.

I decided to call md.c's ID "SMGR_RELATION", describing what it really
holds -- regular relations -- rather than perpetuating the doubly
anachronistic "magnetic disk" name.

While here, I resurrected the ancient notion of a per-SMGR 'open'
routine, so that a small amount of md.c-specific stuff could be kicked
out of smgr.c and future implementations can do their own thing here
too.

While doing that work I realised that at least pg_rewind needs to
learn about how different storage managers map blocks to files, so
that's a new TODO item requiring more thought. I wonder what other
places know how to map { RelFileNode, ForkNumber, BlockNumber } to a
path + offset, and I wonder what to think about the fact that some of
them may be non-backend code...

=== 0003 "Add undo log manager." ===

This and the next couple of patches live in CF thread
https://commitfest.postgresql.org/22/1649/ but here's a much newer
snapshot that hasn't been posted there yet.

Manages a set of undo logs in shared memory, manages undo segment
files, tracks discard, insert, end pointers visible in
pg_stat_undo_logs. With this patch you can allocate and discard space
in undo logs using the UndoRecPtr type to refer to addresses, but
there is no access to the data yet. Improvements since the last
version are not requiring DSM segments, proper FPW support and reduced
WAL traffic. Previously there were extra per-xact and per-checkpoint
records requiring retry-loops in code that inserted undo data.

=== 0004 "Provide access to undo log data via the buffer manager." ===

Provide SMGR_UNDO. While the 0003 patch deals with allocating and
discarding undo address space and makes sure that backing files exist,
this patch lets you read and write buffered data in them.

=== 0005 "Allow WAL record data on first modification after a checkpoint." ===

Provide a way for data to be attached to a WAL-registered block that
is only included if this turns out to be the first WAL record that
touches the block after a checkpoint. This is a bit like FPW images,
except that it's arbitrary extra data and happens even if FPW is off.
This is used to capture a copy of the (tiny) undo log meta-data
(primary the insertion pointer) to fix a consistency problem when
recovering from an online checkpoint.

=== 0006 + 0007 "Provide interfaces to store and fetch undo records." ===

This is a snapshot of work by my colleagues Dilip, Rafia and others
based on earlier prototyping by Robert. While the earlier patches
give you buffered binary undo data, this patch introduces the concept
of high level undo records that can be inserted, and read back given
an UndoRecPtr. This is a version presented on another thread already;
here it's lightly changed due to rebasing by me.

Undo-aware modules should design a set of undo record types, and
insert exactly the same ones at do and undo time.

The 0007 patch is fixups from me to bring that code into line with
changes to the lower level patches. Future versions will be squashed
and tidied up; still working on that.

Currently, undo branch[1]https://github.com/EnterpriseDB/zheap/tree/undo contain an older version of the (undo
interface + some fixup). Now, I have merged the latest changes from
the zheap branch[2]https://github.com/EnterpriseDB/zheap to the undo branch[1]https://github.com/EnterpriseDB/zheap/tree/undo
which can be applied on top of the undo storage commit[3]https://github.com/EnterpriseDB/zheap/tree/undo (b397d96176879ed5b09cf7322b8d6f2edd8043a5). For
merging those changes, I need to add some changes to the undo log
storage patch as well for handling the multi log transaction. So I
have attached two patches, 1) improvement to undo log storage 2)
complete undo interface patch which include 0006+0007 from undo
branch[1]https://github.com/EnterpriseDB/zheap/tree/undo + new changes on the zheap branch.

[1]: https://github.com/EnterpriseDB/zheap/tree/undo
[2]: https://github.com/EnterpriseDB/zheap
[3]: https://github.com/EnterpriseDB/zheap/tree/undo (b397d96176879ed5b09cf7322b8d6f2edd8043a5)
(b397d96176879ed5b09cf7322b8d6f2edd8043a5)

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

Attachments:

0001-Enhance-multi-log-handling-in-undo-log.patchapplication/octet-stream; name=0001-Enhance-multi-log-handling-in-undo-log.patchDownload+102-3
0002-Provide-interfaces-to-store-and-fetch-undo-records.patchapplication/octet-stream; name=0002-Provide-interfaces-to-store-and-fetch-undo-records.patchDownload+2047-2
#11Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#10)
Re: POC: Cleaning up orphaned files using undo logs

On Fri, Apr 19, 2019 at 6:16 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

Currently, undo branch[1] contain an older version of the (undo
interface + some fixup). Now, I have merged the latest changes from
the zheap branch[2] to the undo branch[1]
which can be applied on top of the undo storage commit[3]. For
merging those changes, I need to add some changes to the undo log
storage patch as well for handling the multi log transaction. So I
have attached two patches, 1) improvement to undo log storage 2)
complete undo interface patch which include 0006+0007 from undo
branch[1] + new changes on the zheap branch.

Some review comments:

+#define AtAbort_ResetUndoBuffers() ResetUndoBuffers()

I don't think this really belongs in xact.c. Seems like we ought to
declare it in the appropriate header file. Perhaps we also ought to
consider using a static inline function rather than a macro, although
I guess it doesn't really matter.

+void
+SetCurrentUndoLocation(UndoRecPtr urec_ptr)
+{
+ UndoLogControl *log = UndoLogGet(UndoRecPtrGetLogNo(urec_ptr), false);
+ UndoPersistence upersistence = log->meta.persistence;

Could we arrange things so that the caller passes the persistence
level, instead of having to recalculate it here? This doesn't seem to
be a particularly cheap operation. UndoLogGet() will call
get_undo_log() which I guess will normally just do a hash table lookup
but which in the worst case will take an LWLock and perform a linear
search of a shared memory array. But at PrepareUndoInsert time we
knew the persistence level already, so I don't see why
InsertPreparedUndo should have to force it to be looked up all over
again -- and while in a critical section, no less.

Another thing that is strange about this patch is that it adds these
start_urec_ptr and latest_urec_ptr arrays and then uses them for
absolutely nothing. I think that's a pretty clear sign that the
division of this work into multiple patches is not correct. We
shouldn't have one patch that tracks some information that is used
nowhere for anything and then another patch that adds a user of that
information -- the two should go together.

Incidentally, wouldn't StartTransaction() need to reinitialize these fields?

+ * When the undorecord for a transaction gets inserted in the next log then we

undo record

+ * insert a transaction header for the first record in the new log and update
+ * the transaction header with this new logs location.  We will also keep

This appears to be nonsensical. You're saying that you add a
transaction header to the new log and then update it with its own
location. That can't be what you mean.

+ * Incase, this is not the first record in new log (aka new log already

"Incase," -> "If"
"aka" -> "i.e."

Overall this whole paragraph is a bit hard to understand.

+ * same transaction spans across multiple logs depending on which log is

delete "across"

+ * processed first by the discard worker.  If it processes the first log which
+ * contains the transactions first record, then it can get the last record
+ * of that transaction even if it is in different log and then processes all
+ * the undo records from last to first.  OTOH, if the next log get processed

Not sure how that would work if the number of logs is >2.

This whole paragraph is also hard to understand.

+static UndoBuffers def_buffers[MAX_UNDO_BUFFERS];
+static int buffer_idx;

This is a file-global variable with a very generic name that is very
similar to a local variable name used by multiple functions within the
file (bufidx) and no comments. Ugh.

+UndoRecordIsValid(UndoLogControl * log, UndoRecPtr urp)

The locking regime for this function is really confusing. It requires
that the caller hold discard_lock on entry, and on exit the lock will
still be held if the return value is true but will no longer be held
if the return value is false. Yikes! Anybody who reads code that
uses this function is not going to guess that it has such strange
behavior. I'm not exactly sure how to redesign this, but I think it's
not OK the way you have it. One option might be to inline the logic
into each call site.

+/*
+ * Overwrite the first undo record of the previous transaction to update its
+ * next pointer.  This will just insert the already prepared record by
+ * UndoRecordPrepareTransInfo.  This must be called under the critical section.
+ * This will just overwrite the undo header not the data.
+ */
+static void
+UndoRecordUpdateTransInfo(int idx)

It bugs me that this function goes back in to reacquire the discard
lock for the purpose of preventing a concurrent undo discard.
Apparently, if the other transaction's undo has been discarded between
the prepare phase and where we are now, we're OK with that and just
exit without doing anything; otherwise, we update the previous
transaction header. But that seems wrong. When we enter a critical
section, I think we should aim to know exactly what modifications we
are going to make within that critical section.

I also wonder how the concurrent discard could really happen. We must
surely be holding exclusive locks on the relevant buffers -- can undo
discard really discard undo when the relevant buffers are x-locked?

It seems to me that remaining_bytes is a crock that should be ripped
out entirely, both here and in InsertUndoRecord. It seems to me that
UndoRecordUpdateTransInfo can just contrive to set remaining_bytes
correctly. e.g.

do
{
// stuff
if (!BufferIsValid(buffer))
{
Assert(InRecovery);
already_written += (BLCKSZ - starting_byte);
done = (already_written >= undo_len);
}
else
{
page = BufferGetPage(buffer);
done = InsertUndoRecord(...);
MarkBufferDirty(buffer);
}
} while (!done);

InsertPreparedUndo needs similar treatment.

To make this work, I guess the long string of assignments in
InsertUndoRecord will need to be made unconditional, but that's
probably pretty cheap. As a fringe benefit, all of those work_blah
variables that are currently referencing file-level globals can be
replaced with something local to this function, which will surely
please the coding style police.

+ * In recovery, 'xid' refers to the transaction id stored in WAL, otherwise,
+ * it refers to the top transaction id because undo log only stores mapping
+ * for the top most transactions.
+ */
+UndoRecPtr
+PrepareUndoInsert(UnpackedUndoRecord *urec, FullTransactionId fxid,

xid vs fxid

+ urec->uur_xidepoch = EpochFromFullTransactionId(fxid);

We need to make some decisions about how we're going to handle 64-bit
XIDs vs. 32-bit XIDs in undo. This doesn't look like a well-considered
scheme. In general, PrepareUndoInsert expects the caller to have
populated the UnpackedUndoRecord, but here, uur_xidepoch is getting
overwritten with the high bits of the caller-specified XID. The
low-order bits aren't stored anywhere by this function, but the caller
is presumably expected to have placed them inside urec->uur_xid. And
it looks like the low-order bits (urec->uur_xid) get stored for every
undo record, but the high-order bits (urec->xidepoch) only get stored
when we emit a transaction header. This all seems very confusing.

I would really like to see us replace uur_xid and uur_xidepoch with a
FullTransactionId; now that we have that concept, it seems like bad
practice to break what is really a FullTransactionId into two halves
and store them separately. However, it would be a bit unfortunate to
store an additional 4 bytes of mostly-static data in every undo
record. What if we went the other way? That is, remove urec_xid
from UndoRecordHeader and from UnpackedUndoRecord. Make it the
responsibility of whoever is looking up an undo record to know which
transaction's undo they are searching. zheap, at least, generally
does know this: if it's starting from a page, then it has the XID +
epoch available from the transaction slot, and if it's starting from
an undo record, you need to know the XID for which you are searching,
I guess from uur_prevxid.

I also think that we need to remove uur_prevxid. That field does not
seem to be properly a general-purpose part of the undo machinery, but
a zheap-specific consideration. I think it's job is to tell you which
transaction last modified the current tuple, but zheap can put that
data in the payload if it likes. It is a waste to store it in every
undo record, because it's not needed if the older undo has already
been discarded or if the operation is an insert.

+ * Insert a previously-prepared undo record. This will write the actual undo

Looks like this now inserts all previously-prepared undo records
(rather than just a single one).

+ * in page. We start writting immediately after the block header.

Spelling.

+ * Helper function for UndoFetchRecord.  It will fetch the undo record pointed
+ * by urp and unpack the record into urec.  This function will not release the
+ * pin on the buffer if complete record is fetched from one buffer, so caller
+ * can reuse the same urec to fetch the another undo record which is on the
+ * same block.  Caller will be responsible to release the buffer inside urec
+ * and set it to invalid if it wishes to fetch the record from another block.
+ */
+UnpackedUndoRecord *
+UndoGetOneRecord(UnpackedUndoRecord *urec, UndoRecPtr urp, RelFileNode rnode,
+ UndoPersistence persistence)

I don't really understand why uur_buffer is part of an
UnpackedUndoRecord. It doesn't look like the other fields, which tell
you about the contents of an undo record that you have created or that
you have parsed. Instead, it's some kind of scratch space for keeping
track of a buffer that we're using in the process of reading an undo
record. It looks like it should be an argument to UndoGetOneRecord()
and ResetUndoRecord().

I also wonder whether it's really a good design to make the caller
responsible for invalidating the buffer before accessing another
block. Maybe it would be simpler to have this function just check
whether the buffer it's been given is the right one; if not, unpin it
and pin the new one instead. But I'm not really sure...

+ /* If we already have a buffer pin then no need to allocate a new one. */
+ if (!BufferIsValid(buffer))
+ {
+ buffer = ReadBufferWithoutRelcache(SMGR_UNDO,
+    rnode, UndoLogForkNum, cur_blk,
+    RBM_NORMAL, NULL,
+    RelPersistenceForUndoPersistence(persistence));
+
+ urec->uur_buffer = buffer;
+ }

I think you should move this code inside the loop that follows. Then
at the bottom of that loop, instead of making a similar call, just set
buffer = InvalidBuffer. Then when you loop around it'll do the right
thing and you'll need less code.

Notice that having both the local variable buffer and the structure
member urec->uur_buffer is actually making this code more complex. You
are already setting urec->uur_buffer = InvalidBuffer when you do
UnlockReleaseBuffer(). If you didn't have a separate 'buffer'
variable you wouldn't need to clear them both here. In fact I think
what you should have is an argument Buffer *curbuf, or something like
that, and no uur_buffer at all.

+ /*
+ * If we have copied the data then release the buffer, otherwise, just
+ * unlock it.
+ */
+ if (is_undo_rec_split)
+ UnlockReleaseBuffer(buffer);
+ else
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);

Ugh. I think what's going on here is: UnpackUndoRecord copies the
data if it switches buffers, but not otherwise. So if the record is
split, we can release the lock and pin, but otherwise we have to keep
the pin to avoid having the data get clobbered. But having this code
know about what UnpackUndoRecord does internally seems like an
abstraction violation. It's also probably not right if we ever want
to fetch undo records in bulk, as I see that the latest code in zheap
master does. I think we should switch UnpackUndoRecord over to always
copying the data and just avoid all this.

(To make that cheaper, we may want to teach UnpackUndoRecord to store
data into scratch space provided by the caller rather than using
palloc to get its own space, but I'm not actually sure that's (a)
worth it or (b) actually better.)

[ Skipping over some things ]

+bool
+UnpackUndoRecord(UnpackedUndoRecord *uur, Page page, int starting_byte,
+ int *already_decoded, bool header_only)

I think we should split this function into three functions that use a
context object, call it say UnpackUndoContext. The caller will do:

BeginUnpackUndo(&ucontext); // just once
UnpackUndoData(&ucontext, page, starting_byte); // any number of times
FinishUnpackUndo(&uur, &ucontext); // just once

The undo context will store an enum value that tells us the "stage" of decoding:

- UNDO_DECODE_STAGE_HEADER: We have not yet decoded even the record
header; we need to do that next.
- UNDO_DECODE_STAGE_RELATION_DETAILS: The next thing to be decoded is
the relation details, if present.
- UNDO_DECODE_STAGE_BLOCK: The next thing to be decoded is the block
details, if present.
- UNDO_DECODE_STAGE_TRANSACTION: The next thing to be decoded is the
transaction details, if present.
- UNDO_DECODE_STAGE_PAYLOAD: The next thing to be decoded is the
payload details, if present.
- UNDO_DECODE_STAGE_DONE: Decoding is complete.

It will also store the number of bytes that have been already been
copied as part of whichever stage is current. A caller who wants only
part of the record can stop when ucontext.stage > desired_stage; e.g.
the current header_only flag corresponds to stopping when
ucontext.stage > UNDO_DECODE_STAGE_HEADER, and the potential
optimization mentioned in UndoGetOneRecord could be done by stopping
when ucontext.stage > UNDO_DECODE_STAGE_BLOCK (although I don't know
if that's worth doing).

In this scheme, BeginUnpackUndo just needs to set the stage to
UNDO_DECODE_STAGE_HEADER and the number of bytes copied to 0. The
context object contains all the work_blah things (no more global
variables!), but BeginUnpackUndo does not need to initialize them,
since they will be overwritten before they are examined. And
FinishUnpackUndo just needs to copy all of the fields from the
work_blah things into the UnpackedUndoRecord. The tricky part is
UnpackUndoData itself, which I propose should look like a big switch
where all branches fall through. Roughly:

switch (ucontext->stage)
{
case UNDO_DECODE_STAGE_HEADER:
if (!ReadUndoBytes(...))
return;
stage = UNDO_DECODE_STAGE_RELATION_DETAILS;
/* FALLTHROUGH */
case UNDO_DECODE_STAGE_RELATION_DETAILS:
if ((uur->uur_info & UREC_INFO_RELATION_DETAILS) != 0)
{
if (!ReadUndoBytes(...))
return;
}
stage = UNDO_DECODE_STAGE_BLOCK;
/* FALLTHROUGH */
etc.

ReadUndoBytes would need some adjustments in this scheme; it wouldn't
need my_bytes_read any more since it would only get called for
structures that are not yet completely read. (Regardless of whether
we adopt this idea, the nocopy flag to ReadUndoBytes appears to be
unused and can be removed.)

We could use a similar context object for InsertUndoRecord.
BeginInsertUndoRecord(&ucontext, &uur) would initialize all of the
work_blah structures within the context object. InsertUndoData will
be a big switch. Maybe no need for a "finish" function here. There
can also be a SkipInsertingUndoData function that can be called
instead of InsertUndoData if the page is discarded. I think this
would be more elegant than what we've got now.

This is not a complete review, but I'm out of time and energy for the moment...

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Shawn Debnath
sdn@amazon.com
In reply to: Thomas Munro (#9)
Re: POC: Cleaning up orphaned files using undo logs

On Wed, Mar 13, 2019 at 02:20:29AM +1300, Thomas Munro wrote:

=== 0002 "Add SmgrId to smgropen() and BufferTag." ===

This is new, and is based on the discussion from another recent
thread[1] about how we should identify buffers belonging to different
storage managers. In earlier versions of the patch-set I had used a
special reserved DB OID for undo data. Tom Lane didn't like that idea
much, and Anton Shyrabokau (via Shawn Debnath) suggested making
ForkNumber narrower so we can add a new field to BufferTag, and Andres
Freund +1'd my proposal to add the extra value as a parameter to
smgropen(). So, here is a patch that tries those ideas.

Another way to do this would be to widen RelFileNode instead, to avoid
having to pass around the SMGR ID separately in various places.
Looking at the number of places that have to chance, you can probably
see why we wanted to use a magic DB OID instead, and I'm not entirely
convinced that it wasn't better that way, or that I've found all the
places that need to carry an smgrid alongside a RelFileNode.

Archeological note: smgropen() was like that ~15 years ago before
commit 87bd9563, but buffer tags didn't include the SMGR ID.

I decided to call md.c's ID "SMGR_RELATION", describing what it really
holds -- regular relations -- rather than perpetuating the doubly
anachronistic "magnetic disk" name.

While here, I resurrected the ancient notion of a per-SMGR 'open'
routine, so that a small amount of md.c-specific stuff could be kicked
out of smgr.c and future implementations can do their own thing here
too.

While doing that work I realised that at least pg_rewind needs to
learn about how different storage managers map blocks to files, so
that's a new TODO item requiring more thought. I wonder what other
places know how to map { RelFileNode, ForkNumber, BlockNumber } to a
path + offset, and I wonder what to think about the fact that some of
them may be non-backend code...

Given the scope of this patch, it might be prudent to start a separate
thread for it. So far, this discussion has been burried within other
discussions and I want to ensure folks don't miss this.

Thanks.

--
Shawn Debnath
Amazon Web Services (AWS)

#13Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#11)
Re: POC: Cleaning up orphaned files using undo logs

On Fri, Apr 19, 2019 at 10:13 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Apr 19, 2019 at 6:16 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

Currently, undo branch[1] contain an older version of the (undo
interface + some fixup). Now, I have merged the latest changes from
the zheap branch[2] to the undo branch[1]
which can be applied on top of the undo storage commit[3]. For
merging those changes, I need to add some changes to the undo log
storage patch as well for handling the multi log transaction. So I
have attached two patches, 1) improvement to undo log storage 2)
complete undo interface patch which include 0006+0007 from undo
branch[1] + new changes on the zheap branch.

Thanks for the review Robert. Please find my reply inline.

Some review comments:

+#define AtAbort_ResetUndoBuffers() ResetUndoBuffers()

I don't think this really belongs in xact.c. Seems like we ought to
declare it in the appropriate header file. Perhaps we also ought to
consider using a static inline function rather than a macro, although
I guess it doesn't really matter.

Moved to undoinsert.h

+void
+SetCurrentUndoLocation(UndoRecPtr urec_ptr)
+{
+ UndoLogControl *log = UndoLogGet(UndoRecPtrGetLogNo(urec_ptr), false);
+ UndoPersistence upersistence = log->meta.persistence;

Right. This should not be part of this patch so removed.

+ * When the undorecord for a transaction gets inserted in the next log then we

undo record

Changed

+ * insert a transaction header for the first record in the new log and update
+ * the transaction header with this new logs location.  We will also keep

This appears to be nonsensical. You're saying that you add a
transaction header to the new log and then update it with its own
location. That can't be what you mean.

Actually, what I meant is update the transaction's header which is in
the old log. I have changed the comments

+ * Incase, this is not the first record in new log (aka new log already

"Incase," -> "If"
"aka" -> "i.e."

Done

Overall this whole paragraph is a bit hard to understand.

I tired to improve it in newer version.

+ * same transaction spans across multiple logs depending on which log is

delete "across"

Fixed

+ * processed first by the discard worker.  If it processes the first log which
+ * contains the transactions first record, then it can get the last record
+ * of that transaction even if it is in different log and then processes all
+ * the undo records from last to first.  OTOH, if the next log get processed

Not sure how that would work if the number of logs is >2.
This whole paragraph is also hard to understand.

Actually, what I meant is that if it spread in multiple logs for
example 3 logs(1,2,3) and the discard worker check the log 1 first
then for aborted transaction it will follow the chain of undo headers
and register complete request for rollback and it will apply all undo
action in log1,2and 3 together. Whereas if it encounters log2 first
it will register request for undo actions in log2 and 3 and similarly
if it encounter log 3 first then it will only process that log. We
have done that so that we can maintain the order of undo apply.
However, there is possibility that we always collect all undos and
apply together but for that we need to add one more pointer in the
transaction header (transaction's undo header in previous log). May
be the next log pointer we can keep in separate header instead if
keeping in transaction header so that it will only occupy space on
log switch.

I think this comment don't belong here it's more related to undo
discard processing so I have removed.

+static UndoBuffers def_buffers[MAX_UNDO_BUFFERS];
+static int buffer_idx;

This is a file-global variable with a very generic name that is very
similar to a local variable name used by multiple functions within the
file (bufidx) and no comments. Ugh.

Comment added.

+UndoRecordIsValid(UndoLogControl * log, UndoRecPtr urp)

The locking regime for this function is really confusing. It requires
that the caller hold discard_lock on entry, and on exit the lock will
still be held if the return value is true but will no longer be held
if the return value is false. Yikes! Anybody who reads code that
uses this function is not going to guess that it has such strange
behavior. I'm not exactly sure how to redesign this, but I think it's
not OK the way you have it. One option might be to inline the logic
into each call site.

I think the simple solution will be that inside UndoRecordIsValid
function we can directly check UndoLogIsDiscarded if oldest_data is
not yet initialized, I think we don't need to release the discard lock
for that. So I have changed it like that.

+/*
+ * Overwrite the first undo record of the previous transaction to update its
+ * next pointer.  This will just insert the already prepared record by
+ * UndoRecordPrepareTransInfo.  This must be called under the critical section.
+ * This will just overwrite the undo header not the data.
+ */
+static void
+UndoRecordUpdateTransInfo(int idx)

It bugs me that this function goes back in to reacquire the discard
lock for the purpose of preventing a concurrent undo discard.
Apparently, if the other transaction's undo has been discarded between
the prepare phase and where we are now, we're OK with that and just
exit without doing anything; otherwise, we update the previous
transaction header. But that seems wrong. When we enter a critical
section, I think we should aim to know exactly what modifications we
are going to make within that critical section.

I also wonder how the concurrent discard could really happen. We must
surely be holding exclusive locks on the relevant buffers -- can undo
discard really discard undo when the relevant buffers are x-locked?

It seems to me that remaining_bytes is a crock that should be ripped
out entirely, both here and in InsertUndoRecord. It seems to me that
UndoRecordUpdateTransInfo can just contrive to set remaining_bytes
correctly. e.g.

do
{
// stuff
if (!BufferIsValid(buffer))
{
Assert(InRecovery);
already_written += (BLCKSZ - starting_byte);
done = (already_written >= undo_len);
}
else
{
page = BufferGetPage(buffer);
done = InsertUndoRecord(...);
MarkBufferDirty(buffer);
}
} while (!done);

InsertPreparedUndo needs similar treatment.

To make this work, I guess the long string of assignments in
InsertUndoRecord will need to be made unconditional, but that's
probably pretty cheap. As a fringe benefit, all of those work_blah
variables that are currently referencing file-level globals can be
replaced with something local to this function, which will surely
please the coding style police.

Got fixed as part of last comment fix where we introduced
SkipInsertingUndoData and globals moved to the context.

+ * In recovery, 'xid' refers to the transaction id stored in WAL, otherwise,
+ * it refers to the top transaction id because undo log only stores mapping
+ * for the top most transactions.
+ */
+UndoRecPtr
+PrepareUndoInsert(UnpackedUndoRecord *urec, FullTransactionId fxid,

xid vs fxid

+ urec->uur_xidepoch = EpochFromFullTransactionId(fxid);

We need to make some decisions about how we're going to handle 64-bit
XIDs vs. 32-bit XIDs in undo. This doesn't look like a well-considered
scheme. In general, PrepareUndoInsert expects the caller to have
populated the UnpackedUndoRecord, but here, uur_xidepoch is getting
overwritten with the high bits of the caller-specified XID. The
low-order bits aren't stored anywhere by this function, but the caller
is presumably expected to have placed them inside urec->uur_xid. And
it looks like the low-order bits (urec->uur_xid) get stored for every
undo record, but the high-order bits (urec->xidepoch) only get stored
when we emit a transaction header. This all seems very confusing.

Yeah it seems bit confusing. Actually, discard worker process the
transaction chain from one transaction header to the next transaction
header so we need epoch only when it's first record of the transaction
and currently we have set all header information inside
PrepareUndoInsert. Xid is stored by caller as caller needs it for the
MVCC purpose. I think caller can always set it and if transaction
header get added then it will be stored otherwise not. So I think we
can remove setting it here.

I would really like to see us replace uur_xid and uur_xidepoch with a
FullTransactionId; now that we have that concept, it seems like bad
practice to break what is really a FullTransactionId into two halves
and store them separately. However, it would be a bit unfortunate to
store an additional 4 bytes of mostly-static data in every undo
record. What if we went the other way? That is, remove urec_xid
from UndoRecordHeader and from UnpackedUndoRecord. Make it the
responsibility of whoever is looking up an undo record to know which
transaction's undo they are searching. zheap, at least, generally
does know this: if it's starting from a page, then it has the XID +
epoch available from the transaction slot, and if it's starting from
an undo record, you need to know the XID for which you are searching,
I guess from uur_prevxid.

Right, from uur_prevxid we would know that for which xid's undo we are
looking for but without having uur_xid in undo record it self how we
would know which undo record is inserted by the xid we are looking
for? Because in zheap while following the undo chain and if slot got
switch, then there is possibility (because of slot reuse) that we
might get some other transaction's undo record for the same zheap
tuple, but we want to traverse back as we want to find the record
inserted by uur_prevxid. So we need uur_xid as well to tell who is
inserter of this undo record?

I also think that we need to remove uur_prevxid. That field does not
seem to be properly a general-purpose part of the undo machinery, but
a zheap-specific consideration. I think it's job is to tell you which
transaction last modified the current tuple, but zheap can put that
data in the payload if it likes. It is a waste to store it in every
undo record, because it's not needed if the older undo has already
been discarded or if the operation is an insert.

Done

+ * Insert a previously-prepared undo record. This will write the actual undo

Looks like this now inserts all previously-prepared undo records
(rather than just a single one).

Fixed

+ * in page. We start writting immediately after the block header.

Spelling.

Done

+ * Helper function for UndoFetchRecord.  It will fetch the undo record pointed
+ * by urp and unpack the record into urec.  This function will not release the
+ * pin on the buffer if complete record is fetched from one buffer, so caller
+ * can reuse the same urec to fetch the another undo record which is on the
+ * same block.  Caller will be responsible to release the buffer inside urec
+ * and set it to invalid if it wishes to fetch the record from another block.
+ */
+UnpackedUndoRecord *
+UndoGetOneRecord(UnpackedUndoRecord *urec, UndoRecPtr urp, RelFileNode rnode,
+ UndoPersistence persistence)

I don't really understand why uur_buffer is part of an
UnpackedUndoRecord. It doesn't look like the other fields, which tell
you about the contents of an undo record that you have created or that
you have parsed. Instead, it's some kind of scratch space for keeping
track of a buffer that we're using in the process of reading an undo
record. It looks like it should be an argument to UndoGetOneRecord()
and ResetUndoRecord().

I also wonder whether it's really a good design to make the caller
responsible for invalidating the buffer before accessing another
block. Maybe it would be simpler to have this function just check
whether the buffer it's been given is the right one; if not, unpin it
and pin the new one instead. But I'm not really sure...

I am not sure what will be better here, But I thought caller anyway
has to release the last buffer so why not to make it responsible to
keeping the track of the first buffer of the undo record and caller
understands it better that it needs to hold the first buffer of the
undo record because it hope that the previous undo record in the chain
might fall on the same buffer?
May be we can make caller completely responsible for reading the
buffer for the first block of the undo record and it will always pass
the valid buffer and UndoGetOneRecord only need to read buffer if the
undo record is spilt and it can release them right there. So the
caller will always keep track of the first buffer where undo record
start and whenever the undo record pointer change it will be
responsible for changing the buffer.

+ /* If we already have a buffer pin then no need to allocate a new one. */
+ if (!BufferIsValid(buffer))
+ {
+ buffer = ReadBufferWithoutRelcache(SMGR_UNDO,
+    rnode, UndoLogForkNum, cur_blk,
+    RBM_NORMAL, NULL,
+    RelPersistenceForUndoPersistence(persistence));
+
+ urec->uur_buffer = buffer;
+ }

I think you should move this code inside the loop that follows. Then
at the bottom of that loop, instead of making a similar call, just set
buffer = InvalidBuffer. Then when you loop around it'll do the right
thing and you'll need less code.

Done

Notice that having both the local variable buffer and the structure
member urec->uur_buffer is actually making this code more complex. You
are already setting urec->uur_buffer = InvalidBuffer when you do
UnlockReleaseBuffer(). If you didn't have a separate 'buffer'
variable you wouldn't need to clear them both here. In fact I think
what you should have is an argument Buffer *curbuf, or something like
that, and no uur_buffer at all.

Done

+ /*
+ * If we have copied the data then release the buffer, otherwise, just
+ * unlock it.
+ */
+ if (is_undo_rec_split)
+ UnlockReleaseBuffer(buffer);
+ else
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);

Ugh. I think what's going on here is: UnpackUndoRecord copies the
data if it switches buffers, but not otherwise. So if the record is
split, we can release the lock and pin, but otherwise we have to keep
the pin to avoid having the data get clobbered. But having this code
know about what UnpackUndoRecord does internally seems like an
abstraction violation. It's also probably not right if we ever want
to fetch undo records in bulk, as I see that the latest code in zheap
master does. I think we should switch UnpackUndoRecord over to always
copying the data and just avoid all this.

Done

(To make that cheaper, we may want to teach UnpackUndoRecord to store
data into scratch space provided by the caller rather than using
palloc to get its own space, but I'm not actually sure that's (a)
worth it or (b) actually better.)

[ Skipping over some things ]

+bool
+UnpackUndoRecord(UnpackedUndoRecord *uur, Page page, int starting_byte,
+ int *already_decoded, bool header_only)

I think we should split this function into three functions that use a
context object, call it say UnpackUndoContext. The caller will do:

BeginUnpackUndo(&ucontext); // just once
UnpackUndoData(&ucontext, page, starting_byte); // any number of times
FinishUnpackUndo(&uur, &ucontext); // just once

The undo context will store an enum value that tells us the "stage" of decoding:

- UNDO_DECODE_STAGE_HEADER: We have not yet decoded even the record
header; we need to do that next.
- UNDO_DECODE_STAGE_RELATION_DETAILS: The next thing to be decoded is
the relation details, if present.
- UNDO_DECODE_STAGE_BLOCK: The next thing to be decoded is the block
details, if present.
- UNDO_DECODE_STAGE_TRANSACTION: The next thing to be decoded is the
transaction details, if present.
- UNDO_DECODE_STAGE_PAYLOAD: The next thing to be decoded is the
payload details, if present.
- UNDO_DECODE_STAGE_DONE: Decoding is complete.

It will also store the number of bytes that have been already been
copied as part of whichever stage is current. A caller who wants only
part of the record can stop when ucontext.stage > desired_stage; e.g.
the current header_only flag corresponds to stopping when
ucontext.stage > UNDO_DECODE_STAGE_HEADER, and the potential
optimization mentioned in UndoGetOneRecord could be done by stopping
when ucontext.stage > UNDO_DECODE_STAGE_BLOCK (although I don't know
if that's worth doing).

In this scheme, BeginUnpackUndo just needs to set the stage to
UNDO_DECODE_STAGE_HEADER and the number of bytes copied to 0. The
context object contains all the work_blah things (no more global
variables!), but BeginUnpackUndo does not need to initialize them,
since they will be overwritten before they are examined. And
FinishUnpackUndo just needs to copy all of the fields from the
work_blah things into the UnpackedUndoRecord. The tricky part is
UnpackUndoData itself, which I propose should look like a big switch
where all branches fall through. Roughly:

switch (ucontext->stage)
{
case UNDO_DECODE_STAGE_HEADER:
if (!ReadUndoBytes(...))
return;
stage = UNDO_DECODE_STAGE_RELATION_DETAILS;
/* FALLTHROUGH */
case UNDO_DECODE_STAGE_RELATION_DETAILS:
if ((uur->uur_info & UREC_INFO_RELATION_DETAILS) != 0)
{
if (!ReadUndoBytes(...))
return;
}
stage = UNDO_DECODE_STAGE_BLOCK;
/* FALLTHROUGH */
etc.

ReadUndoBytes would need some adjustments in this scheme; it wouldn't
need my_bytes_read any more since it would only get called for
structures that are not yet completely read.

Yeah so we can directly jump to the header which is not yet completely
read but if any of the header is partially read then we need to
maintain some kind of partial read variable otherwise from 'already
read' we wouldn't be able to know how many bytes of the header got
read in last call unless we calculate that from uur_info or maintain
the partial_read in context like I have done in the new version.

(Regardless of whether

we adopt this idea, the nocopy flag to ReadUndoBytes appears to be
unused and can be removed.)

Yup.

We could use a similar context object for InsertUndoRecord.
BeginInsertUndoRecord(&ucontext, &uur) would initialize all of the
work_blah structures within the context object. InsertUndoData will
be a big switch. Maybe no need for a "finish" function here. There
can also be a SkipInsertingUndoData function that can be called
instead of InsertUndoData if the page is discarded. I think this
would be more elegant than what we've got now.

Done.

Note:
- I think the ucontext->stage are same for the insert and DECODE can
we just declare only
one enum and give some generic name e.g. UNDO_PROCESS_STAGE_HEADER ?
- In SkipInsertingUndoData also I have to go through all the stages so
that if we find some valid
block then stage is right for inserting the partial record? Do you
think I could have avoided that?

Apart from these changes I have also included UndoRecordBulkFetch in
the undoinsert.c.

I have tested this patch with my local test modules which basically
insert, fetch and bulk fetch multiple records and compare the
contents. My test patch is still not in good shape so I will post the
test module later.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

Attachments:

0001-Enhance-multi-log-handling-in-undo-log.patchapplication/octet-stream; name=0001-Enhance-multi-log-handling-in-undo-log.patchDownload+102-3
0002-Add-prefetch-support-for-the-undo-log.patchapplication/octet-stream; name=0002-Add-prefetch-support-for-the-undo-log.patchDownload+88-37
0003-Provide-interfaces-to-store-and-fetch-undo-records_v2.patchapplication/octet-stream; name=0003-Provide-interfaces-to-store-and-fetch-undo-records_v2.patchDownload+2546-3
#14Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#13)
Re: POC: Cleaning up orphaned files using undo logs

On Thu, Apr 25, 2019 at 7:15 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

+static UndoBuffers def_buffers[MAX_UNDO_BUFFERS];
+static int buffer_idx;

This is a file-global variable with a very generic name that is very
similar to a local variable name used by multiple functions within the
file (bufidx) and no comments. Ugh.

Comment added.

The variable name is still bad, and the comment isn't very helpful
either. First, you can't tell by looking at the name that it has
anything to do with the undo_buffers variable, because undo_buffers
and buffer_idx are not obviously related names. Second, it's not an
index; it's a count. A count tells you how many of something you
have; an index tells you which one of those you are presently thinking
about. Third, the undo_buffer array is itself poorly named, because
it's not an array of all the undo buffers in the world or anything
like that, but rather an array of undo buffers for some particular
purpose. "static UndoBuffers *undo_buffer" is about as helpful as
"int integer" and I hope I don't need to explain why that isn't
usually a good thing to write. Maybe prepared_undo_buffer for the
array and nprepared_undo_buffer for the count, or something like that.

I think the simple solution will be that inside UndoRecordIsValid
function we can directly check UndoLogIsDiscarded if oldest_data is
not yet initialized, I think we don't need to release the discard lock
for that. So I have changed it like that.

Can we instead eliminate the special case? It seems like the if
(log->oldest_data == InvalidUndoRecPtr) case will be taken very
rarely, so if it's buggy, we might not notice.

Right, from uur_prevxid we would know that for which xid's undo we are
looking for but without having uur_xid in undo record it self how we
would know which undo record is inserted by the xid we are looking
for? Because in zheap while following the undo chain and if slot got
switch, then there is possibility (because of slot reuse) that we
might get some other transaction's undo record for the same zheap
tuple, but we want to traverse back as we want to find the record
inserted by uur_prevxid. So we need uur_xid as well to tell who is
inserter of this undo record?

It seems desirable to me to make this the caller's problem. When we
are processing undo a transaction at a time, we'll know the XID
because it will be available from the transaction header. If a system
like zheap maintains a pointer to an undo record someplace in the
middle of a transaction, it can also store the XID if it needs it.
The thing is, the zheap code almost works that way already.
Transaction slots within a page store both the undo record pointer and
the XID. The only case where zheap doesn't store the undo record
pointer and the XID is when a slot switch occurs, but that could be
changed.

If we moved urec_xid into UndoRecordTransaction, we'd save 4 bytes per
undo record across the board. When zheap emits undo records for
updates or deletes, they would need store an UndoRecPtr (8 bytes) +
FullTransactionId (8 bytes) in the payload unless the previous change
to that TID is all-visible or the previous change to that TID was made
by the same transaction. Also, zheap would no longer need to store
the slot number in the payload in any case, because this would
substitute for that (and permit more efficient lookups, to boot). So
the overall impact on zheap update and delete records would be
somewhere between -4 bytes (when we save the space used by XID and
incur no other cost) and +12 bytes (when we lose the XID but gain the
UndoRecPtr + FullTransactionId).

That worst case could be further optimized. For example, instead of
storing a FullTransactionId, zheap could store the difference between
the XID to which the current record pertains (which in this model the
caller is required to know) and the XID of whoever last modified the
tuple. That difference certainly can't exceed 4 billion (or even 2
billion) so 4 bytes is enough. That reduces the worst-case difference
to +8 bytes. Probably zheap could use payloads with some kind of
variable-length encoding and squeeze out even more in common cases,
but I'm not sure that's necessary or worth the complexity.

Let's also give uur_blkprev its own UREC_INFO_* constant and omit it
when this is the first time we're touching this block in this
transaction and thus the value is InvalidUndoRecPtr. In the
pretty-common case where a transaction updates one tuple on the page
and never comes back, this - together with the optimization in the
previous paragraph - will cause zheap to come out even on undo,
because it'll save 4 bytes by omitting urec_xid and 8 bytes by
omitting uur_blkrev, and it'll lose 8 bytes storing an UndoRecPtr in
the payload and 4 bytes storing an XID-difference.

Even with those changes, zheap's update and delete could still come
out a little behind on undo volume if hitting many tuples on the same
page, because for every tuple they hit after the first, we'll still
need the UndoRecPtr for the previous change to that page (uur_blkprev)
and we'll also have the UndoRecPtr extracted from the tuple's previous
slot, store in the payload. So we'll end up +8 bytes in this case. I
think that's acceptable, because it often won't happen, it's hardly
catastrophic if it does, and saving 4 bytes on every insert, and on
every update or delete where the old undo is already discarded is
pretty sweet.

Yeah so we can directly jump to the header which is not yet completely
read but if any of the header is partially read then we need to
maintain some kind of partial read variable otherwise from 'already
read' we wouldn't be able to know how many bytes of the header got
read in last call unless we calculate that from uur_info or maintain
the partial_read in context like I have done in the new version.

Right, we need to know the bytes already read for the next header.

Note:
- I think the ucontext->stage are same for the insert and DECODE can
we just declare only
one enum and give some generic name e.g. UNDO_PROCESS_STAGE_HEADER ?

I agree. Maybe UNDO_PACK_STAGE_WHATEVER or, more briefly, UNDO_PACK_WHATEVER.

- In SkipInsertingUndoData also I have to go through all the stages so
that if we find some valid
block then stage is right for inserting the partial record? Do you
think I could have avoided that?

Hmm, I didn't foresee that, but no, I don't think you can avoid that.
That problem wouldn't occur before we added the stage stuff, since
we'd just go through all the stages every time and each one would know
its own size and do nothing if that number of bytes had already been
passed, but with this design there seems to be no way around it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#15Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#1)
Re: POC: Cleaning up orphaned files using undo logs

Replying to myself to resend to the list, since my previous attempt
seems to have been eaten by a grue.

On Tue, Apr 30, 2019 at 11:14 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Apr 30, 2019 at 2:16 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

Like previous version these patch set also applies on:
https://github.com/EnterpriseDB/zheap/tree/undo
(b397d96176879ed5b09cf7322b8d6f2edd8043a5)

Some more review of 0003:

There is a whitespace-only hunk in xact.c.

It would be nice if AtAbort_ResetUndoBuffers didn't need to exist at
all. Then, this patch would make no changes whatsoever to xact.c.
We'd still need such changes in other patches, because the whole idea
of undo is tightly bound up with the concept of transactions. Yet,
this particular patch wouldn't touch that file, and that would be
nice. In general, the reason why we need AtCommit/AtAbort/AtEOXact
callbacks is to adjust the values of global variables (or the data
structures to which they point) at commit or abort time. And that is
also the case here. The thing is, I'm not sure why we need these
particular global variables. Is there some way that we can get by
without them? The obvious thing to do seems to be to invent yet
another context object, allocated via a new function, which can then
be passed to PrepareUndoInsert, InsertPreparedUndo,
UndoLogBuffersSetLSN, UnlockReleaseUndoBuffers, etc. This would
obsolete UndoSetPrepareSize, since you'd instead pass the size to the
context allocator function.

UndoRecordUpdateTransInfo should declare a local variable
XactUndoRecordInfo *something = &xact_urec_info[idx] and use that
variable wherever possible.

It should also probably use while (1) { ... } rather than do { ... }
while (true).

In UndoBufferGetSlot you could replace 'break;' with 'return i;' and
then more than half the function would need one less level of
indentation. This function should also declare PreparedUndoBuffer
*something and set that variable equal to &prepared_undo_buffers[i] at
the top of the loop and again after the loop, and that variable should
then be used whenever possible.

UndoRecordRelationDetails seems to need renaming now that it's down to
a single member.

The comment for UndoRecordBlock needs updating, as it still refers to blkprev.

The comment for UndoRecordBlockPrev refers to "Identifying
information" but it's not identifying anything. I think you could
just delete "Identifying information for" from this sentence and not
lose anything. And I think several of the nearby comments that refer
to "Identifying information" could more simply and more correctly just
refer to "Information".

I don't think that SizeOfUrecNext needs to exist.

xact.h should not add an include for undolog.h. There are no other
changes to this header, so presumably the header does not depend on
anything in undolog.h. If .c files that include xact.h need
undolog.h, then the header should be included in those files, not in
the header itself. That way, we avoid making partial recompiles more
painful than necessary.

UndoGetPrevUndoRecptr looks like a strange interface. Why not just
arrange not to call the function in the first place if prevurp is
valid?

Every use of palloc0 in this code should be audited to check whether
it is really necessary to zero the memory before use. If it will be
initialized before it's used for anything anyway, it doesn't need to
be pre-zeroed.

FinishUnpackUndo looks nice! But it is missing a blank line in one
place, and 'if it presents' should be 'if it is present' in a whole
bunch of places.

BeginInsertUndo also looks to be missing a few blank lines.

Still need to do some cleanup of the UnpackedUndoRecord, e.g. unifying
uur_xid and uur_xidepoch into uur_fxid.

InsertUndoData ends in an unnecessary return, as does SkipInsertingUndoData.

I think the argument to SkipInsertingUndoData should be the number of
bytes to skip, rather than the starting byte within the block.

Function header comment formatting is not consistent. Compare
FinishUnpackUndo (function name recapitulated on first line of
comment) to ReadUndoBytes (not recapitulated) to UnpackUndoData
(entire header comment jammed into one paragraph with function name at
start). I prefer the style where the function name is not restated,
but YMMV. Anyway, it has to be consistent.

UndoGetPrevRecordLen should not declare char *page instead of Page
page, I think.

UndoRecInfo looks a bit silly, I think. Isn't index just the index of
this entry in the array? You can always figure that out by ptr -
array_base. Instead of having UndoRecPtr urp in this structure, how
about adding that to UnpackedUndoRecord? When inserting, caller
leaves it unset and InsertPreparedUndo sets it; when retrieving,
UndoFetchRecord or UndoRecordBulkFetch sets it. With those two
changes, I think you can get rid of UndoRecInfo entirely and just
return an array of UnpackedUndoRecords. This might also eliminate the
need for an 'urp' member in PreparedUndoSpace.

I'd probably use UREC_INFO_BLKPREV rather than UREC_INFO_BLOCKPREV for
consistency.

Similarly UndoFetchRecord and UndoRecordBulkFetch seems a bit
inconsistent. Perhaps UndoBulkFetchRecord.

In general, I find the code for updating transaction headers to be
really hard to understand. I'm not sure exactly what can be done
about that. Like, why is UndoRecordPrepareTransInfo unpacking undo?
Why does it take two undo record pointers as arguments and how are
they different?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#15)
Re: POC: Cleaning up orphaned files using undo logs

On Wed, May 1, 2019 at 6:02 AM Robert Haas <robertmhaas@gmail.com> wrote:

Replying to myself to resend to the list, since my previous attempt
seems to have been eaten by a grue.

On Tue, Apr 30, 2019 at 11:14 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Apr 30, 2019 at 2:16 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

Like previous version these patch set also applies on:
https://github.com/EnterpriseDB/zheap/tree/undo
(b397d96176879ed5b09cf7322b8d6f2edd8043a5)

Some more review of 0003:

Another suggestion:

+/*
+ * Insert a previously-prepared undo records.  This will write the actual undo
+ * record into the buffers already pinned and locked in PreparedUndoInsert,
+ * and mark them dirty.  This step should be performed after entering a
+ * criticalsection; it should never fail.
+ */
+void
+InsertPreparedUndo(void)
+{
..
..
+
+ /* Advance the insert pointer past this record. */
+ UndoLogAdvance(urp, size);
+ }
..
}

UndoLogAdvance internally takes LWLock and we don't recommend doing
that in the critical section which will happen as this function is
supposed to be invoked in the critical section as mentioned in
comments.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#17Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#1)
Re: POC: Cleaning up orphaned files using undo logs

Thomas told me offlist that this email of mine didn't hit
pgsql-hackers, so trying it again by resending.

On Mon, Apr 29, 2019 at 3:51 PM Amit Kapila <amit.kapila16@gmail.com> wrote:

On Fri, Apr 19, 2019 at 3:46 PM Dilip Kumar <dilipbalaut@gmail.com> wrote:

On Tue, Mar 12, 2019 at 6:51 PM Thomas Munro <thomas.munro@gmail.com> wrote:

Currently, undo branch[1] contain an older version of the (undo
interface + some fixup). Now, I have merged the latest changes from
the zheap branch[2] to the undo branch[1]
which can be applied on top of the undo storage commit[3]. For
merging those changes, I need to add some changes to the undo log
storage patch as well for handling the multi log transaction. So I
have attached two patches, 1) improvement to undo log storage 2)
complete undo interface patch which include 0006+0007 from undo
branch[1] + new changes on the zheap branch.

[1] https://github.com/EnterpriseDB/zheap/tree/undo
[2] https://github.com/EnterpriseDB/zheap
[3] https://github.com/EnterpriseDB/zheap/tree/undo
(b397d96176879ed5b09cf7322b8d6f2edd8043a5)

[]

Dilip has posted the patch for "undo record interface", next in series
is a patch that handles transaction rollbacks (the machinery to
perform undo actions) and background workers to manage undo.

Transaction Rollbacks
----------------------------------
We always perform rollback actions after cleaning up the current
(sub)transaction. This will ensure that we perform the actions
immediately after an error (and release the locks) rather than when
the user issues Rollback command at some later point of time. We are
releasing the locks after the undo actions are applied. The reason to
delay lock release is that if we release locks before applying undo
actions, then the parallel session can acquire the lock before us
which can lead to deadlock.

We promote the error to FATAL error if it occurred while applying undo
for a subtransaction. The reason we can't proceed without applying
subtransaction's undo is that the modifications made in that case must
not be visible even if the main transaction commits. Normally, the
backends that receive the request to perform Rollback (To Savepoint)
applies the undo actions, but there are cases where it is preferable
to push the requests to background workers. The main reasons to push
the requests to background workers are (a) The request for a very
large rollback, this will allow us to return control to users quickly.
There is a guc rollback_overflow_size which indicates that rollbacks
greater than the configured size are performed lazily by background
workers. (b) While applying the undo actions, if there is an error, we
push such a request to background workers.

Undo Requests and Undo workers
--------------------------------------------------
To improve the efficiency of the rollbacks, we create three queues and
a hash table for the rollback requests. A Xid based priority queue
which will allow us to process the requests of older transactions and
help us to move oldesdXidHavingUndo (this is a xid-horizon below which
all the transactions are visible) forward. A size-based queue which
will help us to perform the rollbacks of larger aborts in a timely
fashion so that we don't get stuck while processing them during
discard of the logs. An error queue to hold the requests for
transactions that failed to apply its undo. The rollback hash table
is used to avoid duplicate undo requests by backends and discard
worker.

Undo launcher is responsible for launching the workers iff there is
some work available in one of the work queues and there are more
workers available. The worker is launched to handle requests for a
particular database. Each undo worker then start reading from one of
the queues the requests for that particular database. A worker would
peek into each queue for the requests from a particular database if it
needs to switch a database in less than undo_worker_quantum ms (10s as
default) after starting. Also, if there is no work, it lingers for
UNDO_WORKER_LINGER_MS (10s as default). This avoids restarting the
workers too frequently.

The discard worker is responsible for discarding the undo log of
transactions that are committed and all-visible or are rolled-back.
It also registers the request for aborted transactions in the work
queues. It iterates through all the active logs one-by-one and tries
to discard the transactions that are old enough to matter.

The details of how all of this works are described in
src/backend/access/undo/README.UndoProcessing. The main idea to keep
a readme is to allow reviewers to understand this patch, later we can
decide parts of it to move to comments in code and others to main
README of undo.

Question: Currently, TwoPhaseFileHeader stores just TransactionId, so
for the systems (like zheap) that support FullTransactionId, the
two-phase transaction will be tricky to support as we need
FullTransactionId during rollbacks. Is it a good idea to store
FullTransactionId in TwoPhaseFileHeader?

Credits:
--------------
Designed by: Andres Freund, Amit Kapila, Robert Haas, and Thomas Munro
Author: Amit Kapila, Dilip Kumar, Kuntal Ghosh, and Thomas Munro

This patch is based on the latest Dilip's patch for undo record
interface. The branch can be accessed at
https://github.com/EnterpriseDB/zheap/tree/undoprocessing

Inputs on design/code are welcome.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

Attachments:

undoprocessing_1.patchapplication/octet-stream; name=undoprocessing_1.patchDownload+4739-73
#18Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#1)
Re: POC: Cleaning up orphaned files using undo logs

On Tue, Apr 30, 2019 at 11:45 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

The attached patch will provide mechanism for masking the necessary
bits in undo pages for supporting consistency checking for the undo
pages. Ideally we can merge this patch with the main interface patch
but currently I have kept it separate for mainly because a) this is
still a WIP patch and b) review of the changes will be easy.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

Attachments:

0005-undo-page-consistency-checker_WIP_v3.patchapplication/octet-stream; name=0005-undo-page-consistency-checker_WIP_v3.patchDownload+201-12
#19Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#1)
Re: POC: Cleaning up orphaned files using undo logs

On Tue, Apr 30, 2019 at 8:44 PM Robert Haas <robertmhaas@gmail.com> wrote:

UndoRecInfo looks a bit silly, I think. Isn't index just the index of
this entry in the array? You can always figure that out by ptr -
array_base. Instead of having UndoRecPtr urp in this structure, how
about adding that to UnpackedUndoRecord? When inserting, caller
leaves it unset and InsertPreparedUndo sets it; when retrieving,
UndoFetchRecord or UndoRecordBulkFetch sets it. With those two
changes, I think you can get rid of UndoRecInfo entirely and just
return an array of UnpackedUndoRecords. This might also eliminate the
need for an 'urp' member in PreparedUndoSpace.

Yeah, at least in this patch it looks silly. Actually, I added that
index to make the qsort stable when execute_undo_action sorts them in
block order. But, as part of this patch we don't have that processing
so either we can remove this structure completely as you suggested but
undo processing patch has to add that structure or we can just add
comment that why we added this index field.

I am ok with other comments and will work on them.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

#20Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#19)
Re: POC: Cleaning up orphaned files using undo logs

On Thu, May 2, 2019 at 5:32 AM Dilip Kumar <dilipbalaut@gmail.com> wrote:

Yeah, at least in this patch it looks silly. Actually, I added that
index to make the qsort stable when execute_undo_action sorts them in
block order. But, as part of this patch we don't have that processing
so either we can remove this structure completely as you suggested but
undo processing patch has to add that structure or we can just add
comment that why we added this index field.

Well, the qsort comparator could compute the index as ptr - array_base
just like any other code, couldn't it?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#21Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#20)
#22Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#21)
#23Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#17)
#24Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#1)
#25Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#24)
#26Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#16)
#27Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#24)
#28Thomas Munro
thomas.munro@gmail.com
In reply to: Dilip Kumar (#27)
#29Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Thomas Munro (#28)
#30Thomas Munro
thomas.munro@gmail.com
In reply to: Kuntal Ghosh (#29)
#31Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#27)
#32Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#31)
#33Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#32)
#34Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#32)
#35Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#23)
#36Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#35)
#37Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#35)
#38Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#37)
#39Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#38)
#40Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#37)
#41Asim R P
apraveen@pivotal.io
In reply to: Amit Kapila (#40)
#42Thomas Munro
thomas.munro@gmail.com
In reply to: Asim R P (#41)
#43Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#40)
#44Thomas Munro
thomas.munro@gmail.com
In reply to: Thomas Munro (#42)
#45Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#44)
#46Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#43)
#47Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#46)
#48Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#47)
#49Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#48)
#50Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#49)
#51Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#49)
#52Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#50)
#53Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#51)
#54Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#49)
#55Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#52)
#56Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#53)
#57Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#54)
#58Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#57)
#59Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#54)
#60Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#56)
#61Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#59)
#62Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#58)
#63Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#60)
#64Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#63)
#65Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#57)
#66Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#65)
#67Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#66)
#68Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#64)
#69Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#68)
#70Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#65)
#71Thomas Munro
thomas.munro@gmail.com
In reply to: Robert Haas (#69)
#72Thomas Munro
thomas.munro@gmail.com
In reply to: Thomas Munro (#71)
#73Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#71)
#74Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#73)
#75Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#74)
#76Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#68)
#77Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#71)
#78Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#76)
#79Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#73)
#80Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#77)
#81Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#80)
#82Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#79)
#83Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#77)
#84Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#83)
#85Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#82)
#86Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#84)
#87Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#85)
#88Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#84)
#89Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#86)
#90Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#89)
#91Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#90)
#92Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#91)
#93Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#71)
#94Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#93)
#95Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#92)
#96Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#87)
#97Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#93)
#98Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#71)
#99Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#97)
#100Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#99)
#101Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#94)
#102Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#97)
#103Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#71)
#104Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#91)
#105Thomas Munro
thomas.munro@gmail.com
In reply to: Dilip Kumar (#98)
#106Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#92)
#107Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#106)
#108Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#105)
#109Thomas Munro
thomas.munro@gmail.com
In reply to: Dilip Kumar (#108)
#110Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#109)
#111Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#101)
#112Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#106)
#113Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#102)
#114Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#104)
#115Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#114)
#116Thomas Munro
thomas.munro@gmail.com
In reply to: Robert Haas (#93)
#117Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#96)
#118Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#71)
#119Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#111)
#120Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#119)
#121Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#87)
#122Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Dilip Kumar (#27)
#123Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#120)
#124Robert Haas
robertmhaas@gmail.com
In reply to: Amit Khandekar (#122)
#125Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#106)
#126Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#125)
#127Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#126)
In reply to: Robert Haas (#125)
#129Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#127)
#130Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#128)
#131Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#129)
#132Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#131)
In reply to: Robert Haas (#130)
#134Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#133)
In reply to: Robert Haas (#134)
#136Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#124)
#137Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#123)
#138Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#125)
#139Amit Kapila
amit.kapila16@gmail.com
In reply to: Peter Geoghegan (#133)
#140Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#123)
#141Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#140)
#142Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#116)
#143Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#142)
#144Thomas Munro
thomas.munro@gmail.com
In reply to: Peter Geoghegan (#135)
#145Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Kapila (#142)
#146Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Khandekar (#122)
#147Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#142)
#148Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Khandekar (#145)
#149Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Kapila (#148)
#150Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#143)
#151Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Dilip Kumar (#150)
#152Dilip Kumar
dilipbalaut@gmail.com
In reply to: Rushabh Lathia (#151)
#153Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#118)
#154Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#153)
#155Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#153)
#156vignesh C
vignesh21@gmail.com
In reply to: Amit Kapila (#155)
#157Amit Kapila
amit.kapila16@gmail.com
In reply to: vignesh C (#156)
#158vignesh C
vignesh21@gmail.com
In reply to: Amit Kapila (#157)
#159Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: vignesh C (#158)
#160Thomas Munro
thomas.munro@gmail.com
In reply to: Amit Kapila (#153)
#161Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#160)
#162Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#153)
#163vignesh C
vignesh21@gmail.com
In reply to: vignesh C (#158)
#164Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#160)
#165Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#161)
#166Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Khandekar (#149)
#167Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#150)
#168Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Kapila (#166)
#169Andres Freund
andres@anarazel.de
In reply to: Thomas Munro (#160)
#170Thomas Munro
thomas.munro@gmail.com
In reply to: Andres Freund (#169)
#171Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#68)
#172Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#170)
#173Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#135)
In reply to: Thomas Munro (#144)
#175Robert Haas
robertmhaas@gmail.com
In reply to: Amit Khandekar (#149)
#176Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#174)
In reply to: Robert Haas (#173)
In reply to: Robert Haas (#176)
In reply to: Peter Geoghegan (#178)
#180Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#178)
In reply to: Robert Haas (#180)
#182Thomas Munro
thomas.munro@gmail.com
In reply to: Peter Geoghegan (#177)
#183Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#171)
In reply to: Thomas Munro (#182)
#185Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#175)
#186Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#121)
#187Thomas Munro
thomas.munro@gmail.com
In reply to: Dilip Kumar (#186)
#188Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#183)
#189Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#187)
#190Thomas Munro
thomas.munro@gmail.com
In reply to: Amit Kapila (#185)
#191Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#188)
#192Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#190)
#193Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#93)
#194Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#150)
#195Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#192)
#196Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#143)
#197Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Amit Kapila (#193)
#198Amit Kapila
amit.kapila16@gmail.com
In reply to: Heikki Linnakangas (#197)
#199Thomas Munro
thomas.munro@gmail.com
In reply to: Amit Kapila (#198)
#200Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Thomas Munro (#199)
#201Amit Kapila
amit.kapila16@gmail.com
In reply to: Heikki Linnakangas (#200)
#202Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#201)
#203Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#197)
#204Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#188)
#205Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#204)
#206Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#188)
#207Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#205)
#208Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#202)
#209Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#206)
#210Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#209)
#211Thomas Munro
thomas.munro@gmail.com
In reply to: Andres Freund (#210)
#212Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Andres Freund (#209)
#213Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#209)
#214Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#209)
#215Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#209)
#216Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#189)
#217Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Robert Haas (#203)
#218Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Dilip Kumar (#216)
#219Thomas Munro
thomas.munro@gmail.com
In reply to: Amit Kapila (#195)
#220Amit Kapila
amit.kapila16@gmail.com
In reply to: Thomas Munro (#219)
#221Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#215)
#222Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#221)
#223Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#222)
#224Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#143)
#225Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Khandekar (#145)
#226Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Khandekar (#149)
#227Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Khandekar (#168)
#228Robert Haas
robertmhaas@gmail.com
In reply to: Heikki Linnakangas (#217)
#229Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#228)
#230Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#117)
#231Dilip Kumar
dilipbalaut@gmail.com
In reply to: Amit Kapila (#121)
#232Dilip Kumar
dilipbalaut@gmail.com
In reply to: Rushabh Lathia (#151)
#233Dilip Kumar
dilipbalaut@gmail.com
In reply to: Thomas Munro (#187)
#234Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#188)
#235Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#222)
#236Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#221)
#237Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#206)
#238Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#209)
#239Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#235)
#240Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#210)
#241Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#240)
#242Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#234)
#243Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#237)
#244Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#241)
#245Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#244)
#246Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#241)
#247Thomas Munro
thomas.munro@gmail.com
In reply to: Amit Kapila (#155)
#248Thomas Munro
thomas.munro@gmail.com
In reply to: Kuntal Ghosh (#159)
#249Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#246)
#250Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#249)
#251Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#243)
#252Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#251)
#253Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#251)
#254Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#244)
#255Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#252)
#256Andres Freund
andres@anarazel.de
In reply to: Amit Kapila (#255)
#257Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#256)
#258Thomas Munro
thomas.munro@gmail.com
In reply to: Thomas Munro (#248)
#259Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#244)
#260Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#252)
#261Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#254)
#262Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#255)
#263Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#256)
#264Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#257)
#265Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#260)
#266Andres Freund
andres@anarazel.de
In reply to: Thomas Munro (#258)
#267Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#259)
#268Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#266)
#269Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#258)
#270Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#239)
#271Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#261)
#272Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#264)
#273Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#271)
#274Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#272)
#275Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#274)
#276Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#240)
#277Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#273)
#278Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#277)
#279Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#278)
#280Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#279)
#281Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#280)
#282Amit Kapila
amit.kapila16@gmail.com
In reply to: Dilip Kumar (#281)
#283Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#280)
#284Robert Haas
robertmhaas@gmail.com
In reply to: Dilip Kumar (#281)
#285Dilip Kumar
dilipbalaut@gmail.com
In reply to: Robert Haas (#284)
#286Dilip Kumar
dilipbalaut@gmail.com
In reply to: Dilip Kumar (#285)
#287Andres Freund
andres@anarazel.de
In reply to: Dilip Kumar (#286)
#288Dilip Kumar
dilipbalaut@gmail.com
In reply to: Andres Freund (#287)
#289Thomas Munro
thomas.munro@gmail.com
In reply to: Andres Freund (#266)
#290Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#289)
#291Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#276)
#292Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Robert Haas (#291)
#293Thomas Munro
thomas.munro@gmail.com
In reply to: Kuntal Ghosh (#292)
#294vignesh C
vignesh21@gmail.com
In reply to: Thomas Munro (#293)
#295Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: vignesh C (#294)
#296Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Thomas Munro (#293)
#297Thomas Munro
thomas.munro@gmail.com
In reply to: Kuntal Ghosh (#296)
#298Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Thomas Munro (#297)
#299Robert Haas
robertmhaas@gmail.com
In reply to: Kuntal Ghosh (#298)
#300Thomas Munro
thomas.munro@gmail.com
In reply to: Kuntal Ghosh (#298)
#301Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#299)
#302Michael Paquier
michael@paquier.xyz
In reply to: Thomas Munro (#300)
#303Thomas Munro
thomas.munro@gmail.com
In reply to: Michael Paquier (#302)
#304Antonin Houska
ah@cybertec.at
In reply to: Thomas Munro (#303)
#305Thomas Munro
thomas.munro@gmail.com
In reply to: Antonin Houska (#304)
#306Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#304)
#307Antonin Houska
ah@cybertec.at
In reply to: Thomas Munro (#305)
#308Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#306)
#309Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#308)
#310Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Kapila (#309)
#311Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#308)
#312Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#309)
#313Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#312)
#314Antonin Houska
ah@cybertec.at
In reply to: Antonin Houska (#312)
#315Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#313)
#316Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#315)
#317Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#314)
#318Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#317)
#319Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#316)
#320Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#311)
#321Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#320)
#322Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Antonin Houska (#320)
#323Antonin Houska
ah@cybertec.at
In reply to: Dmitry Dolgov (#322)
#324Antonin Houska
ah@cybertec.at
In reply to: Antonin Houska (#323)
#325Bruce Momjian
bruce@momjian.us
In reply to: Antonin Houska (#324)
#326Antonin Houska
ah@cybertec.at
In reply to: Bruce Momjian (#325)
#327tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Antonin Houska (#326)
#328Amit Kapila
amit.kapila16@gmail.com
In reply to: tsunakawa.takay@fujitsu.com (#327)
#329tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: Amit Kapila (#328)
#330Antonin Houska
ah@cybertec.at
In reply to: tsunakawa.takay@fujitsu.com (#329)
#331Antonin Houska
ah@cybertec.at
In reply to: tsunakawa.takay@fujitsu.com (#329)
#332Antonin Houska
ah@cybertec.at
In reply to: Antonin Houska (#331)
#333vignesh C
vignesh21@gmail.com
In reply to: Antonin Houska (#332)
#334Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Antonin Houska (#332)
#335Antonin Houska
ah@cybertec.at
In reply to: Dmitry Dolgov (#334)
#336Antonin Houska
ah@cybertec.at
In reply to: Antonin Houska (#335)
#337Antonin Houska
ah@cybertec.at
In reply to: Antonin Houska (#335)
#338Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#336)
#339Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Antonin Houska (#337)
#340Amit Kapila
amit.kapila16@gmail.com
In reply to: Dmitry Dolgov (#339)
#341Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#340)
#342Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#338)
#343Antonin Houska
ah@cybertec.at
In reply to: Dmitry Dolgov (#339)
#344Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Antonin Houska (#343)
#345Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#341)
#346Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#342)
#347Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#345)
#348Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#347)
#349Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#348)
#350Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#349)
#351Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#346)
#352Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Dmitry Dolgov (#344)
#353Antonin Houska
ah@cybertec.at
In reply to: Dmitry Dolgov (#352)
#354Thomas Munro
thomas.munro@gmail.com
In reply to: Antonin Houska (#353)
#355Antonin Houska
ah@cybertec.at
In reply to: Thomas Munro (#354)
#356Amit Kapila
amit.kapila16@gmail.com
In reply to: Antonin Houska (#351)
#357Antonin Houska
ah@cybertec.at
In reply to: Amit Kapila (#356)
#358Julien Rouhaud
rjuju123@gmail.com
In reply to: Antonin Houska (#357)
#359孔凡深(云梳)
fanshen.kfs@alibaba-inc.com
In reply to: Antonin Houska (#336)
#360Antonin Houska
ah@cybertec.at
In reply to: 孔凡深(云梳) (#359)