agora inbox for [email protected]  
help / color / mirror / Atom feed
Re: Patch: Write Amplification Reduction Method (WARM)
151+ messages / 9 participants
[nested] [flat]

* Re: Patch: Write Amplification Reduction Method (WARM)
@ 2017-04-05 02:21 Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  0 siblings, 1 reply; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-05 02:21 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Thu, Mar 30, 2017 at 7:55 PM, Robert Haas <[email protected]> wrote:

>  but
> try to access the TOAST table would be fatal; that probably would have
> deadlock hazards among other problems.


Hmm. I think you're right. We could make a copy of the heap tuple, drop the
lock and then access TOAST to handle that. Would that work?

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-05 03:12 ` Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  0 siblings, 1 reply; 151+ messages in thread

From: Robert Haas @ 2017-04-05 03:12 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Tue, Apr 4, 2017 at 10:21 PM, Pavan Deolasee
<[email protected]> wrote:
> On Thu, Mar 30, 2017 at 7:55 PM, Robert Haas <[email protected]> wrote:
>>  but
>> try to access the TOAST table would be fatal; that probably would have
>> deadlock hazards among other problems.
>
> Hmm. I think you're right. We could make a copy of the heap tuple, drop the
> lock and then access TOAST to handle that. Would that work?

Yeah, but it might suck.  :-)

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


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-05 03:43   ` Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  0 siblings, 1 reply; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-05 03:43 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 5, 2017 at 8:42 AM, Robert Haas <[email protected]> wrote:

> On Tue, Apr 4, 2017 at 10:21 PM, Pavan Deolasee
> <[email protected]> wrote:
> > On Thu, Mar 30, 2017 at 7:55 PM, Robert Haas <[email protected]>
> wrote:
> >>  but
> >> try to access the TOAST table would be fatal; that probably would have
> >> deadlock hazards among other problems.
> >
> > Hmm. I think you're right. We could make a copy of the heap tuple, drop
> the
> > lock and then access TOAST to handle that. Would that work?
>
> Yeah, but it might suck.  :-)


Well, better than causing a deadlock ;-)

Lets see if we want to go down the path of blocking WARM when tuples have
toasted attributes. I submitted a patch yesterday, but having slept over
it, I think I made mistakes there. It might not be enough to look at the
caller supplied new tuple because that may not have any toasted values, but
the final tuple that gets written to the heap may be toasted. We could look
at the new tuple's attributes to find if any indexed attributes are
toasted, but that might suck as well. Or we can simply block WARM if the
old or the new tuple has external attributes i.e. HeapTupleHasExternal()
returns true. That could be overly restrictive because irrespective of
whether the indexed attributes are toasted or just some other attribute is
toasted, we will block WARM on such updates. May be that's not a problem.

We will also need to handle the case where some older tuple in the chain
has toasted value and that tuple is presented to recheck (I think we can
handle that case fairly easily, but its not done in the code yet) because
of a subsequent WARM update and the tuples updated by WARM did not have any
toasted values (and hence allowed).

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-05 13:36     ` Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-05 18:32       ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  0 siblings, 2 replies; 151+ messages in thread

From: Robert Haas @ 2017-04-05 13:36 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Tue, Apr 4, 2017 at 11:43 PM, Pavan Deolasee
<[email protected]> wrote:
> Well, better than causing a deadlock ;-)

Yep.

> Lets see if we want to go down the path of blocking WARM when tuples have
> toasted attributes. I submitted a patch yesterday, but having slept over it,
> I think I made mistakes there. It might not be enough to look at the caller
> supplied new tuple because that may not have any toasted values, but the
> final tuple that gets written to the heap may be toasted.

Yes, you have to make whatever decision you're going to make here
after any toast-ing has been done.

> We could look at
> the new tuple's attributes to find if any indexed attributes are toasted,
> but that might suck as well. Or we can simply block WARM if the old or the
> new tuple has external attributes i.e. HeapTupleHasExternal() returns true.
> That could be overly restrictive because irrespective of whether the indexed
> attributes are toasted or just some other attribute is toasted, we will
> block WARM on such updates. May be that's not a problem.

Well, I think that there's some danger of whittling down this
optimization to the point where it still incurs most of the costs --
in bit-space if not in CPU cycles -- but no longer yields much of the
benefit.  Even though the speed-up might still be substantial in the
cases where the optimization kicks in, if a substantial number of
users doing things that are basically pretty normal sometimes fail to
get the optimization, this isn't going to be very exciting outside of
synthetic benchmarks.

Backing up a little bit, it seems like the root of the issue here is
that, at a certain point in what was once a HOT chain, you make a WARM
update, and you make a decision about which indexes to update at that
point.  Now, later on, when you traverse that chain, you need to be
able to figure what decide you made before; otherwise, you might make
a bad decision about whether an index pointer applies to a particular
tuple.  If the index tuple is WARM, then the answer is "yes" if the
heap tuple is also WARM, and "no" if the heap tuple is CLEAR (which is
an odd antonym to WARM, but leave that aside).  If the index tuple is
CLEAR, then the answer is "yes" if the heap tuple is also CLEAR, and
"maybe" if the heap tuple is WARM.

In that "maybe" case, we are trying to reconstruct the decision that
we made when we did the update.  If, at the time of the update, we
decided to insert a new index entry, then the answer is "no"; if not,
it's "yes".  From an integrity point of view, it doesn't really matter
how we make the decision; what matters is that we're consistent.  More
specifically, if we sometimes insert a new index tuple even when the
value has not changed in any user-visible way, I think that would be
fine, provided that later chain traversals can tell that we did that.
As an extreme example, suppose that the WARM update inserted in some
magical way a bitmap of which attributes had changed into the new
tuple.  Then, when we are walking the chain following a CLEAR index
tuple, we test whether the index columns overlap with that bitmap; if
they do, then that index got a new entry; if not, then it didn't.  It
would actually be fine (apart from efficiency) to set extra bits in
this bitmap; extra indexes would get updated, but chain traversal
would know exactly which ones, so no problem.  This is of course just
a gedankenexperiment, but the point is that as long as the insert
itself and later chain traversals agree on the rule, there's no
integrity problem.  I think.

The first idea I had for an actual solution to this problem was to
make the decision as to whether to insert new index entries based on
whether the indexed attributes in the final tuple (post-TOAST) are
byte-for-byte identical with the original tuple.  If somebody injects
a new compression algorithm into the system, or just changes the
storage parameters on a column, or we re-insert an identical value
into the TOAST table when we could have reused the old TOAST pointer,
then you might have some potentially-WARM updates that end up being
done as regular updates, but that's OK.  When you are walking the
chain, you will KNOW whether you inserted new index entries or not,
because you can do the exact same comparison that was done before and
be sure of getting the same answer.  But that's actually not really a
solution, because it doesn't work if all of the CLEAR tuples are gone
-- all you have is the index tuple and the new heap tuple; there's no
old heap tuple with which to compare.

The only other idea that I have for a really clean solution here is to
support this only for index types that are amcanreturn, and actually
compare the value stored in the index tuple with the one stored in the
heap tuple, ensuring that new index tuples are inserted whenever they
don't match and then using the exact same test to determine the
applicability of a given index pointer to a given heap tuple.  I'm not
sure how viable that is either, but hopefully you see my underlying
point here: it would be OK for there to be cases where we fall back to
a non-WARM update because a logically equal value changed at the
physical level, especially if those cases are likely to be rare in
practice, but it can never be allowed to happen that chain traversal
gets confused about which indexes actually got touched by a particular
WARM update.

By the way, the "Converting WARM chains back to HOT chains" section of
README.WARM seems to be out of date.  Any chance you could update that
to reflect the current state and thinking of the patch?

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


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-05 18:27       ` Andres Freund <[email protected]>
  2017-04-05 18:50         ` Re: Patch: Write Amplification Reduction Method (WARM) Peter Geoghegan <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 2 replies; 151+ messages in thread

From: Andres Freund @ 2017-04-05 18:27 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Pavan Deolasee <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On 2017-04-05 09:36:47 -0400, Robert Haas wrote:
> By the way, the "Converting WARM chains back to HOT chains" section of
> README.WARM seems to be out of date.  Any chance you could update that
> to reflect the current state and thinking of the patch?

I propose we move this patch to the next CF.  That shouldn't prevent you
working on it, although focusing on review of patches that still might
make it wouldn't hurt either.

- Andres


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
@ 2017-04-05 18:50         ` Peter Geoghegan <[email protected]>
  2017-04-07 04:28           ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 1 reply; 151+ messages in thread

From: Peter Geoghegan @ 2017-04-05 18:50 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Robert Haas <[email protected]>; Pavan Deolasee <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 5, 2017 at 11:27 AM, Andres Freund <[email protected]> wrote:
> I propose we move this patch to the next CF.

I agree. I think it's too late to be working out fine details around
TOAST like this. This is a patch that touches the storage format in a
fairly fundamental way.

The idea of turning WARM on or off reminds me a little bit of the way
it was at one time suggested that HOT not be used against catalog
tables, a position that Tom pushed against. I'm not saying that it's
necessarily a bad idea, but we should exhaust alternatives, and have a
clear rationale for it.

-- 
Peter Geoghegan


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-05 18:50         ` Re: Patch: Write Amplification Reduction Method (WARM) Peter Geoghegan <[email protected]>
@ 2017-04-07 04:28           ` Pavan Deolasee <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-07 04:28 UTC (permalink / raw)
  To: Peter Geoghegan <[email protected]>; +Cc: Andres Freund <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Thu, Apr 6, 2017 at 12:20 AM, Peter Geoghegan <[email protected]> wrote:

> On Wed, Apr 5, 2017 at 11:27 AM, Andres Freund <[email protected]> wrote:
> > I propose we move this patch to the next CF.
>
> I agree. I think it's too late to be working out fine details around
> TOAST like this. This is a patch that touches the storage format in a
> fairly fundamental way.
>
> The idea of turning WARM on or off reminds me a little bit of the way
> it was at one time suggested that HOT not be used against catalog
> tables, a position that Tom pushed against.


I agree. I am grateful that Tom put his put down and helped me find answers
to all hard problems, including catalog tables and create index
concurrently. So I was very clear in my mind from the very beginning that
WARM must support all these things too. Obviously it still doesn't support
everything like other index methods and expression indexes, but IMHO that's
a much smaller problem. Also, making sure that WARM works on system tables
helped me find any corner bugs which would have otherwise skipped via
regular regression testing.



> I'm not saying that it's
> necessarily a bad idea, but we should exhaust alternatives, and have a
> clear rationale for it.
>

One reason why it's probably a good idea is because we know WARM will not
effective for all use cases and it might actually cause performance
regression for some of them. Even worse and as Robert fears, it might cause
data loss issues. Though TBH I haven't yet seen any concrete example where
it breaks so badly that it causes data loss, but that may be because the
patch still hasn't received enough eye balls or outside tests. Having table
level option would allow us to incrementally improve things instead of
making the initial patch so large that reviewing it is a complete
nightmare. May be it's already a nightmare.

It's not as if HOT would not have caused regression for some specific use
cases. But I think the general benefit was so strong that we never invested
time in finding and tuning for those specific cases, thus avoided some more
complexity to the code. WARM's benefits are probably not the same as HOT or
our standards may have changed or we probably have resources to do much
more elaborate tests, which were missing 10 years back. But now that we are
aware of some regressions, the choice is between spending considerable
amount of time trying to handle every case vs doing it incrementally and
start delivering to majority of the users, yet keeping the patch at a
manageable level.

Even if we were to provide table level option, my preference would be keep
it ON by default.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
@ 2017-04-08 18:06         ` Pavan Deolasee <[email protected]>
  2017-04-10 23:19           ` Re: Patch: Write Amplification Reduction Method (WARM) Bruce Momjian <[email protected]>
  2017-04-10 23:34           ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  1 sibling, 3 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-08 18:06 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 5, 2017 at 11:57 PM, Andres Freund <[email protected]> wrote:

> On 2017-04-05 09:36:47 -0400, Robert Haas wrote:
> > By the way, the "Converting WARM chains back to HOT chains" section of
> > README.WARM seems to be out of date.  Any chance you could update that
> > to reflect the current state and thinking of the patch?
>
> I propose we move this patch to the next CF.  That shouldn't prevent you
> working on it, although focusing on review of patches that still might
> make it wouldn't hurt either.
>
>
Thank you all for the  reviews, feedback, tests, criticism. And apologies
for keep pushing it till the last minute even though it was clear to me
quite some time back the patch is not going to make it. But if I'd given
up, it would have never received whatever little attention it got. The only
thing that disappoints me is that the patch was held back on no strong
technical grounds -  at least none were clear to me. There were concerns
about on-disk changes etc, but most on-disk changes were known for 7 months
now. Reminds me of HOT development, when it would not receive adequate
feedback for quite many months, probably for very similar reasons - complex
patch, changes on-disk format, risky, even though performance gains were
quite substantial. I was much more hopeful this time because we have many
more experts now as compared to then, but we probably have equally more
amount of complex patches to review/commit.

I understand that we would like this patch to go in very early in the
development cycle. So as Alvaro mentioned elsewhere, we will continue to
work on it so that we can get it in as soon as v11 tree open. We shall soon
submit a revised version, with the list of critical things so that we can
discuss them here and get some useful feedback. I hope everyone understands
that the feature of this kind won't happen without on-disk format changes.
So to be able to address any concerns, we will need specific feedback and
workable suggestions, if any.

Finally, my apologies for not spending enough time reviewing other patches.
I know its critical, and I'll try to improve on that. Congratulations to
all whose work got accepted and many thanks to all reviewers/committers/CF
managers. I know how difficult and thankless that work is.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-10 23:19           ` Bruce Momjian <[email protected]>
  2 siblings, 0 replies; 151+ messages in thread

From: Bruce Momjian @ 2017-04-10 23:19 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Andres Freund <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Sat, Apr  8, 2017 at 11:36:13PM +0530, Pavan Deolasee wrote:
> Thank you all for the  reviews, feedback, tests, criticism. And apologies for
> keep pushing it till the last minute even though it was clear to me quite some
> time back the patch is not going to make it. But if I'd given up, it would have
> never received whatever little attention it got. The only thing that
> disappoints me is that the patch was held back on no strong technical grounds -
>  at least none were clear to me. There were concerns about on-disk changes etc,
> but most on-disk changes were known for 7 months now. Reminds me of HOT
> development, when it would not receive adequate feedback for quite many months,
> probably for very similar reasons - complex patch, changes on-disk format,
> risky, even though performance gains were quite substantial. I was much more
> hopeful this time because we have many more experts now as compared to then,
> but we probably have equally more amount of complex patches to review/commit.

I am sad to see WARM didn't make it into Postgres 10, but I agree
deferment was the right decision, as painful as that is.  We now have
something to look forward to in Postgres 11.  :-)

-- 
  Bruce Momjian  <[email protected]>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-10 23:34           ` Andres Freund <[email protected]>
  2017-04-12 00:50             ` Re: Patch: Write Amplification Reduction Method (WARM) Bruce Momjian <[email protected]>
  2 siblings, 1 reply; 151+ messages in thread

From: Andres Freund @ 2017-04-10 23:34 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

Hi,


On 2017-04-08 23:36:13 +0530, Pavan Deolasee wrote:
> On Wed, Apr 5, 2017 at 11:57 PM, Andres Freund <[email protected]> wrote:
> 
> > On 2017-04-05 09:36:47 -0400, Robert Haas wrote:
> > > By the way, the "Converting WARM chains back to HOT chains" section of
> > > README.WARM seems to be out of date.  Any chance you could update that
> > > to reflect the current state and thinking of the patch?
> >
> > I propose we move this patch to the next CF.  That shouldn't prevent you
> > working on it, although focusing on review of patches that still might
> > make it wouldn't hurt either.
> >
> >
> Thank you all for the  reviews, feedback, tests, criticism. And apologies
> for keep pushing it till the last minute even though it was clear to me
> quite some time back the patch is not going to make it.

What confuses me about that position is that people were advocating to
actually commit till literally hours before the CF closed.


> But if I'd given
> up, it would have never received whatever little attention it got. The only
> thing that disappoints me is that the patch was held back on no strong
> technical grounds -  at least none were clear to me. There were concerns
> about on-disk changes etc, but most on-disk changes were known for 7 months
> now. Reminds me of HOT development, when it would not receive adequate
> feedback for quite many months, probably for very similar reasons - complex
> patch, changes on-disk format, risky, even though performance gains were
> quite substantial. I was much more hopeful this time because we have many
> more experts now as compared to then, but we probably have equally more
> amount of complex patches to review/commit.

I don't think it's realistic to expect isolated in-depth review of
on-disk changes, when the rest of the patch isn't in a close-to-ready
shape. The likelihood that further work on the patch invalidates such
in-depth review is significant. It's not like only minor details changed
in the last few months.

I do agree that it's hard to get qualified reviewers on bigger patches.
But I think part of the reaction to that has to be active work on that
front: If your patch needs reviews by committers or other topical
experts, you need to explicitly reach out.  There's a lot of active
threads, and nobody has time to follow all of them in sufficient detail
to know that certain core parts of an actively developed patch are ready
for review.  Offer tit-for-tat reviews.  Announce that your patch is
ready, that you're only waiting for review.  Post a summary of open
questions...


> Finally, my apologies for not spending enough time reviewing other
> patches.  I know its critical, and I'll try to improve on that.

I do find it a more than a bit ironic to lament early lack of attention
to your patch, while also being aware of not having done much review.
This can only scale if everyone reviews each others patches, not if
there's a few individuals that have to review everyones patches.

Greetings,

Andres Freund


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-10 23:34           ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
@ 2017-04-12 00:50             ` Bruce Momjian <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Bruce Momjian @ 2017-04-12 00:50 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Pavan Deolasee <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Mon, Apr 10, 2017 at 04:34:50PM -0700, Andres Freund wrote:
> Hi,
> 
> 
> On 2017-04-08 23:36:13 +0530, Pavan Deolasee wrote:
> > On Wed, Apr 5, 2017 at 11:57 PM, Andres Freund <[email protected]> wrote:
> > 
> > > On 2017-04-05 09:36:47 -0400, Robert Haas wrote:
> > > > By the way, the "Converting WARM chains back to HOT chains" section of
> > > > README.WARM seems to be out of date.  Any chance you could update that
> > > > to reflect the current state and thinking of the patch?
> > >
> > > I propose we move this patch to the next CF.  That shouldn't prevent you
> > > working on it, although focusing on review of patches that still might
> > > make it wouldn't hurt either.
> > >
> > >
> > Thank you all for the  reviews, feedback, tests, criticism. And apologies
> > for keep pushing it till the last minute even though it was clear to me
> > quite some time back the patch is not going to make it.
> 
> What confuses me about that position is that people were advocating to
> actually commit till literally hours before the CF closed.

Yes, I was surprised by that too and have privately emailed people on
this topic.

-- 
  Bruce Momjian  <[email protected]>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-11 13:40           ` Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2 siblings, 1 reply; 151+ messages in thread

From: Robert Haas @ 2017-04-11 13:40 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Andres Freund <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Sat, Apr 8, 2017 at 2:06 PM, Pavan Deolasee <[email protected]> wrote:
> Thank you all for the  reviews, feedback, tests, criticism. And apologies
> for keep pushing it till the last minute even though it was clear to me
> quite some time back the patch is not going to make it. But if I'd given up,
> it would have never received whatever little attention it got. The only
> thing that disappoints me is that the patch was held back on no strong
> technical grounds -  at least none were clear to me. There were concerns
> about on-disk changes etc, but most on-disk changes were known for 7 months
> now. Reminds me of HOT development, when it would not receive adequate
> feedback for quite many months, probably for very similar reasons - complex
> patch, changes on-disk format, risky, even though performance gains were
> quite substantial. I was much more hopeful this time because we have many
> more experts now as compared to then, but we probably have equally more
> amount of complex patches to review/commit.

Yes, and as Andres says, you don't help with those, and then you're
upset when your own patch doesn't get attention.  I think there are
two ways that this patch could have gotten the detailed and in-depth
review which it needs.  First, I would have been more than happy to
spend time on WARM in exchange for a comparable amount of your time
spent on parallel bitmap heap scan, or partition-wise join, or
partitioning, but that time was not forthcoming.  Second, there are
numerous senior reviewers at 2ndQuadrant who could have put time time
into this patch and didn't.  Yes, Alvaro did some review, but it was
not in a huge degree of depth and didn't arrive until quite late,
unless there was more to it than what was posted on the mailing list
which, as a reminder, is the place where review is supposed to take
place.

If people senior reviewers with whom you share an employer don't have
time to review your patch, and you aren't willing to trade review time
on other patches for a comparable amount of attention on your own,
then it shouldn't surprise you when people object to it being
committed.

If there is an intention to commit this patch soon after v11
development opens, then signs of serious in-depth review, and
responses to criticisms thus-far proffered, really ought to be in
evidence will in advance of that date.  It's slightly better to commit
an inadequately-reviewed patch at the beginning of the cycle than at
the end, but what's even better is thorough review, which I maintain
this patch hasn't really had yet.  Amit and others who have started to
dig into this patch a little bit found real problems pretty quickly
when they started digging.  Those problems should be addressed, and
review should continue (from whatever source) until no more problems
can be found.  Everyone here understands (if they've been paying
attention) that this patch has large benefits in sympathetic cases,
and everyone wants those benefits.  What nobody wants (I assume) is
regressions is unsympathetic cases, or data corruption.  The patch may
or may not have any data-corrupting bugs, but regressions have been
found and not addressed.  Yet, there's still talk of committing this
with as much haste as possible.  I do not think that is a responsible
way to do development.

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


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-11 17:20             ` Pavan Deolasee <[email protected]>
  2017-04-12 03:53               ` Re: Patch: Write Amplification Reduction Method (WARM) Amit Kapila <[email protected]>
  2017-04-12 17:12               ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  0 siblings, 2 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-11 17:20 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Andres Freund <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Tue, Apr 11, 2017 at 7:10 PM, Robert Haas <[email protected]> wrote:

>
>
> Yes, and as Andres says, you don't help with those, and then you're
> upset when your own patch doesn't get attention.


I am not upset, I was obviously a bit disappointed which I think is a very
natural emotion after spending weeks on it. I am not blaming any one
individual (excluding myself) for that and neither the community at large
for the outcome. And I've moved on. I know everyone is busy getting the
release ready and I see no point discussing this endlessly. We have enough
on our plates for next few weeks.


>
>   Amit and others who have started to
> dig into this patch a little bit found real problems pretty quickly
> when they started digging.


And I fixed them as quickly as humanly possible.


>   Those problems should be addressed, and
> review should continue (from whatever source) until no more problems
> can be found.


Absolutely.


>  The patch may
> or may not have any data-corrupting bugs, but regressions have been
> found and not addressed.


I don't know why you say that regressions are not addressed. Here are a few
things I did to address the regressions/reviews/concerns, apart from fixing
all the bugs discovered, but please let me know if there are things I've
not addressed.

1. Improved the interesting attrs patch that Alvaro wrote to address the
regression discovered in fetching more heap attributes. The patch that got
committed in fact improved certain synthetic workloads over then master.
2. Based on Petr and your feedback, disabled WARM on toasted attributes to
reduce overhead of fetching/decompressing the attributes.
3. Added code to avoid doing second index scan when the index does not
contain any WARM pointers. This should address the situation Amit brought
up where only one of the indexes receive WARM inserts.
4. Added code to kill wrong index pointers to do online cleanup.
5. Added code to set a CLEAR pointer to a WARM pointer when we know that
the entire chain is WARM. This should address the workload Dilip ran and
found regression (I don't think he got chance to confirm that)
6. Enhanced stats collector to collect information about candidate WARM
chains and added mechanism to control WARM cleanup at the heap as well as
index level, based on configurable parameters. This gives user better
control over the additional work that is required for WARM cleanup.
7. Added table level option to disable WARM if nothing else works.
8. Added mechanism to disable WARM when more than 50% indexes are being
updated. I ran some benchmarks with different percentage of indexes getting
updated and thought this is a good threshold.

I may have missed something, but there is no intention to ignore known
regressions/reviews. Of course, I don't think that every regression will be
solvable, like if you run a CPU-bound workload, setting it up in a way such
that you repeatedly exercise the area where WARM is doing additional work,
without providing any benefit, may be you can still find regression. I am
willing to fix them as long as they are fixable and we are comfortable with
the additional code complexity. IMHO certain trade-offs are good, but I
understand that not everybody will agree with my views and that's ok.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-12 03:53               ` Amit Kapila <[email protected]>
  2017-04-12 04:23                 ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 1 reply; 151+ messages in thread

From: Amit Kapila @ 2017-04-12 03:53 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Tue, Apr 11, 2017 at 10:50 PM, Pavan Deolasee
<[email protected]> wrote:
>
>
> On Tue, Apr 11, 2017 at 7:10 PM, Robert Haas <[email protected]> wrote:
>>
>>
>>
>> Yes, and as Andres says, you don't help with those, and then you're
>> upset when your own patch doesn't get attention.
>
>
> I am not upset, I was obviously a bit disappointed which I think is a very
> natural emotion after spending weeks on it. I am not blaming any one
> individual (excluding myself) for that and neither the community at large
> for the outcome. And I've moved on. I know everyone is busy getting the
> release ready and I see no point discussing this endlessly. We have enough
> on our plates for next few weeks.
>
>>
>>
>>   Amit and others who have started to
>> dig into this patch a little bit found real problems pretty quickly
>> when they started digging.
>
>
> And I fixed them as quickly as humanly possible.
>

Yes, you have responded to them quickly, but I didn't get a chance to
re-verify all of those.  However, I think the main point Robert wants
to say is that somebody needs to dig the complete patch to see if
there is any kind of problems with it.

>>
>>   Those problems should be addressed, and
>> review should continue (from whatever source) until no more problems
>> can be found.
>
>
> Absolutely.
>
>>
>>  The patch may
>> or may not have any data-corrupting bugs, but regressions have been
>> found and not addressed.
>
>
> I don't know why you say that regressions are not addressed. Here are a few
> things I did to address the regressions/reviews/concerns, apart from fixing
> all the bugs discovered, but please let me know if there are things I've not
> addressed.
>
> 1. Improved the interesting attrs patch that Alvaro wrote to address the
> regression discovered in fetching more heap attributes. The patch that got
> committed in fact improved certain synthetic workloads over then master.
> 2. Based on Petr and your feedback, disabled WARM on toasted attributes to
> reduce overhead of fetching/decompressing the attributes.
> 3. Added code to avoid doing second index scan when the index does not
> contain any WARM pointers. This should address the situation Amit brought up
> where only one of the indexes receive WARM inserts.
> 4. Added code to kill wrong index pointers to do online cleanup.
> 5. Added code to set a CLEAR pointer to a WARM pointer when we know that the
> entire chain is WARM. This should address the workload Dilip ran and found
> regression (I don't think he got chance to confirm that)
>

Have you by any chance tried to reproduce it at your end?


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


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-12 03:53               ` Re: Patch: Write Amplification Reduction Method (WARM) Amit Kapila <[email protected]>
@ 2017-04-12 04:23                 ` Pavan Deolasee <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-12 04:23 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 12, 2017 at 9:23 AM, Amit Kapila <[email protected]>
wrote:

> On Tue, Apr 11, 2017 at 10:50 PM, Pavan Deolasee
>
> >
> > And I fixed them as quickly as humanly possible.
> >
>
> Yes, you have responded to them quickly, but I didn't get a chance to
> re-verify all of those.  However, I think the main point Robert wants
> to say is that somebody needs to dig the complete patch to see if
> there is any kind of problems with it.
>

There are no two views about that. I don't even claim that more problems
won't be found during in-depth review. I was only responding to his view
that I did not do much to address the regressions reported during the
review/tests.


>
> > 5. Added code to set a CLEAR pointer to a WARM pointer when we know that
> the
> > entire chain is WARM. This should address the workload Dilip ran and
> found
> > regression (I don't think he got chance to confirm that)
> >
>
> Have you by any chance tried to reproduce it at your end?


I did reproduce and verified that the new technique helps the case [1] (see
last para). I did not go extra length to check if there are more cases
which can still cause regression, like recheck is applied only once  to
each tuple (so the new technique does not yield any benefit) and whether
that still causes regression and by how much. However I ran pure pgbench
workload (only HOT updates) with smallish scale factor so that everything
fits in memory and did not find any regression.

Having said that, it's my view that others need not agree to it, that we
need to distinguish between CPU and IO load since WARM is designed to
address IO problems and not so much CPU problems. We also need to see
things in totality and probably measure updates and selects both if we are
going to WARM update all tuples once and read them once. That doesn't mean
we shouldn't perform more tests and I am more than willing to fix if we
find regression in even a remotely real-world use case.

Thanks,
Pavan

[1]
https://www.postgresql.org/message-id/CABOikdOTstHK2y0rDk%2BY3Wx9HRe%2BbZtj3zuYGU%3DVngneiHo5KQ%40ma...

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-12 17:12               ` Robert Haas <[email protected]>
  2017-04-12 20:34                 ` Re: Patch: Write Amplification Reduction Method (WARM) Peter Geoghegan <[email protected]>
  2017-04-13 05:42                 ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 2 replies; 151+ messages in thread

From: Robert Haas @ 2017-04-12 17:12 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Andres Freund <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Tue, Apr 11, 2017 at 1:20 PM, Pavan Deolasee
<[email protected]> wrote:
> I don't know why you say that regressions are not addressed. Here are a few
> things I did to address the regressions/reviews/concerns, apart from fixing
> all the bugs discovered, but please let me know if there are things I've not
> addressed.

I'm making statements based on my perception of the discussion on the
thread.  Perhaps you did some work which you either didn't mention or
I missed you mentioning it, but it sure didn't feel like all of the
things reported got addressed.

> 1. Improved the interesting attrs patch that Alvaro wrote to address the
> regression discovered in fetching more heap attributes. The patch that got
> committed in fact improved certain synthetic workloads over then master.

Yep, though it was not clear that all of the regressing cases were
actually addressed, at least not to me.

> 2. Based on Petr and your feedback, disabled WARM on toasted attributes to
> reduce overhead of fetching/decompressing the attributes.

But that's not necessarily the right fix, as per
http://postgr.es/m/CA+TgmoYUfxy1LseDzsw8uuuLUJHH0r8NCD-Up-HZMC1fYDPH3Q@mail.gmail.com
and subsequent discussion.  It's not clear to me from that discussion
that we've got to a place where the method used to identify whether a
WARM update happened during a scan is exactly identical to the method
used to decide whether to perform one in the first place.

> 3. Added code to avoid doing second index scan when the index does not
> contain any WARM pointers. This should address the situation Amit brought up
> where only one of the indexes receive WARM inserts
> 4. Added code to kill wrong index pointers to do online cleanup.

Good changes.

> 5. Added code to set a CLEAR pointer to a WARM pointer when we know that the
> entire chain is WARM. This should address the workload Dilip ran and found
> regression (I don't think he got chance to confirm that)

Which is clearly a thing that should happen before commit, and really,
you ought to be leading the effort to confirm that, not him.  It's
good for him to verify that your fix worked, but you should test it
first.

> 6. Enhanced stats collector to collect information about candidate WARM
> chains and added mechanism to control WARM cleanup at the heap as well as
> index level, based on configurable parameters. This gives user better
> control over the additional work that is required for WARM cleanup.

I haven't seen previous discussion of this; therefore I doubt whether
we have agreement on these parameters.

> 7. Added table level option to disable WARM if nothing else works.

-1 from me.

> 8. Added mechanism to disable WARM when more than 50% indexes are being
> updated. I ran some benchmarks with different percentage of indexes getting
> updated and thought this is a good threshold.

+1 from me.

> I may have missed something, but there is no intention to ignore known
> regressions/reviews. Of course, I don't think that every regression will be
> solvable, like if you run a CPU-bound workload, setting it up in a way such
> that you repeatedly exercise the area where WARM is doing additional work,
> without providing any benefit, may be you can still find regression. I am
> willing to fix them as long as they are fixable and we are comfortable with
> the additional code complexity. IMHO certain trade-offs are good, but I
> understand that not everybody will agree with my views and that's ok.

The point here is that we can't make intelligent decisions about
whether to commit this feature unless we know which situations get
better and which get worse and by how much.  I don't accept as a
general principle the idea that CPU-bound workloads don't matter.
Obviously, I/O-bound workloads matter too, but we can't throw
CPU-bound workloads under the bus.  Now, avoiding index bloat does
also save CPU, so it is easy to imagine that WARM could come out ahead
even if each update consumes slightly more CPU when actually updating,
so we might not actually regress.  If we do, I guess I'd want to know
why.

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


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-12 17:12               ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-12 20:34                 ` Peter Geoghegan <[email protected]>
  2017-04-13 05:14                   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 1 reply; 151+ messages in thread

From: Peter Geoghegan @ 2017-04-12 20:34 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Pavan Deolasee <[email protected]>; Andres Freund <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers; [email protected]

On Wed, Apr 12, 2017 at 10:12 AM, Robert Haas <[email protected]> wrote:
>> I may have missed something, but there is no intention to ignore known
>> regressions/reviews. Of course, I don't think that every regression will be
>> solvable, like if you run a CPU-bound workload, setting it up in a way such
>> that you repeatedly exercise the area where WARM is doing additional work,
>> without providing any benefit, may be you can still find regression. I am
>> willing to fix them as long as they are fixable and we are comfortable with
>> the additional code complexity. IMHO certain trade-offs are good, but I
>> understand that not everybody will agree with my views and that's ok.
>
> The point here is that we can't make intelligent decisions about
> whether to commit this feature unless we know which situations get
> better and which get worse and by how much.  I don't accept as a
> general principle the idea that CPU-bound workloads don't matter.
> Obviously, I/O-bound workloads matter too, but we can't throw
> CPU-bound workloads under the bus.  Now, avoiding index bloat does
> also save CPU, so it is easy to imagine that WARM could come out ahead
> even if each update consumes slightly more CPU when actually updating,
> so we might not actually regress.  If we do, I guess I'd want to know
> why.

I myself wonder if this CPU overhead is at all related to LP_DEAD
recycling during page splits. I have my suspicions that the recyling
has some relationship to locality, which leads me to want to
investigate how Claudio Freire's patch to consistently treat heap TID
as part of the B-Tree sort order could help, both in general, and for
WARM.

Bear in mind that the recycling has to happen with an exclusive buffer
lock held on a leaf page, which could hold up rather a lot of scans
that need to visit the same value even if it's on some other,
relatively removed leaf page.

This is just a theory.

-- 
Peter Geoghegan

VMware vCenter Server
https://www.vmware.com/


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-12 17:12               ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-12 20:34                 ` Re: Patch: Write Amplification Reduction Method (WARM) Peter Geoghegan <[email protected]>
@ 2017-04-13 05:14                   ` Pavan Deolasee <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-13 05:14 UTC (permalink / raw)
  To: Peter Geoghegan <[email protected]>; +Cc: Robert Haas <[email protected]>; Andres Freund <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers; Claudio Freire <[email protected]>

On Thu, Apr 13, 2017 at 2:04 AM, Peter Geoghegan <[email protected]> wrote:

> On Wed, Apr 12, 2017 at 10:12 AM, Robert Haas <[email protected]>
> wrote:
> >> I may have missed something, but there is no intention to ignore known
> >> regressions/reviews. Of course, I don't think that every regression
> will be
> >> solvable, like if you run a CPU-bound workload, setting it up in a way
> such
> >> that you repeatedly exercise the area where WARM is doing additional
> work,
> >> without providing any benefit, may be you can still find regression. I
> am
> >> willing to fix them as long as they are fixable and we are comfortable
> with
> >> the additional code complexity. IMHO certain trade-offs are good, but I
> >> understand that not everybody will agree with my views and that's ok.
> >
> > The point here is that we can't make intelligent decisions about
> > whether to commit this feature unless we know which situations get
> > better and which get worse and by how much.  I don't accept as a
> > general principle the idea that CPU-bound workloads don't matter.
> > Obviously, I/O-bound workloads matter too, but we can't throw
> > CPU-bound workloads under the bus.  Now, avoiding index bloat does
> > also save CPU, so it is easy to imagine that WARM could come out ahead
> > even if each update consumes slightly more CPU when actually updating,
> > so we might not actually regress.  If we do, I guess I'd want to know
> > why.
>
> I myself wonder if this CPU overhead is at all related to LP_DEAD
> recycling during page splits.


With the respect to the tests that myself, Dilip and others did for WARM, I
think we were kinda exercising the worst case scenario. Like in one case,
we created a table with 40% fill factor,  created an index with a large
text column, WARM updated all rows in the table, turned off autovacuum so
that chain conversion does not take place, and then repeatedly run select
query on those rows using the index which did not receive WARM insert.

IOW we were only measuring the overhead of doing recheck by constructing an
index tuple from the heap tuple and then comparing it against the existing
index tuple. And we did find regression, which is not entirely surprising
because obviously that code path does extra work when it needs to do
recheck. And we're only measuring that overhead without taking into account
the benefits of WARM to the system in general. I think counter-argument to
that is, such workload may exists somewhere which might be regressed.

I have my suspicions that the recyling
> has some relationship to locality, which leads me to want to
> investigate how Claudio Freire's patch to consistently treat heap TID
> as part of the B-Tree sort order could help, both in general, and for
> WARM.
>

It could be, especially if we re-redesign recheck solely based on the index
pointer state and the heap tuple state. That could be more performant for
selects and could also be more robust, but will require index inserts to
get hold of the old index pointer (based on root TID), compare it against
the new index tuple and either skip the insert (if everything matches) or
set a PREWARM flag on the old pointer, and insert the new tuple with
POSTWARM flag.

Searching for old index pointer will be non-starter for non-unique indexes,
unless they are also sorted by TID, something that Claudio's patch does.
What I am not sure is whether the patch on its own will stand the
performance implications because it increases the index tuple width (and
probably index maintenance cost too).

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:27       ` Re: Patch: Write Amplification Reduction Method (WARM) Andres Freund <[email protected]>
  2017-04-08 18:06         ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-11 13:40           ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-11 17:20             ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-12 17:12               ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-13 05:42                 ` Pavan Deolasee <[email protected]>
  1 sibling, 0 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-13 05:42 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Andres Freund <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 12, 2017 at 10:42 PM, Robert Haas <[email protected]> wrote:

> On Tue, Apr 11, 2017 at 1:20 PM, Pavan Deolasee
>


> > 5. Added code to set a CLEAR pointer to a WARM pointer when we know that
> the
> > entire chain is WARM. This should address the workload Dilip ran and
> found
> > regression (I don't think he got chance to confirm that)
>
> Which is clearly a thing that should happen before commit, and really,
> you ought to be leading the effort to confirm that, not him.  It's
> good for him to verify that your fix worked, but you should test it
> first.
>

Not sure why you think I did not do the tests. I did and reported that it
helps reduce the regression. Last para here:  https://www.postgresql.
org/message-id/CABOikdOTstHK2y0rDk%2BY3Wx9HRe%2BbZtj3zuYGU%
3DVngneiHo5KQ%40mail.gmail.com

I understand it might have got lost in the conversation and I possibly did
a poor job of explaining it. From my perspective, I did not want say that
everything is hunky-dory based on my own tests because 1. I probably do not
have access to the same kind of machine Dilip has and 2. It's better to get
it confirmed by someone who initially reported it. Again, I fully respect
that he would be busy with other things and I do not expect him or anyone
else to test/review my patch on priority. The only point I am trying to
make is that I did my own tests and made sure that it helps.

(Having said that, I am not sure if changing pointer state from CLEAR to
WARM is indeed a good change. Having thought more about it and after
looking at the page-split code, I now think that this might just confuse
the WARM cleanup code and make algorithm that much harder to prove)


> > 6. Enhanced stats collector to collect information about candidate WARM
> > chains and added mechanism to control WARM cleanup at the heap as well as
> > index level, based on configurable parameters. This gives user better
> > control over the additional work that is required for WARM cleanup.
>
> I haven't seen previous discussion of this; therefore I doubt whether
> we have agreement on these parameters.
>

Sure. I will bring these up in a more structured manner for everyone to
comment.


>
> > 7. Added table level option to disable WARM if nothing else works.
>
> -1 from me.
>

Ok. It's kinda last resort for me too. But at some point, we might want to
make that call if we find an important use case that regresses because of
WARM and we see no way to fix that or at least not without a whole lot of
complexity.


>
>
> > I may have missed something, but there is no intention to ignore known
> > regressions/reviews. Of course, I don't think that every regression will
> be
> > solvable, like if you run a CPU-bound workload, setting it up in a way
> such
> > that you repeatedly exercise the area where WARM is doing additional
> work,
> > without providing any benefit, may be you can still find regression. I am
> > willing to fix them as long as they are fixable and we are comfortable
> with
> > the additional code complexity. IMHO certain trade-offs are good, but I
> > understand that not everybody will agree with my views and that's ok.
>
> The point here is that we can't make intelligent decisions about
> whether to commit this feature unless we know which situations get
> better and which get worse and by how much.


Sure.


>   I don't accept as a
> general principle the idea that CPU-bound workloads don't matter.
> Obviously, I/O-bound workloads matter too, but we can't throw
> CPU-bound workloads under the bus.


Yeah, definitely not suggesting that.


>   Now, avoiding index bloat does
> also save CPU, so it is easy to imagine that WARM could come out ahead
> even if each update consumes slightly more CPU when actually updating,
> so we might not actually regress.  If we do, I guess I'd want to know
> why.


Well the kind of tests we did to look for regression were worst case
scenarios. For example, in the test where we found 10-15% regression, we
used a wide index (so recheck cost is high), WARM updated all rows,
disabled auto-vacuum (so no chain conversion) and then repeatedly selected
the rows from the index, thus incurring recheck overhead and in fact,
measuring only that.

When I measured WARM on tables with small scale factor so that everything
fits in memory, I found a modest 20% improvement in tps. So, you're right,
WARM might also help in-memory workloads. But that will show up only if we
measure UPDATEs and SELECTs both. If we measure only SELECTs and that too
in a state where we are paying all price for having done a WARM update,
obviously we will only see regression, if any. Not saying we should ignore
that. We should in fact measure all possible loads, and try to fix as many
as we can, especially if they resemble to a real-world use case,  but there
will be a trade-off to make. So I highly appreciate Amit and Dilip's help
with coming up additional tests. At least it gives us opportunity to think
how to fix them, even if we can't fix all of them.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-05 18:32       ` Pavan Deolasee <[email protected]>
  2017-04-05 19:36         ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-14 15:51         ` Re: Patch: Write Amplification Reduction Method (WARM) Jaime Casanova <[email protected]>
  1 sibling, 2 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-05 18:32 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 5, 2017 at 7:06 PM, Robert Haas <[email protected]> wrote:

> On Tue, Apr 4, 2017 at 11:43 PM, Pavan Deolasee
> <[email protected]> wrote:
> > Well, better than causing a deadlock ;-)
>
> Yep.
>
> > Lets see if we want to go down the path of blocking WARM when tuples have
> > toasted attributes. I submitted a patch yesterday, but having slept over
> it,
> > I think I made mistakes there. It might not be enough to look at the
> caller
> > supplied new tuple because that may not have any toasted values, but the
> > final tuple that gets written to the heap may be toasted.
>
> Yes, you have to make whatever decision you're going to make here
> after any toast-ing has been done.
>

I am worried that might add more work in that code path since we then have
to fetch attributes for the new tuple as well. May be a good compromise
would be to still only check on the user supplied new tuple, but be
prepared to handle toasted values during recheck. The attached version does
that.


>
> Well, I think that there's some danger of whittling down this
> optimization to the point where it still incurs most of the costs --
> in bit-space if not in CPU cycles -- but no longer yields much of the
> benefit.  Even though the speed-up might still be substantial in the
> cases where the optimization kicks in, if a substantial number of
> users doing things that are basically pretty normal sometimes fail to
> get the optimization, this isn't going to be very exciting outside of
> synthetic benchmarks.
>

I agree. Blocking WARM off for too many cases won't serve the purpose.


>
> Backing up a little bit, it seems like the root of the issue here is
> that, at a certain point in what was once a HOT chain, you make a WARM
> update, and you make a decision about which indexes to update at that
> point.  Now, later on, when you traverse that chain, you need to be
> able to figure what decide you made before; otherwise, you might make
> a bad decision about whether an index pointer applies to a particular
> tuple.  If the index tuple is WARM, then the answer is "yes" if the
> heap tuple is also WARM, and "no" if the heap tuple is CLEAR (which is
> an odd antonym to WARM, but leave that aside).  If the index tuple is
> CLEAR, then the answer is "yes" if the heap tuple is also CLEAR, and
> "maybe" if the heap tuple is WARM.
>

That's fairly accurate description of the problem.


>
> The first idea I had for an actual solution to this problem was to
> make the decision as to whether to insert new index entries based on
> whether the indexed attributes in the final tuple (post-TOAST) are
> byte-for-byte identical with the original tuple.  If somebody injects
> a new compression algorithm into the system, or just changes the
> storage parameters on a column, or we re-insert an identical value
> into the TOAST table when we could have reused the old TOAST pointer,
> then you might have some potentially-WARM updates that end up being
> done as regular updates, but that's OK.  When you are walking the
> chain, you will KNOW whether you inserted new index entries or not,
> because you can do the exact same comparison that was done before and
> be sure of getting the same answer.  But that's actually not really a
> solution, because it doesn't work if all of the CLEAR tuples are gone
> -- all you have is the index tuple and the new heap tuple; there's no
> old heap tuple with which to compare.
>

Right. The old/new tuples may get HOT pruned and hence we cannot rely on
any algorithm which assumes that we can compare old and new tuples after
the update is committed/aborted.


>
> The only other idea that I have for a really clean solution here is to
> support this only for index types that are amcanreturn, and actually
> compare the value stored in the index tuple with the one stored in the
> heap tuple, ensuring that new index tuples are inserted whenever they
> don't match and then using the exact same test to determine the
> applicability of a given index pointer to a given heap tuple.


Just so that I understand, are you suggesting that while inserting WARM
index pointers, we check if the new index tuple will look exactly the same
as the old index tuple and not insert a duplicate pointer at all? I
considered that, but it will require us to do an index lookup during WARM
index insert and for non-unique keys, that may or may not be exactly cheap.
Or we need something like what Claudio wrote to sort all index entries by
heap TIDs. If we do that, then the recheck can be done just based on the
index and heap flags (because we can then turn the old index pointer into a
CLEAR pointer. Index pointer is set to COMMON during initial insert).

The other way is to pass old tuple values along with the new tuple values
to amwarminsert, build index tuples and then do a comparison. For duplicate
index tuples, skip WARM inserts.


>
> By the way, the "Converting WARM chains back to HOT chains" section of
> README.WARM seems to be out of date.  Any chance you could update that
> to reflect the current state and thinking of the patch?
>
>
Ok. I've extensively updated the README to match the current state of
affairs. Updated patch set attached. I've also added mechanism to deal with
known-dead pointers during regular index scans. We can derive some
knowledge from index/heap states and recheck results. One additional thing
I did which should help Dilip's test case is that we use the index/heap
state to decide whether a recheck is necessary or not. And when we see a
CLEAR pointer to all-WARM tuples, we set the pointer WARM and thus avoid
repeated recheck for the same tuple. My own tests show that the regression
should go away with this version, but I am not suggesting that we can't
come up with some other workload where we still see regression.

I also realised that altering table-level enable_warm reloption would
require AccessExclusiveLock. So included that change too.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [application/octet-stream] 0002-Free-3-bits-in-ip_posid-field-of-the-ItemPointer_v26.patch (4.2K, ../../CABOikdOTstHK2y0rDk+Y3Wx9HRe+bZtj3zuYGU=VngneiHo5KQ@mail.gmail.com/3-0002-Free-3-bits-in-ip_posid-field-of-the-ItemPointer_v26.patch)
  download | inline diff:
From 046a14badc3f86b1d3a2791db327a61ba51a47e9 Mon Sep 17 00:00:00 2001
From: Pavan Deolasee <[email protected]>
Date: Wed, 29 Mar 2017 10:44:01 +0530
Subject: [PATCH 2/4] Free 3-bits in ip_posid field of the ItemPointerData.

We can use those for storing some other information. Right now only index
methods will use those to store WARM/CLEAR property of an index pointer.
---
 src/include/access/ginblock.h     |  3 ++-
 src/include/access/htup_details.h |  2 +-
 src/include/storage/itemptr.h     | 30 +++++++++++++++++++++++++++---
 src/include/storage/off.h         | 11 ++++++++++-
 4 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/src/include/access/ginblock.h b/src/include/access/ginblock.h
index 438912c..316ab65 100644
--- a/src/include/access/ginblock.h
+++ b/src/include/access/ginblock.h
@@ -135,7 +135,8 @@ typedef struct GinMetaPageData
 	(ItemPointerGetBlockNumberNoCheck(pointer))
 
 #define GinItemPointerGetOffsetNumber(pointer) \
-	(ItemPointerGetOffsetNumberNoCheck(pointer))
+	(ItemPointerGetOffsetNumberNoCheck(pointer) | \
+	 (ItemPointerGetFlags(pointer) << OffsetNumberBits))
 
 #define GinItemPointerSetBlockNumber(pointer, blkno) \
 	(ItemPointerSetBlockNumber((pointer), (blkno)))
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 24433c7..4d614b7 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -288,7 +288,7 @@ struct HeapTupleHeaderData
  * than MaxOffsetNumber, so that it can be distinguished from a valid
  * offset number in a regular item pointer.
  */
-#define SpecTokenOffsetNumber		0xfffe
+#define SpecTokenOffsetNumber		OffsetNumberPrev(OffsetNumberMask)
 
 /*
  * HeapTupleHeader accessor macros
diff --git a/src/include/storage/itemptr.h b/src/include/storage/itemptr.h
index c21d2ad..74eed4e 100644
--- a/src/include/storage/itemptr.h
+++ b/src/include/storage/itemptr.h
@@ -57,7 +57,7 @@ typedef ItemPointerData *ItemPointer;
  *		True iff the disk item pointer is not NULL.
  */
 #define ItemPointerIsValid(pointer) \
-	((bool) (PointerIsValid(pointer) && ((pointer)->ip_posid != 0)))
+	((bool) (PointerIsValid(pointer) && (((pointer)->ip_posid & OffsetNumberMask) != 0)))
 
 /*
  * ItemPointerGetBlockNumberNoCheck
@@ -84,7 +84,7 @@ typedef ItemPointerData *ItemPointer;
  */
 #define ItemPointerGetOffsetNumberNoCheck(pointer) \
 ( \
-	(pointer)->ip_posid \
+	((pointer)->ip_posid & OffsetNumberMask) \
 )
 
 /*
@@ -98,6 +98,30 @@ typedef ItemPointerData *ItemPointer;
 )
 
 /*
+ * Get the flags stored in high order bits in the OffsetNumber.
+ */
+#define ItemPointerGetFlags(pointer) \
+( \
+	((pointer)->ip_posid & ~OffsetNumberMask) >> OffsetNumberBits \
+)
+
+/*
+ * Set the flag bits. We first left-shift since flags are defined starting 0x01
+ */
+#define ItemPointerSetFlags(pointer, flags) \
+( \
+	((pointer)->ip_posid |= ((flags) << OffsetNumberBits)) \
+)
+
+/*
+ * Clear all flags.
+ */
+#define ItemPointerClearFlags(pointer) \
+( \
+	((pointer)->ip_posid &= OffsetNumberMask) \
+)
+
+/*
  * ItemPointerSet
  *		Sets a disk item pointer to the specified block and offset.
  */
@@ -105,7 +129,7 @@ typedef ItemPointerData *ItemPointer;
 ( \
 	AssertMacro(PointerIsValid(pointer)), \
 	BlockIdSet(&((pointer)->ip_blkid), blockNumber), \
-	(pointer)->ip_posid = offNum \
+	(pointer)->ip_posid = (offNum) \
 )
 
 /*
diff --git a/src/include/storage/off.h b/src/include/storage/off.h
index fe8638f..f058fe1 100644
--- a/src/include/storage/off.h
+++ b/src/include/storage/off.h
@@ -26,7 +26,16 @@ typedef uint16 OffsetNumber;
 #define InvalidOffsetNumber		((OffsetNumber) 0)
 #define FirstOffsetNumber		((OffsetNumber) 1)
 #define MaxOffsetNumber			((OffsetNumber) (BLCKSZ / sizeof(ItemIdData)))
-#define OffsetNumberMask		(0xffff)		/* valid uint16 bits */
+
+/*
+ * The biggest BLCKSZ we support is 32kB, and each ItemId takes 6 bytes.
+ * That limits the number of line pointers in a page to 32kB/6B = 5461.
+ * Therefore, 13 bits in OffsetNumber are enough to represent all valid
+ * on-disk line pointers.  Hence, we can reserve the high-order bits in
+ * OffsetNumber for other purposes.
+ */
+#define OffsetNumberBits		13
+#define OffsetNumberMask		((((uint16) 1) << OffsetNumberBits) - 1)
 
 /* ----------------
  *		support macros
-- 
2.9.3 (Apple Git-75)



  [application/octet-stream] 0003-Main-WARM-patch_v26.patch (317.3K, ../../CABOikdOTstHK2y0rDk+Y3Wx9HRe+bZtj3zuYGU=VngneiHo5KQ@mail.gmail.com/4-0003-Main-WARM-patch_v26.patch)
  download | inline diff:
From ed94731e62385c3437831b148f34ac6deda268a2 Mon Sep 17 00:00:00 2001
From: Pavan Deolasee <[email protected]>
Date: Wed, 5 Apr 2017 23:26:31 +0530
Subject: [PATCH 3/4] Main WARM patch.

We perform WARM update if the update is not modifying all indexes, but
modifying at least one index and has enough free space in the heap block to
keep the new version of the tuple.

The update works pretty much the same way as HOT updates, but the index whose
key values have changed must receive another index entry, pointing to the same
root of the HOT chain. Such chains which may have more than one index pointers
in at least one index, are called WARM chains. But now since there are 2 index
pointers to the same chain, we must do recheck to confirm that the index
pointer should or should not see the tuple. HOT pruning and other technique
remain the same.

WARM chains must subsequently be cleaned up by removing additional index
pointers. Once cleaned up, they are further be WARM updated and
index-only-scans will work.

To ensure that we don't do wasteful work, we only do WARM update if less than
50% indexes need updates. For anything above that, it probably does not make
sense to do WARM updates because most indexes will receive an update anyways
and cleanup cost will be high.

A new table-level option (enable_warm) is added, the default currently being
ON. When the option is ON, WARM updates are allowed on the table. We allow user
to set enable_warm to OFF. But once it's turned ON, we don't allow turning it OFF
again. This is necessary because once WARM is enabled, the table may have WARM
chains and WARM index pointers and those must be handled correctly.
---
 contrib/bloom/blutils.c                     |   1 +
 contrib/bloom/blvacuum.c                    |   2 +-
 src/backend/access/brin/brin.c              |   1 +
 src/backend/access/common/reloptions.c      |  18 +-
 src/backend/access/gin/ginvacuum.c          |   3 +-
 src/backend/access/gist/gist.c              |   1 +
 src/backend/access/gist/gistvacuum.c        |   3 +-
 src/backend/access/hash/hash.c              |  18 +-
 src/backend/access/hash/hashsearch.c        |   5 +
 src/backend/access/heap/README.WARM         | 400 ++++++++++++
 src/backend/access/heap/heapam.c            | 790 ++++++++++++++++++++---
 src/backend/access/heap/pruneheap.c         |   9 +-
 src/backend/access/heap/rewriteheap.c       |  12 +-
 src/backend/access/heap/tuptoaster.c        |   3 +-
 src/backend/access/index/genam.c            |   4 +
 src/backend/access/index/indexam.c          | 206 +++++-
 src/backend/access/nbtree/nbtinsert.c       | 228 ++++---
 src/backend/access/nbtree/nbtpage.c         |  56 +-
 src/backend/access/nbtree/nbtree.c          | 105 +++-
 src/backend/access/nbtree/nbtsearch.c       |   5 +
 src/backend/access/nbtree/nbtutils.c        | 196 ++++++
 src/backend/access/nbtree/nbtxlog.c         |  27 +-
 src/backend/access/rmgrdesc/heapdesc.c      |  26 +-
 src/backend/access/rmgrdesc/nbtdesc.c       |   4 +-
 src/backend/access/spgist/spgutils.c        |   1 +
 src/backend/access/spgist/spgvacuum.c       |  12 +-
 src/backend/catalog/index.c                 |  71 ++-
 src/backend/catalog/indexing.c              |  60 +-
 src/backend/catalog/system_views.sql        |   4 +-
 src/backend/commands/constraint.c           |   7 +-
 src/backend/commands/copy.c                 |   3 +
 src/backend/commands/indexcmds.c            |  17 +-
 src/backend/commands/tablecmds.c            |  14 +-
 src/backend/commands/vacuumlazy.c           | 668 +++++++++++++++++++-
 src/backend/executor/execIndexing.c         |  21 +-
 src/backend/executor/execReplication.c      |  30 +-
 src/backend/executor/nodeBitmapHeapscan.c   |  13 +-
 src/backend/executor/nodeIndexscan.c        |   4 +-
 src/backend/executor/nodeModifyTable.c      |  27 +-
 src/backend/postmaster/pgstat.c             |   7 +-
 src/backend/replication/logical/decode.c    |  13 +-
 src/backend/storage/page/bufpage.c          |  23 +
 src/backend/utils/adt/pgstatfuncs.c         |  31 +
 src/backend/utils/cache/relcache.c          | 113 +++-
 src/backend/utils/time/combocid.c           |   4 +-
 src/backend/utils/time/tqual.c              |  24 +-
 src/include/access/amapi.h                  |  22 +
 src/include/access/genam.h                  |  22 +-
 src/include/access/heapam.h                 |  31 +-
 src/include/access/heapam_xlog.h            |  24 +-
 src/include/access/htup_details.h           | 116 +++-
 src/include/access/nbtree.h                 |  26 +-
 src/include/access/nbtxlog.h                |  10 +-
 src/include/access/relscan.h                |   9 +-
 src/include/catalog/index.h                 |   7 +
 src/include/catalog/pg_proc.h               |   4 +
 src/include/commands/progress.h             |   1 +
 src/include/executor/executor.h             |   1 +
 src/include/executor/nodeIndexscan.h        |   1 -
 src/include/nodes/execnodes.h               |   1 +
 src/include/pgstat.h                        |   4 +-
 src/include/storage/bufpage.h               |   2 +
 src/include/utils/rel.h                     |  19 +
 src/include/utils/relcache.h                |   5 +-
 src/test/regress/expected/alter_generic.out |   4 +-
 src/test/regress/expected/rules.out         |  12 +-
 src/test/regress/expected/warm.out          | 930 ++++++++++++++++++++++++++++
 src/test/regress/parallel_schedule          |   2 +
 src/test/regress/sql/warm.sql               | 360 +++++++++++
 69 files changed, 4524 insertions(+), 379 deletions(-)
 create mode 100644 src/backend/access/heap/README.WARM
 create mode 100644 src/test/regress/expected/warm.out
 create mode 100644 src/test/regress/sql/warm.sql

diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index f2eda67..b356e2b 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -142,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/contrib/bloom/blvacuum.c b/contrib/bloom/blvacuum.c
index 04abd0f..ff50361 100644
--- a/contrib/bloom/blvacuum.c
+++ b/contrib/bloom/blvacuum.c
@@ -88,7 +88,7 @@ blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		while (itup < itupEnd)
 		{
 			/* Do we have to delete this tuple? */
-			if (callback(&itup->heapPtr, callback_state))
+			if (callback(&itup->heapPtr, false, callback_state) == IBDCR_DELETE)
 			{
 				/* Yes; adjust count of tuples that will be left on page */
 				BloomPageGetOpaque(page)->maxoff--;
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 649f348..a0fd203 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -119,6 +119,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 6d1f22f..ce7d4da 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -88,6 +88,11 @@
  * Setting parallel_workers is safe, since it acts the same as
  * max_parallel_workers_per_gather which is a USERSET parameter that doesn't
  * affect existing plans or queries.
+ *
+ * Setting enable_warm requires AccessExclusiveLock on the table. This is
+ * essential to ensure that any concurrent scan does not end up ignoring WARM
+ * chains created after enable_warm is turned ON. So we must disallow any
+ * SELECTs while changing this option.
  */
 
 static relopt_bool boolRelOpts[] =
@@ -137,6 +142,15 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
+	{
+		{
+			"enable_warm",
+			"Table supports WARM updates",
+			RELOPT_KIND_HEAP,
+			AccessExclusiveLock
+		},
+		HEAP_DEFAULT_ENABLE_WARM
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1351,7 +1365,9 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"user_catalog_table", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
-		offsetof(StdRdOptions, parallel_workers)}
+		offsetof(StdRdOptions, parallel_workers)},
+		{"enable_warm", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, enable_warm)}
 	};
 
 	options = parseRelOptions(reloptions, validate, kind, &numoptions);
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 26c077a..46ed4fe 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -56,7 +56,8 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, false, gvs->callback_state) ==
+				IBDCR_DELETE)
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 6593771..843389b 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -94,6 +94,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 77d9d12..0955db6 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,8 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), false, callback_state) ==
+						IBDCR_DELETE)
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index b835f77..571dee8 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -75,6 +75,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = hashbuild;
 	amroutine->ambuildempty = hashbuildempty;
 	amroutine->aminsert = hashinsert;
+	amroutine->amwarminsert = NULL;
 	amroutine->ambulkdelete = hashbulkdelete;
 	amroutine->amvacuumcleanup = hashvacuumcleanup;
 	amroutine->amcanreturn = NULL;
@@ -92,6 +93,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -823,6 +825,7 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 			IndexTuple	itup;
 			Bucket		bucket;
 			bool		kill_tuple = false;
+			IndexBulkDeleteCallbackResult	result;
 
 			itup = (IndexTuple) PageGetItem(page,
 											PageGetItemId(page, offno));
@@ -832,13 +835,18 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 			 * To remove the dead tuples, we strictly want to rely on results
 			 * of callback function.  refer btvacuumpage for detailed reason.
 			 */
-			if (callback && callback(htup, callback_state))
+			if (callback)
 			{
-				kill_tuple = true;
-				if (tuples_removed)
-					*tuples_removed += 1;
+				result = callback(htup, false, callback_state);
+				if (result == IBDCR_DELETE)
+				{
+					kill_tuple = true;
+					if (tuples_removed)
+						*tuples_removed += 1;
+				}
 			}
-			else if (split_cleanup)
+
+			if (!kill_tuple && split_cleanup)
 			{
 				/* delete the tuples that are moved by split. */
 				bucket = _hash_hashkey2bucket(_hash_get_indextuple_hashkey(itup),
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 2d92049..330ccc5 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -59,6 +59,8 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
 	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 	so->hashso_heappos = itup->t_tid;
 
+	if (scan->xs_want_itup)
+		scan->xs_itup = itup;
 	return true;
 }
 
@@ -367,6 +369,9 @@ _hash_first(IndexScanDesc scan, ScanDirection dir)
 	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 	so->hashso_heappos = itup->t_tid;
 
+	if (scan->xs_want_itup)
+		scan->xs_itup = itup;
+
 	return true;
 }
 
diff --git a/src/backend/access/heap/README.WARM b/src/backend/access/heap/README.WARM
new file mode 100644
index 0000000..ffdb1e1
--- /dev/null
+++ b/src/backend/access/heap/README.WARM
@@ -0,0 +1,400 @@
+src/backend/access/heap/README.WARM
+
+Write Amplification Reduction Method (WARM)
+===========================================
+
+The Heap Only Tuple (HOT) feature greatly eliminated redudant index
+entries and allowed re-use of the dead space occupied by previously
+updated or deleted tuples (see src/backend/access/heap/README.HOT)
+
+One of the necessary conditions for satisfying HOT update is that the
+update must not change a column used in any of the indexes on the table.
+The condition is sometimes hard to meet, especially for complex
+workloads with several indexes on large yet frequently updated tables.
+Worse, sometimes only one or two index columns may be updated, but the
+regular non-HOT update will still insert a new index entry in every
+index on the table, irrespective of whether the key pertaining to the
+index is changed or not.
+
+WARM is a technique devised to address these problems.
+
+
+Update Chains With Multiple Index Entries Pointing to the Root
+--------------------------------------------------------------
+
+When a non-HOT update is caused by an index key change, a new index
+entry must be inserted for the changed index. But if the index key
+hasn't changed for other indexes, we don't really need to insert a new
+entry. Even though the existing index entry is pointing to the old
+tuple, the new tuple is reachable via the t_ctid chain. To keep things
+simple, a WARM update requires that the heap block must have enough
+space to store the new version of the tuple. This is same as HOT
+updates.
+
+In WARM, we ensure that every index entry always points to the root of
+the WARM chain. In fact, a WARM chain looks exactly like a HOT chain
+except for the fact that there could be multiple index entries pointing
+to the root of the chain. So when new entry is inserted in an index for
+updated tuple, and if we are doing a WARM update, the new entry is made
+point to the root of the WARM chain.
+
+For example, if we have a table with two columns and two indexes on each of the
+column. When a tuple is first inserted into the table, we have exactly one
+index entry pointing to the tuple from both indexes.
+
+	lp [1]
+	[1111, aaaa]
+
+	Index1's entry (1111) points to 1
+	Index2's entry (aaaa) also points to 1
+
+Now if the tuple's second column is updated and if there is room on the
+page, we perform a WARM update. To do so, Index1 does not get any new
+entry and Index2's new entry will still point to the root tuple of the
+chain.
+
+	lp [1]  [2]
+	[1111, aaaa]->[1111, bbbb]
+
+	Index1's entry (1111) points to 1
+	Index2's old entry (aaaa) points to 1
+	Index2's new entry (bbbb) also points to 1
+
+"A update chain which has more than one index entries pointing to its
+root line pointer is called WARM chain and the action that creates a
+WARM chain is called WARM update."
+
+Since all indexes always point to the root of the WARM chain, even when
+there are more than one index entries, WARM chains can be pruned and
+dead tuples can be removed without a need to do corresponding index
+cleanup.
+
+While this solves the problem of pruning dead tuples from a HOT/WARM
+chain, it also opens up a new technical challenge because now we have a
+situation where a heap tuple is reachable from multiple index entries,
+each having a different index key. While MVCC still ensures that only
+valid tuples are returned, a tuple with a wrong index key may be
+returned because of wrong index entries. In the above example, tuple
+[1111, bbbb] is reachable from both keys (aaaa) as well as (bbbb). For
+this reason, tuples returned from a WARM chain must always be rechecked
+for index key-match.
+
+Recheck Index Key Againt Heap Tuple
+-----------------------------------
+
+Since every Index AM has it's own notion of index tuples, each Index AM
+must implement its own method to recheck heap tuples. For example, a
+hash index stores the hash value of the column and hence recheck routine
+for hash AM must first compute the hash value of the heap attribute and
+then compare it against the value stored in the index tuple.
+
+The patch currently implement recheck routines only for btree
+indexes. If the table has an index which doesn't support recheck
+routine, WARM updates are disabled on such tables.
+
+Problem With Duplicate (key, ctid) Index Entries
+------------------------------------------------
+
+The index-key recheck logic works as long as there are no duplicate
+index keys, both pointing to the same WARM chain. In that case, the same
+valid tuple will be reachable via multiple index keys, yet satisfying
+the index key checks. In the above example, if the tuple [1111, bbbb] is
+again updated to [1111, aaaa] and if we insert a new index entry (aaaa)
+pointing to the root line pointer, we will end up with the following
+structure:
+
+	lp [1]  [2]  [3]
+	[1111, aaaa]->[1111, bbbb]->[1111, aaaa]
+
+	Index1's entry (1111) points to 1
+	Index2's oldest entry (aaaa) points to 1
+	Index2's old entry (bbbb) also points to 1
+	Index2's new entry (aaaa) also points to 1
+
+We must solve this problem to ensure that the same tuple is not
+reachable via multiple index pointers. There are couple of ways to
+address this issue:
+
+1. Do not allow WARM update to a tuple from a WARM chain. This
+guarantees that there can never be duplicate index entries to the same
+root line pointer because we must have checked for old and new index
+keys while doing the first WARM update.
+
+2. Do not allow duplicate (key, ctid) index pointers. In the above
+example, since (aaaa, 1) already exists in the index, we must not insert
+a duplicate index entry.
+
+The patch currently implements 1 i.e. do not do WARM updates to a tuple
+from a WARM chain. HOT updates are fine because they do not add a new
+index entry.
+
+Even with the restriction, this is a significant improvement because the
+number of regular UPDATEs are curtailed down to half.
+
+Expression and Partial Indexes
+------------------------------
+
+Expressions may evaluate to the same value even if the underlying column
+values have changed. A simple example is an index on "lower(col)" which
+will return the same value if the new heap value only differs in the
+case sensitivity. So we can not solely rely on the heap column check to
+decide whether or not to insert a new index entry for expression
+indexes. Similarly, for partial indexes, the predicate expression must
+be evaluated to decide whether or not to cause a new index entry when
+columns referred in the predicate expressions change.
+
+(None of these things are currently implemented and we squarely disallow
+WARM update if a column from expression indexes or predicate has
+changed).
+
+
+Efficiently Finding the Root Line Pointer
+-----------------------------------------
+
+During WARM update, we must be able to find the root line pointer of the
+tuple being updated. It must be noted that the t_ctid field in the heap
+tuple header is usually used to find the next tuple in the update chain.
+But the tuple that we are updating, must be the last tuple in the update
+chain. In such cases, the c_tid field usually points the tuple itself.
+So in theory, we could use the t_ctid to store additional information in
+the last tuple of the update chain, if the information about the tuple
+being the last tuple is stored elsewhere.
+
+We now utilize another bit from t_infomask2 to explicitly identify that
+this is the last tuple in the update chain.
+
+HEAP_LATEST_TUPLE - When this bit is set, the tuple is the last tuple in
+the update chain. The OffsetNumber part of t_ctid points to the root
+line pointer of the chain when HEAP_LATEST_TUPLE flag is set.
+
+If UPDATE operation is aborted, the last tuple in the update chain
+becomes dead. The root line pointer information stored in the tuple
+which remains the last valid tuple in the chain is also lost. In such
+rare cases, the root line pointer must be found in a hard way by
+scanning the entire heap page.
+
+Tracking WARM Chains
+--------------------
+
+When a tuple is WARM updated, the old, the new and every subsequent tuple in
+the chain is marked with a special HEAP_WARM_UPDATED flag. We use the last
+remaining bit in t_infomask2 to store this information.
+
+When a tuple is returned from a WARM chain, the caller must do additional
+checks to ensure that the tuple matches the index key. Even if the tuple
+precedes the WARM update in the chain, it must still be rechecked for the index
+key match (case when old tuple is returned by the new index key). So we must
+follow the update chain everytime to the end to check if this is a WARM
+chain.
+
+Converting WARM chains back to HOT chains (VACUUM ?)
+----------------------------------------------------
+
+The current implementation of WARM allows only one WARM update per
+chain. This simplifies the design and addresses certain issues around
+duplicate key scans. But this also implies that the benefit of WARM will be
+no more than 50%, which is still significant, but if we could return
+WARM chains back to normal status, we could do far more WARM updates.
+
+A distinct property of a WARM chain is that at least one index has more
+than one live index entries pointing to the root of the chain. In other
+words, if we can remove duplicate entry from every index or conclusively
+prove that there are no duplicate index entries for the root line
+pointer, the chain can again be marked as HOT.
+
+A WARM chain has two parts, separated by the tuple that caused WARM
+update. All tuples in each part has matching index keys, but certain
+index keys may not match between these two parts. Lets say we mark heap
+tuples in the second part with a special HEAP_WARM_TUPLE flag. Similarly, the
+new index entries caused by the first WARM update are also marked with
+INDEX_WARM_POINTER flags.
+
+There are two distinct parts of the WARM chain. The first part where none of
+the tuples have HEAP_WARM_TUPLE flag set, we call them CLEAR tuples. The second
+part where every tuple has the flag set, we call them WARM tuples. Each of
+these parts satisfy HOT property on its own i.e. all tuples have the same value
+for indexed columns. But these two parts are separated by the WARM update which
+breaks HOT property for one or more indexes.
+
+Heap chain: [1] [2] [3] [4]
+			[aaaa, 1111] -> [aaaa, 1111] -> [bbbb, 1111]W -> [bbbb, 1111]W
+
+Index1: 	(aaaa) points to 1 (satisfies only tuples without W)
+			(bbbb)W points to 1 (satisfies only tuples marked with W)
+
+Index2:		(1111) points to 1 (satisfies tuples with and without W)
+
+
+It's clear that for indexes with both CLEAR and WARM pointers, the CLEAR heap
+tuples will be reachable from CLEAR the index pointer and the WARM heap tuples
+will be reachable from the WARM index pointer. But for indexes which only have
+CLEAR pointers, both CLEAR and WARM heap tuples will be reachable from the
+CLEAR pointers. Note that such indexes must not have received a new index
+entry during WARM update.
+
+During first heap scan of VACUUM, we look for candidate WARM chains. A WARM
+chain is a candidate for conversion if all tuples in the WARM chain are either
+CLEAR tuples or WARM tuples. For all such candidate chains, we remember the
+root line pointer of the chain along with whether the chain has only CLEAR
+tuples or only WARM tuples.
+
+If we have a candidate WARM chain with WARM tuples, then our goal is to remove
+the CLEAR index pointers to such chains. On the other hand, if the candidate
+WARM chain has only CLEAR tuples, our goal is to remove all WARM index pointers
+to the chain. But there is a catch here. For Index2 above, we only have CLEAR
+index pointer, and since all heap tuples, WARM or CLEAR, are reachable only via
+this pointer, it must not be removed. In other words, we should remove CLEAR
+index pointer iff a WARM index pointer to the same root line pointer exists.
+Since index vacuum may visit these pointers in any order, we can't determine in
+a single index pass whether a WARM index pointer exists to a candidate WARM
+chain with all WARM tuples. So in the first index pass we count number of CLEAR
+and WARM pointers to each candidate chain. In the second pass, we remove the
+CLEAR pointer to a WARM chain if another WARM pointer to the chain exists. A
+WARM pointer to a chain with WARM tuples is always preserved, but such pointers
+are converted into CLEAR pointers during the second index scan. Similarly, a
+CLEAR pointer to a chain with CLEAR tuples is always preserved too. A WARM
+pointer to a chain with CLEAR tuples can always be removed since it can happen
+only in case of aborted WARM updates. Note that all index pointers, either
+CLEAR or WARM, to dead tuples are removed during the first index scan itself.
+
+Once we certainly know that all duplicate index pointers have been removed and
+the index pointers have been changed to CLEAR pointers, during the second heap
+scan, we convert the WARM chain by clearing HEAP_WARM_UPDATED and
+HEAP_WARM_TUPLE flags on the tuples.
+
+There are some more problems around aborted vacuums. For example, if vacuum
+aborts after converting WARM index pointer to a CLEAR pointer, but before we
+get chance to remove the existing CLEAR pointer, we will end up with two CLEAR
+pointers to the same root. But since the HEAP_WARM_UPDATED flag on the heap
+tuple is still set, further WARM updates to the chain will not be allowed. We
+will need some special handling for case with multiple CLEAR index pointers. We
+can either leave these WARM chains alone and let them die with a subsequent
+non-WARM update or must apply heap-recheck logic during index vacuum to find
+the dead pointer. But such rechecks will cause random access to the heap and
+won't be very optimal.  Given that vacuum-aborts are not common, we leave this
+case unhandled. We must still check for presence of multiple CLEAR index
+pointers and ensure that we don't accidently remove either of these pointers
+(unless we know which one of those is dead) and also must not allow WARM
+updates to chains with more than one CLEAR pointers.
+
+Tuning AutoVacuum and Manual VACUUM
+------------------ ------------------
+
+The current design of WARM cleanup requires two index passes. We optimise this
+by doing the second pass only for those indexes which have WARM index pointers.
+If we imagine a case where out of N indexes on a table only K gets updated,
+WARM update will create WARM pointers in only those K indexes. As a
+consequence, only those K indexes will require two index scans. When K << N,
+the cost of additional index scan should be limited.
+
+If an UPDATE requires WARM inserts in most of the indexes, then the cost of
+doing WARM updates and the overhead of WARM cleanup, may not be justified. The
+current design thus avoids doing WARM updates when more than 50% indexes
+require WARM inserts. For example, if you've a table with 4 indexes and an
+UPDATE is going to modify 3 of those indexes, WARM update won't be used. But if
+2 or less indexes are being updated, then WARM update will be used, if all
+other conditions are favourable.
+
+To give further control to the user, we've added a few more controlling
+parameters.
+
+autovacuum_warmcleanup_scale_factor - specifies the fraction of the total
+number of tuples for autovacuum to consider WARM cleanup on a table. For
+example, if set to 0.20, WARM cleanup will be done only if the percentage of
+the WARM chains is more than or equal to 20.
+
+autovacuum_warmcleanup_index_scale_factor - specifies the fraction of the WARM
+chains for autovacuum to consider WARM cleanup for a table. So if an index has
+very less WARM inserts, such an index can be skipped from WARM cleanup. Note
+that if an index is skipped, none of the candidate WARM chains pointed to by
+that index can be cleaned up.
+
+Both these parameters can also be specified on per-table basis.
+
+For a manual VACUUM, the user can use a newly added option to force WARM
+cleanup.
+
+Memory Management for VACUUM
+----------------------------
+
+Since WARM cleanup requires tracking of a lot more information than the regular
+VACUUM, we allocate a work area which is large enough to hold the required
+information, but still staying within the set maintenance_work_mem. Obviously,
+if the WARM cleanup is not requested or autovacuum has decided not to do WARM
+cleanup, then the entire memory is available for tracking the dead tuples. But
+when we are doing WARM cleanup, we fill up the work area such that dead tuples
+are added from one end and candidate WARM chains are added from the other end.
+When the allocated work memory is exhausted, we do one round of index and heap
+cleanup and then continue again.
+
+Disabling WARM on a per-table Basis
+-----------------------------------
+
+A new table level option (enable_warm) is added to enable or disable WARM on a
+given table. Note that while you can turn WARM ON, if it's currently OFF, you
+can't turn WARM OFF once it's turned ON. Changing the option requires an
+AccessExclusiveLock on the table.
+
+Online Cleanup of WARM pointers
+-------------------------------
+
+During normal index scans, if we find certain conditions, we can do online
+cleanup of the index pointers. This is very similar to how dead index pointers
+are tracked and marked with the LP_DEAD flag.
+
+During normal index scans, if we find a WARM chain with either all CLEAR or all
+WARM pointers then we do one of the following:
+
+1. If this is a WARM index pointer to a chain with WARM tuples, do nothing.
+2. If this is a CLEAR index pointer to a chain with WARM tuples
+	2a. If recheck returns false, kill the CLEAR pointer.
+	2b. If recheck returns true, convert the CLEAR pointer to a WARM pointer.
+3. If this is a WARM index pointer to a chain with CLEAR tuples, kill the
+   pointer.
+4. If this is a CLEAR index pointer to a chain with CLEAR tuples
+	4a. If recheck returns false, kill the CLEAR pointer
+	4b. If recheck returns true, do nothing.
+
+The choice of 2b is curious because by doing that we are actually making
+changes to the index which did not receive a WARM insert during WARM update.
+But this allows us to avoid repeated "recheck" of the tuple when the same tuple
+is accessed again and again. Since we only use hinting mechanism to mark the
+buffer dirty, this should not cause unnecessary IO.
+
+If we make any changes to the index pointer state, we never WAL log that
+operation.
+
+CREATE INDEX CONCURRENTLY
+-------------------------
+
+Currently CREATE INDEX CONCURRENTLY (CIC) is implemented as a 3-phase
+process.  In the first phase, we create catalog entry for the new index
+so that the index is visible to all other backends, but still don't use
+it for either read or write.  But we ensure that no new broken HOT
+chains are created by new transactions. In the second phase, we build
+the new index using a MVCC snapshot and then make the index available
+for inserts. We then do another pass over the index and insert any
+missing tuples, everytime indexing only it's root line pointer. See
+README.HOT for details about how HOT impacts CIC and how various
+challenges are tackeled.
+
+WARM poses another challenge because it allows creation of HOT chains
+even when an index key is changed. But since the index is not ready for
+insertion until the second phase is over, we might end up with a
+situation where the HOT chain has tuples with different index columns,
+yet only one of these values are indexed by the new index. Note that
+during the third phase, we only index tuples whose root line pointer is
+missing from the index. But we can't easily check if the existing index
+tuple is actually indexing the heap tuple visible to the new MVCC
+snapshot. Finding that information will require us to query the index
+again for every tuple in the chain, especially if it's a WARM tuple.
+This would require repeated access to the index. Another option would be
+to return index keys along with the heap TIDs when index is scanned for
+collecting all indexed TIDs during third phase. We can then compare the
+heap tuple against the already indexed key and decide whether or not to
+index the new tuple.
+
+We solve this problem more simply by disallowing WARM updates until the
+index is ready for insertion. We don't need to disallow WARM on a
+wholesale basis, but only those updates that change the columns of the
+new index are disallowed to be WARM updates.
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 30262ef..a65516d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -97,9 +97,12 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				HeapTuple newtup, OffsetNumber root_offnum,
 				HeapTuple old_key_tup,
 				bool all_visible_cleared, bool new_all_visible_cleared);
-static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
+static void HeapCheckColumns(Relation relation,
 							 Bitmapset *interesting_cols,
-							 HeapTuple oldtup, HeapTuple newtup);
+							 HeapTuple oldtup, HeapTuple newtup,
+							 Bitmapset **toasted_attrs,
+							 Bitmapset **compressed_attrs,
+							 Bitmapset **modified_attrs);
 static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
@@ -1974,6 +1977,212 @@ heap_fetch(Relation relation,
 }
 
 /*
+ * Check status of a (possibly) WARM chain.
+ *
+ * This function looks at a HOT/WARM chain starting at tid and return a bitmask
+ * of information. We only follow the chain as long as it's known to be valid
+ * HOT chain. Information returned by the function consists of:
+ *
+ *  HCWC_WARM_UPDATED_TUPLE - a tuple with HEAP_WARM_UPDATED is found somewhere
+ *  						  in the chain. Note that when a tuple is WARM
+ *  						  updated, both old and new versions are marked
+ *  						  with this flag. So presence of this flag
+ *  						  indicates that a WARM update was performed on
+ *  						  this chain, but the update may have either
+ *  						  committed or aborted.
+ *
+ *  HCWC_WARM_TUPLE  - a tuple with HEAP_WARM_TUPLE is found somewhere in
+ *					  the chain. This flag is set only on the new version of
+ *					  the tuple while performing WARM update.
+ *
+ *  HCWC_CLEAR_TUPLE - a tuple without HEAP_WARM_TUPLE is found somewhere in
+ *  					 the chain. This either implies that the WARM updated
+ *  					 either aborted or it's recent enough that the old
+ *  					 tuple is still not pruned away by chain pruning logic.
+ *
+ *	If stop_at_warm is true, we stop when the first HEAP_WARM_UPDATED tuple is
+ *	found and return information collected so far.
+ */
+HeapCheckWarmChainStatus
+heap_check_warm_chain(Page dp, ItemPointer tid, bool stop_at_warm)
+{
+	TransactionId				prev_xmax = InvalidTransactionId;
+	OffsetNumber				offnum;
+	HeapTupleData				heapTuple;
+	HeapCheckWarmChainStatus	status = 0;
+
+	offnum = ItemPointerGetOffsetNumber(tid);
+	heapTuple.t_self = *tid;
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
+
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
+
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			if (ItemIdIsRedirected(lp))
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		ItemPointerSetOffsetNumber(&heapTuple.t_self, offnum);
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple.t_data)))
+			break;
+
+
+		if (HeapTupleHeaderIsWarmUpdated(heapTuple.t_data))
+		{
+			/* We found a WARM_UPDATED tuple */
+			status |= HCWC_WARM_UPDATED_TUPLE;
+
+			/*
+			 * If we've been told to stop at the first WARM_UPDATED tuple, just
+			 * return whatever information collected so far.
+			 */
+			if (stop_at_warm)
+				return status;
+
+			/*
+			 * Remember whether it's a CLEAR or a WARM tuple.
+			 */
+			if (HeapTupleHeaderIsWarm(heapTuple.t_data))
+				status |= HCWC_WARM_TUPLE;
+			else
+				status |= HCWC_CLEAR_TUPLE;
+		}
+		else
+			/* Must be a regular, non-WARM tuple */
+			status |= HCWC_CLEAR_TUPLE;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (!HeapTupleIsHotUpdated(&heapTuple))
+			break;
+
+		/*
+		 * It can't be a HOT chain if the tuple contains root line pointer
+		 */
+		if (HeapTupleHeaderHasRootOffset(heapTuple.t_data))
+			break;
+
+		offnum = ItemPointerGetOffsetNumber(&heapTuple.t_data->t_ctid);
+		prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple.t_data);
+	}
+
+	/* All OK. No need to recheck */
+	return status;
+}
+
+/*
+ * Scan through the WARM chain starting at tid and reset all WARM related
+ * flags. At the end, the chain will have all characteristics of a regular HOT
+ * chain.
+ *
+ * Return the number of cleared offnums. Cleared offnums are returned in the
+ * passed-in cleared_offnums array. The caller must ensure that the array is
+ * large enough to hold maximum offnums that can be cleared by this invokation
+ * of heap_clear_warm_chain().
+ */
+int
+heap_clear_warm_chain(Page dp, ItemPointer tid, OffsetNumber *cleared_offnums)
+{
+	TransactionId				prev_xmax = InvalidTransactionId;
+	OffsetNumber				offnum;
+	HeapTupleData				heapTuple;
+	int							num_cleared = 0;
+
+	offnum = ItemPointerGetOffsetNumber(tid);
+	heapTuple.t_self = *tid;
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
+
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
+
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			if (ItemIdIsRedirected(lp))
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		ItemPointerSetOffsetNumber(&heapTuple.t_self, offnum);
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple.t_data)))
+			break;
+
+
+		/*
+		 * Clear WARM_UPDATED and WARM flags.
+		 */
+		if (HeapTupleHeaderIsWarmUpdated(heapTuple.t_data))
+		{
+			HeapTupleHeaderClearWarmUpdated(heapTuple.t_data);
+			HeapTupleHeaderClearWarm(heapTuple.t_data);
+			cleared_offnums[num_cleared++] = offnum;
+		}
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (!HeapTupleIsHotUpdated(&heapTuple))
+			break;
+
+		/*
+		 * It can't be a HOT chain if the tuple contains root line pointer
+		 */
+		if (HeapTupleHeaderHasRootOffset(heapTuple.t_data))
+			break;
+
+		offnum = ItemPointerGetOffsetNumber(&heapTuple.t_data->t_ctid);
+		prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple.t_data);
+	}
+
+	return num_cleared;
+}
+
+/*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
  * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
@@ -1993,11 +2202,15 @@ heap_fetch(Relation relation,
  * Unlike heap_fetch, the caller must already have pin and (at least) share
  * lock on the buffer; it is still pinned/locked at exit.  Also unlike
  * heap_fetch, we do not report any pgstats count; caller may do so if wanted.
+ *
+ * recheck should be set false on entry by caller, will be set true on exit
+ * if a WARM tuple is encountered.
  */
 bool
 heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
+					   bool *all_dead, bool first_call,
+					   HeapCheckWarmChainStatus *status)
 {
 	Page		dp = (Page) BufferGetPage(buffer);
 	TransactionId prev_xmax = InvalidTransactionId;
@@ -2010,6 +2223,9 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 	if (all_dead)
 		*all_dead = first_call;
 
+	if (status)
+		*status = 0;
+
 	Assert(TransactionIdIsValid(RecentGlobalXmin));
 
 	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
@@ -2019,6 +2235,22 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 
 	heapTuple->t_self = *tid;
 
+
+	/*
+	 * Check status of the chain if the caller has asked for it. We do it only
+	 * once for each chain.
+	 *
+	 * XXX This is not very efficient right now, and we should look for
+	 * possible improvements here.
+	 *
+	 * XXX We currently don't support turning enable_warm OFF once it's
+	 * turned ON. But if we ever do that, we must not rely on
+	 * RelationWarmUpdatesEnabled check to decide whether recheck is needed
+	 * or not.
+	 */
+	if (RelationWarmUpdatesEnabled(relation) && status)
+		*status = heap_check_warm_chain(dp, &heapTuple->t_self, false);
+
 	/* Scan through possible multiple members of HOT-chain */
 	for (;;)
 	{
@@ -2051,9 +2283,12 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
 
 		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 * Shouldn't see a HEAP_ONLY tuple at chain start, unless we are
+		 * dealing with a WARM updated tuple in which case deferred triggers
+		 * may request to fetch a WARM tuple from middle of a chain.
 		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple) &&
+				!HeapTupleIsWarmUpdated(heapTuple))
 			break;
 
 		/*
@@ -2114,7 +2349,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		 * Check to see if HOT chain continues past this tuple; if so fetch
 		 * the next offnum and loop around.
 		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
+		if (HeapTupleIsHotUpdated(heapTuple) &&
+			!HeapTupleHeaderHasRootOffset(heapTuple->t_data))
 		{
 			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
 				   ItemPointerGetBlockNumber(tid));
@@ -2138,18 +2374,48 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
  */
 bool
 heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
-				bool *all_dead)
+				bool *all_dead, bool *recheck, Buffer *cbuffer,
+				HeapTuple heapTuple)
 {
 	bool		result;
 	Buffer		buffer;
-	HeapTupleData heapTuple;
+	ItemPointerData ret_tid = *tid;
+	HeapCheckWarmChainStatus status = 0;
 
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	result = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
+	result = heap_hot_search_buffer(&ret_tid, relation, buffer, snapshot,
+									heapTuple, all_dead, true, &status);
+
+	/*
+	 * If the chain contains a WARM_UPDATED tuple, then we must do a recheck.
+	 */
+	if (HCWC_IS_WARM_UPDATED(status) && recheck)
+		*recheck = true;
+
+	/*
+	 * If we are returning a potential candidate tuple from this chain and the
+	 * caller has requested for "recheck" hint, keep the buffer locked and
+	 * pinned. The caller must release the lock and pin on the buffer in all
+	 * such cases.
+	 */
+	if (!result || !recheck || !(*recheck))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		ReleaseBuffer(buffer);
+	}
+
+	/*
+	 * Set the caller supplied tid with the actual location of the tuple being
+	 * returned.
+	 */
+	if (result)
+	{
+		*tid = ret_tid;
+		if (cbuffer)
+			*cbuffer = buffer;
+	}
+
 	return result;
 }
 
@@ -2792,7 +3058,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 		{
 			XLogRecPtr	recptr;
 			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			uint8		info = XLOG_HEAP_MULTI_INSERT;
 			char	   *tupledata;
 			int			totaldatalen;
 			char	   *scratchptr = scratch;
@@ -2889,7 +3155,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			/* filtering by origin on a row level is much more efficient */
 			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
 
-			recptr = XLogInsert(RM_HEAP2_ID, info);
+			recptr = XLogInsert(RM_HEAP_ID, info);
 
 			PageSetLSN(page, recptr);
 		}
@@ -3045,7 +3311,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	bool		all_visible_cleared = false;
 	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
 	bool		old_key_copied = false;
-	OffsetNumber	root_offnum;
+	OffsetNumber	root_offnum = InvalidOffsetNumber;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -3278,7 +3544,7 @@ l1:
 							  &new_xmax, &new_infomask, &new_infomask2);
 
 	/*
-	 * heap_get_root_tuple_one() may call palloc, which is disallowed once we
+	 * heap_get_root_tuple() may call palloc, which is disallowed once we
 	 * enter the critical section. So check if the root offset is cached in the
 	 * tuple and if not, fetch that information hard way before entering the
 	 * critical section.
@@ -3313,7 +3579,9 @@ l1:
 	}
 
 	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	if (HeapTupleHeaderIsMoved(tp.t_data))
+		tp.t_data->t_infomask &= ~HEAP_MOVED;
 	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	tp.t_data->t_infomask |= new_infomask;
 	tp.t_data->t_infomask2 |= new_infomask2;
@@ -3508,15 +3776,21 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 HTSU_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			Bitmapset **modified_attrsp, bool *warm_update)
 {
 	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
+	Bitmapset  *exprindx_attrs;
 	Bitmapset  *interesting_attrs;
 	Bitmapset  *modified_attrs;
+	Bitmapset  *notready_attrs;
+	Bitmapset  *compressed_attrs;
+	Bitmapset  *toasted_attrs;
+	List	   *indexattrsList;
 	ItemId		lp;
 	HeapTupleData oldtup;
 	HeapTuple	heaptup;
@@ -3524,8 +3798,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		old_key_copied = false;
 	Page		page;
 	BlockNumber block;
-	OffsetNumber	offnum;
-	OffsetNumber	root_offnum;
+	OffsetNumber	root_offnum = InvalidOffsetNumber;
 	MultiXactStatus mxact_status;
 	Buffer		buffer,
 				newbuf,
@@ -3537,6 +3810,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		use_hot_update = false;
+	bool		use_warm_update = false;
 	bool		hot_attrs_checked = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
@@ -3562,6 +3836,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
 				 errmsg("cannot update tuples during a parallel operation")));
 
+	/* Assume no-warm update */
+	if (warm_update)
+		*warm_update = false;
+
 	/*
 	 * Fetch the list of attributes to be checked for various operations.
 	 *
@@ -3582,10 +3860,14 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
+	exprindx_attrs = RelationGetIndexAttrBitmap(relation,
+										  INDEX_ATTR_BITMAP_EXPR_PREDICATE);
+	notready_attrs = RelationGetIndexAttrBitmap(relation,
+										  INDEX_ATTR_BITMAP_NOTREADY);
 
+	indexattrsList = RelationGetIndexAttrList(relation);
 
 	block = ItemPointerGetBlockNumber(otid);
-	offnum = ItemPointerGetOffsetNumber(otid);
 	buffer = ReadBuffer(relation, block);
 	page = BufferGetPage(buffer);
 
@@ -3605,8 +3887,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 		interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
 		hot_attrs_checked = true;
 	}
+
 	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
+	interesting_attrs = bms_add_members(interesting_attrs, exprindx_attrs);
+	interesting_attrs = bms_add_members(interesting_attrs, notready_attrs);
 
 	/*
 	 * Before locking the buffer, pin the visibility map page if it appears to
@@ -3623,7 +3908,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	Assert(ItemIdIsNormal(lp));
 
 	/*
-	 * Fill in enough data in oldtup for HeapDetermineModifiedColumns to work
+	 * Fill in enough data in oldtup for HeapCheckColumns to work
 	 * properly.
 	 */
 	oldtup.t_tableOid = RelationGetRelid(relation);
@@ -3650,8 +3935,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	}
 
 	/* Determine columns modified by the update. */
-	modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
-												  &oldtup, newtup);
+	HeapCheckColumns(relation, interesting_attrs,
+								 &oldtup, newtup, &toasted_attrs,
+								 &compressed_attrs, &modified_attrs);
+
+	if (modified_attrsp)
+		*modified_attrsp = bms_copy(modified_attrs);
 
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
@@ -3908,8 +4197,10 @@ l2:
 		bms_free(hot_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
+		bms_free(exprindx_attrs);
 		bms_free(modified_attrs);
 		bms_free(interesting_attrs);
+		bms_free(notready_attrs);
 		return result;
 	}
 
@@ -4034,7 +4325,6 @@ l2:
 		uint16		infomask_lock_old_tuple,
 					infomask2_lock_old_tuple;
 		bool		cleared_all_frozen = false;
-		OffsetNumber	root_offnum;
 
 		/*
 		 * To prevent concurrent sessions from updating the tuple, we have to
@@ -4073,7 +4363,9 @@ l2:
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
-		oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		oldtup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		if (HeapTupleHeaderIsMoved(oldtup.t_data))
+			oldtup.t_data->t_infomask &= ~HEAP_MOVED;
 		oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		HeapTupleClearHotUpdated(&oldtup);
 		/* ... and store info about transaction updating this tuple */
@@ -4227,6 +4519,75 @@ l2:
 		 */
 		if (hot_attrs_checked && !bms_overlap(modified_attrs, hot_attrs))
 			use_hot_update = true;
+		else
+		{
+			/*
+			 * If no WARM updates yet on this chain, let this update be a WARM
+			 * update. We must not do any WARM update even if the previous WARM
+			 * updated at the end aborted. That's why we look at
+			 * HEAP_WARM_UPDATED flag.
+			 *
+			 * We don't do WARM updates if one of the columns used in index
+			 * expressions is being modified. Since expressions may evaluate to
+			 * the same value, even when heap values change, we don't have a
+			 * good way to deal with duplicate key scans when expressions are
+			 * used in the index.
+			 *
+			 * We check if the HOT attrs are a subset of the modified
+			 * attributes. Since HOT attrs include all index attributes, this
+			 * allows to avoid doing a WARM update when all index attributes
+			 * are being updated. Performing a WARM update is not a great idea
+			 * because all indexes will receive a new entry anyways.
+			 *
+			 * We also disable WARM temporarily if we are modifying a column
+			 * which is used by a new index that's being added. We can't insert
+			 * new entries to such indexes and hence we must not allow creating
+			 * on WARM chains which are broken with respect to the new index
+			 * being added.
+			 *
+			 * Finally, we disable WARM if either the old or the new tuple has
+			 * toasted/compressed attributes. Detoasting and decompressing very
+			 * large attributes can make the recheck logic too slow and it can
+			 * make the code to determine modified attributes a lot slower too.
+			 * So if we see such attributes in either old or the new tuple, we
+			 * instead disable WARM. Now this is not full proof because the new
+			 * tuple may not have been toasted when we ran HeapCheckColumns. So
+			 * if the UPDATE is changing previously untoasted attributes to
+			 * toasted attributes, it will skip the check and we will end up
+			 * doing a WARM update. The recheck logic should be prepared to
+			 * handle toasted and compressed values from the heap.
+			 */
+			if (RelationWarmUpdatesEnabled(relation) &&
+				relation->rd_supportswarm &&
+				!HeapTupleIsWarmUpdated(&oldtup) &&
+				!bms_overlap(modified_attrs, exprindx_attrs) &&
+				!bms_is_subset(hot_attrs, modified_attrs) &&
+				!bms_overlap(notready_attrs, modified_attrs) &&
+				!bms_overlap(toasted_attrs, hot_attrs) &&
+				!bms_overlap(compressed_attrs, hot_attrs))
+			{
+				int num_indexes, num_updating_indexes;
+				ListCell *l;
+
+				/*
+				 * Everything else is Ok. Now check if the update will require
+				 * less than or equal to 50% index updates. Anything above
+				 * that, we can just do a regular update and save on WARM
+				 * cleanup cost.
+				 */
+				num_indexes = list_length(indexattrsList);
+				num_updating_indexes = 0;
+				foreach (l, indexattrsList)
+				{
+					Bitmapset  *b = (Bitmapset *) lfirst(l);
+					if (bms_overlap(b, modified_attrs))
+						num_updating_indexes++;
+				}
+
+				if ((double)num_updating_indexes/num_indexes <= 0.5)
+					use_warm_update = true;
+			}
+		}
 	}
 	else
 	{
@@ -4273,6 +4634,32 @@ l2:
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
 		HeapTupleSetHeapOnly(newtup);
+
+		/*
+		 * Even if we are doing a HOT update, we must carry forward the WARM
+		 * flag because we may have already inserted another index entry
+		 * pointing to our root and a third entry may create duplicates.
+		 *
+		 * Note: If we ever have a mechanism to avoid duplicate <key, TID> in
+		 * indexes, we could look at relaxing this restriction and allow even
+		 * more WARM udpates.
+		 */
+		if (HeapTupleIsWarmUpdated(&oldtup))
+		{
+			HeapTupleSetWarmUpdated(heaptup);
+			HeapTupleSetWarmUpdated(newtup);
+		}
+
+		/*
+		 * If the old tuple is a WARM tuple then mark the new tuple as a WARM
+		 * tuple as well.
+		 */
+		if (HeapTupleIsWarm(&oldtup))
+		{
+			HeapTupleSetWarm(heaptup);
+			HeapTupleSetWarm(newtup);
+		}
+
 		/*
 		 * For HOT (or WARM) updated tuples, we store the offset of the root
 		 * line pointer of this chain in the ip_posid field of the new tuple.
@@ -4285,12 +4672,45 @@ l2:
 		if (HeapTupleHeaderHasRootOffset(oldtup.t_data))
 			root_offnum = HeapTupleHeaderGetRootOffset(oldtup.t_data);
 	}
+	else if (use_warm_update)
+	{
+		/* Mark the old tuple as HOT-updated */
+		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetWarmUpdated(&oldtup);
+
+		/* And mark the new tuple as heap-only */
+		HeapTupleSetHeapOnly(heaptup);
+		/* Mark the new tuple as WARM tuple */
+		HeapTupleSetWarmUpdated(heaptup);
+		/* This update also starts the WARM chain */
+		HeapTupleSetWarm(heaptup);
+		Assert(!HeapTupleIsWarm(&oldtup));
+
+		/* Mark the caller's copy too, in case different from heaptup */
+		HeapTupleSetHeapOnly(newtup);
+		HeapTupleSetWarmUpdated(newtup);
+		HeapTupleSetWarm(newtup);
+
+		if (HeapTupleHeaderHasRootOffset(oldtup.t_data))
+			root_offnum = HeapTupleHeaderGetRootOffset(oldtup.t_data);
+		else
+			root_offnum = heap_get_root_tuple(page,
+					ItemPointerGetOffsetNumber(&(oldtup.t_self)));
+
+		/* Let the caller know we did a WARM update */
+		if (warm_update)
+			*warm_update = true;
+	}
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
 		HeapTupleClearHotUpdated(&oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
+		HeapTupleClearWarmUpdated(heaptup);
+		HeapTupleClearWarmUpdated(newtup);
+		HeapTupleClearWarm(heaptup);
+		HeapTupleClearWarm(newtup);
 		root_offnum = InvalidOffsetNumber;
 	}
 
@@ -4309,7 +4729,9 @@ l2:
 	HeapTupleHeaderSetHeapLatest(newtup->t_data, root_offnum);
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
-	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	oldtup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	if (HeapTupleHeaderIsMoved(oldtup.t_data))
+		oldtup.t_data->t_infomask &= ~HEAP_MOVED;
 	oldtup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 	/* ... and store info about transaction updating this tuple */
 	Assert(TransactionIdIsValid(xmax_old_tuple));
@@ -4400,7 +4822,10 @@ l2:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 
-	pgstat_count_heap_update(relation, use_hot_update);
+	/*
+	 * Count HOT and WARM updates separately
+	 */
+	pgstat_count_heap_update(relation, use_hot_update, use_warm_update);
 
 	/*
 	 * If heaptup is a private copy, release it.  Don't forget to copy t_self
@@ -4420,17 +4845,33 @@ l2:
 	bms_free(id_attrs);
 	bms_free(modified_attrs);
 	bms_free(interesting_attrs);
+	bms_free(exprindx_attrs);
+	bms_free(notready_attrs);
 
 	return HeapTupleMayBeUpdated;
 }
 
 /*
- * Check if the specified attribute's value is same in both given tuples.
- * Subroutine for HeapDetermineModifiedColumns.
+ * Check if the specified attribute is toasted or compressed in either
+ * the old or the new tuple. For compressed or toasted attributes, we only do a
+ * very simplistic check for the equality by running datumIsEqual on the
+ * compressed or toasted form. This helps us to perform HOT updates (if other
+ * conditions are favourable) when these attributes are not updated. But we
+ * might not be able to capture all possible scenarios such as when the
+ * toasted/compressed attribute is updated, but the modified value is same as
+ * the original value.
+ *
+ * For WARM updates, we don't care what the equality check returns for
+ * toasted/compressed attributes. If such attributes are used in any of the
+ * indexes, we don't perform WARM updates irrespective of whether they are
+ * modified or not.
+ *
+ * Subroutine for HeapCheckColumns.
  */
-static bool
-heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
-					   HeapTuple tup1, HeapTuple tup2)
+static void
+heap_tuple_attr_check(TupleDesc tupdesc, int attrnum,
+					   HeapTuple tup1, HeapTuple tup2,
+					   bool *toasted, bool *compressed, bool *equal)
 {
 	Datum		value1,
 				value2;
@@ -4438,13 +4879,24 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
 				isnull2;
 	Form_pg_attribute att;
 
+	*equal = true;
+	*toasted = *compressed = false;
+
 	/*
 	 * If it's a whole-tuple reference, say "not equal".  It's not really
 	 * worth supporting this case, since it could only succeed after a no-op
 	 * update, which is hardly a case worth optimizing for.
+	 *
+	 * XXX Does thie need special attention in WARM given that we don't want to
+	 * return "not equal" for something that is equal? But how does whole-tuple
+	 * reference ends up in the interesting_attrs list? Regression tests do not
+	 * have covergae for this case as of now.
 	 */
 	if (attrnum == 0)
-		return false;
+	{
+		*equal = false;
+		return;
+	}
 
 	/*
 	 * Likewise, automatically say "not equal" for any system attribute other
@@ -4455,12 +4907,16 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
 	{
 		if (attrnum != ObjectIdAttributeNumber &&
 			attrnum != TableOidAttributeNumber)
-			return false;
+		{
+			*equal = false;
+			/* these attributes can neither be toasted not compressed */
+			return;
+		}
 	}
 
 	/*
 	 * Extract the corresponding values.  XXX this is pretty inefficient if
-	 * there are many indexed columns.  Should HeapDetermineModifiedColumns do
+	 * there are many indexed columns.  Should HeapCheckColumns do
 	 * a single heap_deform_tuple call on each tuple, instead?	But that
 	 * doesn't work for system columns ...
 	 */
@@ -4468,17 +4924,32 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
 	value2 = heap_getattr(tup2, attrnum, tupdesc, &isnull2);
 
 	/*
+	 * If both are NULL, they can be considered equal.
+	 */
+	if (isnull1 && isnull2)
+	{
+		*equal = true;
+		*toasted = *compressed = false;
+		return;
+	}
+
+	/*
 	 * If one value is NULL and other is not, then they are certainly not
 	 * equal
 	 */
 	if (isnull1 != isnull2)
-		return false;
+		*equal = false;
 
-	/*
-	 * If both are NULL, they can be considered equal.
-	 */
-	if (isnull1)
-		return true;
+	/* attrnum == 0 is already handled above */
+	if ((attrnum < 0) && (*equal))
+	{
+		/*
+		 * The only allowed system columns are OIDs, so do this. OIDs can never
+		 * be compressed or toasted.
+		 */
+		*equal = (DatumGetObjectId(value1) == DatumGetObjectId(value2));
+		return;
+	}
 
 	/*
 	 * We do simple binary comparison of the two datums.  This may be overly
@@ -4489,46 +4960,90 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
 	 * classes; furthermore, we cannot safely invoke user-defined functions
 	 * while holding exclusive buffer lock.
 	 */
-	if (attrnum <= 0)
+	Assert(attrnum <= tupdesc->natts);
+	att = tupdesc->attrs[attrnum - 1];
+
+	/*
+	 * If either the old or the new value is toasted, consider the attribute as
+	 * toasted. We don't check if the value is a NULL value.
+	 */
+	if ((att->attlen == -1) && !isnull1 &&
+		(VARATT_IS_EXTERNAL(value1) || VARATT_IS_EXTERNAL(value2)))
 	{
-		/* The only allowed system columns are OIDs, so do this */
-		return (DatumGetObjectId(value1) == DatumGetObjectId(value2));
+		*toasted = true;
 	}
-	else
+
+	/*
+	 * If either the old or the new value is compressed, consider the attribute
+	 * as compressed. We don't check if the value is a NULL value.
+	 */
+	if ((att->attlen == -1) && !isnull2 &&
+		(VARATT_IS_COMPRESSED(value1) || VARATT_IS_COMPRESSED(value2)))
+	{
+		*compressed = true;
+	}
+
+	/*
+	 * Check for equality but only if we haven't already determined above that
+	 * they are not equal. This can happen either because one of the attributes
+	 * is NULL.
+	 */
+	if (*equal)
 	{
-		Assert(attrnum <= tupdesc->natts);
-		att = tupdesc->attrs[attrnum - 1];
-		return datumIsEqual(value1, value2, att->attbyval, att->attlen);
+		*equal = datumIsEqual(value1, value2, att->attbyval, att->attlen);
 	}
 }
 
 /*
- * Check which columns are being updated.
+ * Check the old tuple and the new tuple for the given list of
+ * interesting_cols.
+ *
+ * Given an updated tuple, check if any of the interesting_cols are toasted or
+ * compressed, either in the old or the new tuple. Such columns are returned in
+ * the toasted_attrs and compressed_attrs respectively.
  *
- * Given an updated tuple, determine (and return into the output bitmapset),
- * from those listed as interesting, the set of columns that changed.
+ * Also check which columns are being changed in the update operation. For
+ * toasted/compressed columns, we only do a simple memcmp-based check without
+ * detoasing/decompressing the values. This implies that we might not be able
+ * to capture all cases where two values are equal.
  *
  * The input bitmapset is destructively modified; that is OK since this is
  * invoked at most once in heap_update.
  */
-static Bitmapset *
-HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols,
-							 HeapTuple oldtup, HeapTuple newtup)
+void
+HeapCheckColumns(Relation relation, Bitmapset *interesting_cols,
+							 HeapTuple oldtup, HeapTuple newtup,
+							 Bitmapset **toasted_attrs,
+							 Bitmapset **compressed_attrs,
+							 Bitmapset **modified_attrs)
 {
 	int		attnum;
-	Bitmapset *modified = NULL;
+
+	*toasted_attrs = NULL;
+	*compressed_attrs = NULL;
+	*modified_attrs = NULL;
 
 	while ((attnum = bms_first_member(interesting_cols)) >= 0)
 	{
+		bool equal, compressed, toasted;
+
 		attnum += FirstLowInvalidHeapAttributeNumber;
 
-		if (!heap_tuple_attr_equals(RelationGetDescr(relation),
-								   attnum, oldtup, newtup))
-			modified = bms_add_member(modified,
+		heap_tuple_attr_check(RelationGetDescr(relation),
+								   attnum, oldtup, newtup, &toasted,
+								   &compressed, &equal);
+		if (!equal)
+			*modified_attrs = bms_add_member(*modified_attrs,
+									  attnum - FirstLowInvalidHeapAttributeNumber);
+		if (toasted)
+			*toasted_attrs = bms_add_member(*toasted_attrs,
+									  attnum - FirstLowInvalidHeapAttributeNumber);
+		if (compressed)
+			*compressed_attrs = bms_add_member(*compressed_attrs,
 									  attnum - FirstLowInvalidHeapAttributeNumber);
 	}
 
-	return modified;
+	return;
 }
 
 /*
@@ -4540,7 +5055,8 @@ HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols,
  * via ereport().
  */
 void
-simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
+simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
+		Bitmapset **modified_attrs, bool *warm_update)
 {
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
@@ -4549,7 +5065,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &hufd, &lockmode);
+						 &hufd, &lockmode, modified_attrs, warm_update);
 	switch (result)
 	{
 		case HeapTupleSelfUpdated:
@@ -4640,7 +5156,6 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
 	BlockNumber	block;
-	OffsetNumber	offnum;
 	TransactionId xid,
 				xmax;
 	uint16		old_infomask,
@@ -4649,11 +5164,10 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
-	OffsetNumber	root_offnum;
+	OffsetNumber	root_offnum = InvalidOffsetNumber;
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
-	offnum = ItemPointerGetOffsetNumber(tid);
 
 	/*
 	 * Before locking the buffer, pin the visibility map page if it appears to
@@ -5745,7 +6259,6 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 	bool		cleared_all_frozen = false;
 	Buffer		vmbuffer = InvalidBuffer;
 	BlockNumber block;
-	OffsetNumber offnum;
 
 	ItemPointerCopy(tid, &tupid);
 
@@ -5754,7 +6267,6 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 		new_infomask = 0;
 		new_xmax = InvalidTransactionId;
 		block = ItemPointerGetBlockNumber(&tupid);
-		offnum = ItemPointerGetOffsetNumber(&tupid);
 
 		ItemPointerCopy(&tupid, &(mytup.t_self));
 
@@ -6226,7 +6738,9 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 	PageSetPrunable(page, RecentGlobalXmin);
 
 	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	if (HeapTupleHeaderIsMoved(tp.t_data))
+		tp.t_data->t_infomask &= ~HEAP_MOVED;
 	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 
 	/*
@@ -6800,7 +7314,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
 	 * Old-style VACUUM FULL is gone, but we have to keep this code as long as
 	 * we support having MOVED_OFF/MOVED_IN tuples in the database.
 	 */
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderIsMoved(tuple))
 	{
 		xid = HeapTupleHeaderGetXvac(tuple);
 
@@ -6819,7 +7333,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
 			 * have failed; whereas a non-dead MOVED_IN tuple must mean the
 			 * xvac transaction succeeded.
 			 */
-			if (tuple->t_infomask & HEAP_MOVED_OFF)
+			if (HeapTupleHeaderIsMovedOff(tuple))
 				frz->frzflags |= XLH_INVALID_XVAC;
 			else
 				frz->frzflags |= XLH_FREEZE_XVAC;
@@ -7289,7 +7803,7 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
 			return true;
 	}
 
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderIsMoved(tuple))
 	{
 		xid = HeapTupleHeaderGetXvac(tuple);
 		if (TransactionIdIsNormal(xid))
@@ -7372,7 +7886,7 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 			return true;
 	}
 
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderIsMoved(tuple))
 	{
 		xid = HeapTupleHeaderGetXvac(tuple);
 		if (TransactionIdIsNormal(xid) &&
@@ -7398,7 +7912,7 @@ HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
 	TransactionId xmax = HeapTupleHeaderGetUpdateXid(tuple);
 	TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
-	if (tuple->t_infomask & HEAP_MOVED)
+	if (HeapTupleHeaderIsMoved(tuple))
 	{
 		if (TransactionIdPrecedes(*latestRemovedXid, xvac))
 			*latestRemovedXid = xvac;
@@ -7447,6 +7961,36 @@ log_heap_cleanup_info(RelFileNode rnode, TransactionId latestRemovedXid)
 }
 
 /*
+ * Perform XLogInsert for a heap-warm-clear operation.  Caller must already
+ * have modified the buffer and marked it dirty.
+ */
+XLogRecPtr
+log_heap_warmclear(Relation reln, Buffer buffer,
+			   OffsetNumber *cleared, int ncleared)
+{
+	xl_heap_warmclear	xlrec;
+	XLogRecPtr			recptr;
+
+	/* Caller should not call me on a non-WAL-logged relation */
+	Assert(RelationNeedsWAL(reln));
+
+	xlrec.ncleared = ncleared;
+
+	XLogBeginInsert();
+	XLogRegisterData((char *) &xlrec, SizeOfHeapWarmClear);
+
+	XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+	if (ncleared > 0)
+		XLogRegisterBufData(0, (char *) cleared,
+							ncleared * sizeof(OffsetNumber));
+
+	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_WARMCLEAR);
+
+	return recptr;
+}
+
+/*
  * Perform XLogInsert for a heap-clean operation.  Caller must already
  * have modified the buffer and marked it dirty.
  *
@@ -7601,6 +8145,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	bool		need_tuple_data = RelationIsLogicallyLogged(reln);
 	bool		init;
 	int			bufflags;
+	bool		warm_update = false;
 
 	/* Caller should not call me on a non-WAL-logged relation */
 	Assert(RelationNeedsWAL(reln));
@@ -7612,6 +8157,9 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	else
 		info = XLOG_HEAP_UPDATE;
 
+	if (HeapTupleIsWarmUpdated(newtup))
+		warm_update = true;
+
 	/*
 	 * If the old and new tuple are on the same page, we only need to log the
 	 * parts of the new tuple that were changed.  That saves on the amount of
@@ -7685,6 +8233,8 @@ log_heap_update(Relation reln, Buffer oldbuf,
 				xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_KEY;
 		}
 	}
+	if (warm_update)
+		xlrec.flags |= XLH_UPDATE_WARM_UPDATE;
 
 	/* If new tuple is the single and first tuple on page... */
 	if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
@@ -8099,6 +8649,60 @@ heap_xlog_clean(XLogReaderState *record)
 		XLogRecordPageWithFreeSpace(rnode, blkno, freespace);
 }
 
+
+/*
+ * Handles HEAP2_WARMCLEAR record type
+ */
+static void
+heap_xlog_warmclear(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	xl_heap_warmclear	*xlrec = (xl_heap_warmclear *) XLogRecGetData(record);
+	Buffer		buffer;
+	RelFileNode rnode;
+	BlockNumber blkno;
+	XLogRedoAction action;
+
+	XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno);
+
+	/*
+	 * If we have a full-page image, restore it (using a cleanup lock) and
+	 * we're done.
+	 */
+	action = XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true,
+										   &buffer);
+	if (action == BLK_NEEDS_REDO)
+	{
+		Page		page = (Page) BufferGetPage(buffer);
+		OffsetNumber *cleared;
+		int			ncleared;
+		Size		datalen;
+		int			i;
+
+		cleared = (OffsetNumber *) XLogRecGetBlockData(record, 0, &datalen);
+
+		ncleared = xlrec->ncleared;
+
+		for (i = 0; i < ncleared; i++)
+		{
+			ItemId			lp;
+			OffsetNumber	offnum = cleared[i];
+			HeapTupleData	heapTuple;
+
+			lp = PageGetItemId(page, offnum);
+			heapTuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+
+			HeapTupleHeaderClearWarmUpdated(heapTuple.t_data);
+			HeapTupleHeaderClearWarm(heapTuple.t_data);
+		}
+
+		PageSetLSN(page, lsn);
+		MarkBufferDirty(buffer);
+	}
+	if (BufferIsValid(buffer))
+		UnlockReleaseBuffer(buffer);
+}
+
 /*
  * Replay XLOG_HEAP2_VISIBLE record.
  *
@@ -8345,7 +8949,9 @@ heap_xlog_delete(XLogReaderState *record)
 
 		htup = (HeapTupleHeader) PageGetItem(page, lp);
 
-		htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		htup->t_infomask &= ~HEAP_XMAX_BITS;
+		if (HeapTupleHeaderIsMoved(htup))
+			htup->t_infomask &= ~HEAP_MOVED;
 		htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		HeapTupleHeaderClearHotUpdated(htup);
 		fix_infomask_from_infobits(xlrec->infobits_set,
@@ -8366,7 +8972,7 @@ heap_xlog_delete(XLogReaderState *record)
 		if (!HeapTupleHeaderHasRootOffset(htup))
 		{
 			OffsetNumber	root_offnum;
-			root_offnum = heap_get_root_tuple(page, xlrec->offnum); 
+			root_offnum = heap_get_root_tuple(page, xlrec->offnum);
 			HeapTupleHeaderSetHeapLatest(htup, root_offnum);
 		}
 
@@ -8662,16 +9268,22 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 	Size		freespace = 0;
 	XLogRedoAction oldaction;
 	XLogRedoAction newaction;
+	bool		warm_update = false;
 
 	/* initialize to keep the compiler quiet */
 	oldtup.t_data = NULL;
 	oldtup.t_len = 0;
 
+	if (xlrec->flags & XLH_UPDATE_WARM_UPDATE)
+		warm_update = true;
+
 	XLogRecGetBlockTag(record, 0, &rnode, NULL, &newblk);
 	if (XLogRecGetBlockTag(record, 1, NULL, NULL, &oldblk))
 	{
 		/* HOT updates are never done across pages */
 		Assert(!hot_update);
+		/* WARM updates are never done across pages */
+		Assert(!warm_update);
 	}
 	else
 		oldblk = newblk;
@@ -8731,6 +9343,11 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 								   &htup->t_infomask2);
 		HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
 		HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
+
+		/* Mark the old tuple has a WARM tuple */
+		if (warm_update)
+			HeapTupleHeaderSetWarmUpdated(htup);
+
 		/* Set forward chain link in t_ctid */
 		HeapTupleHeaderSetNextTid(htup, &newtid);
 
@@ -8866,6 +9483,10 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 		HeapTupleHeaderSetCmin(htup, FirstCommandId);
 		HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
 
+		/* Mark the new tuple has a WARM tuple */
+		if (warm_update)
+			HeapTupleHeaderSetWarmUpdated(htup);
+
 		offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
 		if (offnum == InvalidOffsetNumber)
 			elog(PANIC, "failed to add tuple");
@@ -8993,7 +9614,9 @@ heap_xlog_lock(XLogReaderState *record)
 
 		htup = (HeapTupleHeader) PageGetItem(page, lp);
 
-		htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		htup->t_infomask &= ~HEAP_XMAX_BITS;
+		if (HeapTupleHeaderIsMoved(htup))
+			htup->t_infomask &= ~HEAP_MOVED;
 		htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		fix_infomask_from_infobits(xlrec->infobits_set, &htup->t_infomask,
 								   &htup->t_infomask2);
@@ -9072,7 +9695,9 @@ heap_xlog_lock_updated(XLogReaderState *record)
 
 		htup = (HeapTupleHeader) PageGetItem(page, lp);
 
-		htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+		htup->t_infomask &= ~HEAP_XMAX_BITS;
+		if (HeapTupleHeaderIsMoved(htup))
+			htup->t_infomask &= ~HEAP_MOVED;
 		htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		fix_infomask_from_infobits(xlrec->infobits_set, &htup->t_infomask,
 								   &htup->t_infomask2);
@@ -9141,6 +9766,9 @@ heap_redo(XLogReaderState *record)
 		case XLOG_HEAP_INSERT:
 			heap_xlog_insert(record);
 			break;
+		case XLOG_HEAP_MULTI_INSERT:
+			heap_xlog_multi_insert(record);
+			break;
 		case XLOG_HEAP_DELETE:
 			heap_xlog_delete(record);
 			break;
@@ -9169,7 +9797,7 @@ heap2_redo(XLogReaderState *record)
 {
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
 
-	switch (info & XLOG_HEAP_OPMASK)
+	switch (info & XLOG_HEAP2_OPMASK)
 	{
 		case XLOG_HEAP2_CLEAN:
 			heap_xlog_clean(record);
@@ -9183,9 +9811,6 @@ heap2_redo(XLogReaderState *record)
 		case XLOG_HEAP2_VISIBLE:
 			heap_xlog_visible(record);
 			break;
-		case XLOG_HEAP2_MULTI_INSERT:
-			heap_xlog_multi_insert(record);
-			break;
 		case XLOG_HEAP2_LOCK_UPDATED:
 			heap_xlog_lock_updated(record);
 			break;
@@ -9199,6 +9824,9 @@ heap2_redo(XLogReaderState *record)
 		case XLOG_HEAP2_REWRITE:
 			heap_xlog_logical_rewrite(record);
 			break;
+		case XLOG_HEAP2_WARMCLEAR:
+			heap_xlog_warmclear(record);
+			break;
 		default:
 			elog(PANIC, "heap2_redo: unknown op code %u", info);
 	}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index f54337c..6a3baff 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -834,6 +834,13 @@ heap_get_root_tuples_internal(Page page, OffsetNumber target_offnum,
 			if (!HeapTupleHeaderIsHotUpdated(htup))
 				continue;
 
+			/*
+			 * If the tuple has root line pointer, it must be the end of the
+			 * chain
+			 */
+			if (HeapTupleHeaderHasRootOffset(htup))
+				break;
+
 			/* Set up to scan the HOT-chain */
 			nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
 			priorXmax = HeapTupleHeaderGetUpdateXid(htup);
@@ -928,6 +935,6 @@ heap_get_root_tuple(Page page, OffsetNumber target_offnum)
 void
 heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
 {
-	return heap_get_root_tuples_internal(page, InvalidOffsetNumber,
+	heap_get_root_tuples_internal(page, InvalidOffsetNumber,
 			root_offsets);
 }
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2d3ae9b..bd469ee 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -404,6 +404,14 @@ rewrite_heap_tuple(RewriteState state,
 		old_tuple->t_data->t_infomask & HEAP_XACT_MASK;
 
 	/*
+	 * We must clear the HEAP_WARM_TUPLE flag if the HEAP_WARM_UPDATED is
+	 * cleared above.
+	 */
+	if (HeapTupleHeaderIsWarmUpdated(old_tuple->t_data))
+		HeapTupleHeaderClearWarm(new_tuple->t_data);
+
+
+	/*
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
@@ -428,7 +436,7 @@ rewrite_heap_tuple(RewriteState state,
 		memset(&hashkey, 0, sizeof(hashkey));
 		hashkey.xmin = HeapTupleHeaderGetUpdateXid(old_tuple->t_data);
 
-		/* 
+		/*
 		 * We've already checked that this is not the last tuple in the chain,
 		 * so fetch the next TID in the chain.
 		 */
@@ -737,7 +745,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 		newitemid = PageGetItemId(page, newoff);
 		onpage_tup = (HeapTupleHeader) PageGetItem(page, newitemid);
 
-		/* 
+		/*
 		 * Set t_ctid just to ensure that block number is copied correctly, but
 		 * then immediately mark the tuple as the latest.
 		 */
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index aa5a45d..bab48fd 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -1688,7 +1688,8 @@ toast_save_datum(Relation rel, Datum value,
 							 toastrel,
 							 toastidxs[i]->rd_index->indisunique ?
 							 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
-							 NULL);
+							 NULL,
+							 false);
 		}
 
 		/*
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index a91fda7..c650be4 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -115,6 +115,8 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
 	scan->xactStartedInRecovery = TransactionStartedDuringRecovery();
 	scan->ignore_killed_tuples = !scan->xactStartedInRecovery;
 
+	scan->warm_prior_tuple = false;
+
 	scan->opaque = NULL;
 
 	scan->xs_itup = NULL;
@@ -127,6 +129,8 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
 	scan->xs_cbuf = InvalidBuffer;
 	scan->xs_continue_hot = false;
 
+	scan->indexInfo = NULL;
+
 	return scan;
 }
 
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index cc5ac8b..d655d60 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -197,7 +197,8 @@ index_insert(Relation indexRelation,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
 			 IndexUniqueCheck checkUnique,
-			 IndexInfo *indexInfo)
+			 IndexInfo *indexInfo,
+			 bool warm_update)
 {
 	RELATION_CHECKS;
 	CHECK_REL_PROCEDURE(aminsert);
@@ -207,6 +208,12 @@ index_insert(Relation indexRelation,
 									   (HeapTuple) NULL,
 									   InvalidBuffer);
 
+	if (warm_update)
+	{
+		Assert(indexRelation->rd_amroutine->amwarminsert != NULL);
+		return indexRelation->rd_amroutine->amwarminsert(indexRelation, values,
+				isnull, heap_t_ctid, heapRelation, checkUnique, indexInfo);
+	}
 	return indexRelation->rd_amroutine->aminsert(indexRelation, values, isnull,
 												 heap_t_ctid, heapRelation,
 												 checkUnique, indexInfo);
@@ -291,6 +298,25 @@ index_beginscan_internal(Relation indexRelation,
 	scan->parallel_scan = pscan;
 	scan->xs_temp_snap = temp_snap;
 
+	/*
+	 * If the index supports recheck, make sure that index tuple is saved
+	 * during index scans. Also build and cache IndexInfo which is used by
+	 * amrecheck routine.
+	 *
+	 * XXX Ideally, we should look at all indexes on the table and check if
+	 * WARM is at all supported on the base table. If WARM is not supported
+	 * then we don't need to do any recheck. RelationGetIndexAttrBitmap() does
+	 * do that and sets rd_supportswarm after looking at all indexes. But we
+	 * don't know if the function was called earlier in the session when we're
+	 * here. We can't call it now because there exists a risk of causing
+	 * deadlock.
+	 */
+	if (indexRelation->rd_amroutine->amrecheck)
+	{
+		scan->xs_want_itup = true;
+		scan->indexInfo = BuildIndexInfo(indexRelation);
+	}
+
 	return scan;
 }
 
@@ -327,6 +353,7 @@ index_rescan(IndexScanDesc scan,
 	scan->xs_continue_hot = false;
 
 	scan->kill_prior_tuple = false;		/* for safety */
+	scan->warm_prior_tuple = false;		/* for safety */
 
 	scan->indexRelation->rd_amroutine->amrescan(scan, keys, nkeys,
 												orderbys, norderbys);
@@ -358,6 +385,10 @@ index_endscan(IndexScanDesc scan)
 	if (scan->xs_temp_snap)
 		UnregisterSnapshot(scan->xs_snapshot);
 
+	/* Free cached IndexInfo, if any */
+	if (scan->indexInfo)
+		pfree(scan->indexInfo);
+
 	/* Release the scan data structure itself */
 	IndexScanEnd(scan);
 }
@@ -402,6 +433,7 @@ index_restrpos(IndexScanDesc scan)
 	scan->xs_continue_hot = false;
 
 	scan->kill_prior_tuple = false;		/* for safety */
+	scan->warm_prior_tuple = false;		/* for safety */
 
 	scan->indexRelation->rd_amroutine->amrestrpos(scan);
 }
@@ -535,13 +567,14 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
 	/*
 	 * The AM's amgettuple proc finds the next index entry matching the scan
 	 * keys, and puts the TID into scan->xs_ctup.t_self.  It should also set
-	 * scan->xs_recheck and possibly scan->xs_itup/scan->xs_hitup, though we
+	 * scan->xs_tuple_recheck and possibly scan->xs_itup/scan->xs_hitup, though we
 	 * pay no attention to those fields here.
 	 */
 	found = scan->indexRelation->rd_amroutine->amgettuple(scan, direction);
 
-	/* Reset kill flag immediately for safety */
+	/* Reset kill/warm flags immediately for safety */
 	scan->kill_prior_tuple = false;
+	scan->warm_prior_tuple = false;
 
 	/* If we're out of index entries, we're done */
 	if (!found)
@@ -574,7 +607,7 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
  * dropped in a future index_getnext_tid, index_fetch_heap or index_endscan
  * call).
  *
- * Note: caller must check scan->xs_recheck, and perform rechecking of the
+ * Note: caller must check scan->xs_tuple_recheck, and perform rechecking of the
  * scan keys if required.  We do not do that here because we don't have
  * enough information to do it efficiently in the general case.
  * ----------------
@@ -585,6 +618,8 @@ index_fetch_heap(IndexScanDesc scan)
 	ItemPointer tid = &scan->xs_ctup.t_self;
 	bool		all_dead = false;
 	bool		got_heap_tuple;
+	HeapTuple	heaptup = NULL;
+
 
 	/* We can skip the buffer-switching logic if we're in mid-HOT chain. */
 	if (!scan->xs_continue_hot)
@@ -605,37 +640,171 @@ index_fetch_heap(IndexScanDesc scan)
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+
+	/*
+	 * Fetch the status of the WARM chain just once per HOT chain. We cache the
+	 * result in xs_hot_chain_status when the HOT chain is searched for the
+	 * first time.
+	 */
 	got_heap_tuple = heap_hot_search_buffer(tid, scan->heapRelation,
 											scan->xs_cbuf,
 											scan->xs_snapshot,
 											&scan->xs_ctup,
 											&all_dead,
-											!scan->xs_continue_hot);
-	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+											!scan->xs_continue_hot,
+											scan->xs_continue_hot ? NULL : &scan->xs_hot_chain_status);
 
 	if (got_heap_tuple)
 	{
+		bool res = true;
+		bool tuple_recheck = true;
+		bool is_warm;
+
+		/*
+		 * Ok we got a tuple which satisfies the snapshot, but if its part of a
+		 * WARM chain, we must do additional checks to ensure that we are
+		 * indeed returning a correct tuple. Note that if the index AM does not
+		 * implement amrecheck method, then we don't any additional checks
+		 * since WARM must have been disabled on such tables.
+		 */
+		if (scan->xs_itup && scan->indexRelation->rd_amroutine->amrecheck)
+		{
+			is_warm = scan->indexRelation->rd_amroutine->amiswarm(scan->indexRelation,
+						scan->xs_itup);
+
+			/*
+			 * If the chain has only WARM tuples then a WARM index pointer must
+			 * satisfy all tuples in the chain. So we need not do any recheck,
+			 * but but res to true. On the other hand a WARM pointer to a CLEAR
+			 * chain would not satisfy any tuple in the chain. So we just set
+			 * res to false and avoid a recheck.
+			 *
+			 * If the chain has only CLEAR tuples then a WARM index pointer
+			 * must not satisfy any tuple from the chain. A WARM pointer to a
+			 * CLEAR chain can only occur because of an aborted WARM update. We
+			 * can kill such WARM pointers immediately.
+			 */
+			if (HCWC_IS_ALL_WARM(scan->xs_hot_chain_status))
+			{
+				if (is_warm)
+				{
+					tuple_recheck = false;
+					res = true;
+				}
+			}
+			else if (HCWC_IS_ALL_CLEAR(scan->xs_hot_chain_status))
+			{
+				if (is_warm)
+				{
+					tuple_recheck = false;
+					res = false;
+					scan->kill_prior_tuple = true;
+				}
+			}
+		}
+		else
+		{
+			/*
+			 * We can't do and should not do recheck if the index tuple is not
+			 * available or when the index AM does not implement a recheck
+			 * method.
+			 */
+			tuple_recheck = false;
+		}
+
+		/*
+		 * Ok. We must recheck to decide whether to return a tuple from the
+		 * current index pointer.
+		 *
+		 * XXX If the heap tuple has toasted data, we should get a copy of the
+		 * tuple, drop the buffer lock and then work with the copy. Otherwise
+		 * we might risk getting into a deadlock.
+		 *
+		 * XXX In theory, we don't allow WARM updates when either old and new
+		 * tuple has toasted attributes. But if we ever hit a situation where
+		 * we are presented with a heap tuple with toasted values, recheck
+		 * should be able to handle that.
+		 */
+		if (tuple_recheck)
+		{
+			if (HeapTupleHasExternal(&scan->xs_ctup))
+			{
+				heaptup = heap_copytuple(&scan->xs_ctup);
+				LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+			}
+			else
+				heaptup = &scan->xs_ctup;
+
+			Assert(RelationWarmUpdatesEnabled(scan->heapRelation));
+			res = scan->indexRelation->rd_amroutine->amrecheck(
+						scan->indexRelation,
+						scan->indexInfo,
+						scan->xs_itup,
+						scan->heapRelation,
+						heaptup);
+
+			/*
+			 * If it's a CLEAR pointer to a chain with only WARM tuples then it
+			 * could be the only index pointer pointing to this chain or it
+			 * could be a duplicate CLEAR pointer resulted from an aborted
+			 * vacuum. We consult the recheck result and either kill the
+			 * pointer or mark it WARM to match the state of the chain. This
+			 * avoid repeated evaluation of recheck when the index is
+			 * repeatedly used to query the table.
+			 */
+			if (!is_warm && HCWC_IS_ALL_WARM(scan->xs_hot_chain_status))
+			{
+				if (!res)
+					scan->kill_prior_tuple = true;
+				else
+					scan->warm_prior_tuple = true;
+			}
+
+			/*
+			 * XXX Can we have a CLEAR pointer to a CLEAR chain and still not
+			 * see any tuple from the chain? Should we just Assert that res
+			 * must always be true?
+			 */
+			if (!is_warm && HCWC_IS_ALL_CLEAR(scan->xs_hot_chain_status))
+			{
+				Assert(res);
+				if (!res)
+					scan->kill_prior_tuple = true;
+			}
+		}
+
+		if (heaptup && heaptup != &scan->xs_ctup)
+			heap_freetuple(heaptup);
+		else
+			LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+
 		/*
 		 * Only in a non-MVCC snapshot can more than one member of the HOT
 		 * chain be visible.
 		 */
 		scan->xs_continue_hot = !IsMVCCSnapshot(scan->xs_snapshot);
 		pgstat_count_heap_fetch(scan->indexRelation);
-		return &scan->xs_ctup;
+
+		if (res)
+			return &scan->xs_ctup;
 	}
+	else
+	{
+		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 
-	/* We've reached the end of the HOT chain. */
-	scan->xs_continue_hot = false;
+		/* We've reached the end of the HOT chain. */
+		scan->xs_continue_hot = false;
 
-	/*
-	 * If we scanned a whole HOT chain and found only dead tuples, tell index
-	 * AM to kill its entry for that TID (this will take effect in the next
-	 * amgettuple call, in index_getnext_tid).  We do not do this when in
-	 * recovery because it may violate MVCC to do so.  See comments in
-	 * RelationGetIndexScan().
-	 */
-	if (!scan->xactStartedInRecovery)
-		scan->kill_prior_tuple = all_dead;
+		/*
+		 * If we scanned a whole HOT chain and found only dead tuples, tell index
+		 * AM to kill its entry for that TID (this will take effect in the next
+		 * amgettuple call, in index_getnext_tid).  We do not do this when in
+		 * recovery because it may violate MVCC to do so.  See comments in
+		 * RelationGetIndexScan().
+		 */
+		if (!scan->xactStartedInRecovery)
+			scan->kill_prior_tuple = all_dead;
+	}
 
 	return NULL;
 }
@@ -719,6 +888,7 @@ index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap)
 
 	/* just make sure this is false... */
 	scan->kill_prior_tuple = false;
+	scan->warm_prior_tuple = false;
 
 	/*
 	 * have the am's getbitmap proc do all the work.
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 6dca810..463d4bf 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -20,6 +20,7 @@
 #include "access/nbtxlog.h"
 #include "access/transam.h"
 #include "access/xloginsert.h"
+#include "catalog/index.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
@@ -250,6 +251,10 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 	BTPageOpaque opaque;
 	Buffer		nbuf = InvalidBuffer;
 	bool		found = false;
+	Buffer		buffer;
+	HeapTupleData	heapTuple;
+	bool		recheck = false;
+	IndexInfo	*indexInfo = BuildIndexInfo(rel);
 
 	/* Assume unique until we find a duplicate */
 	*is_unique = true;
@@ -309,6 +314,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				curitup = (IndexTuple) PageGetItem(page, curitemid);
 				htid = curitup->t_tid;
 
+				recheck = false;
+
 				/*
 				 * If we are doing a recheck, we expect to find the tuple we
 				 * are rechecking.  It's not a duplicate, but we have to keep
@@ -326,112 +333,153 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 * have just a single index entry for the entire chain.
 				 */
 				else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
-										 &all_dead))
+							&all_dead, &recheck, &buffer,
+							&heapTuple))
 				{
 					TransactionId xwait;
+					bool result = true;
 
 					/*
-					 * It is a duplicate. If we are only doing a partial
-					 * check, then don't bother checking if the tuple is being
-					 * updated in another transaction. Just return the fact
-					 * that it is a potential conflict and leave the full
-					 * check till later.
+					 * If the tuple was WARM update, we may again see our own
+					 * tuple. Since WARM updates don't create new index
+					 * entries, our own tuple is only reachable via the old
+					 * index pointer.
 					 */
-					if (checkUnique == UNIQUE_CHECK_PARTIAL)
+					if (checkUnique == UNIQUE_CHECK_EXISTING &&
+							ItemPointerCompare(&htid, &itup->t_tid) == 0)
 					{
-						if (nbuf != InvalidBuffer)
-							_bt_relbuf(rel, nbuf);
-						*is_unique = false;
-						return InvalidTransactionId;
+						found = true;
+						result = false;
+						if (recheck)
+							UnlockReleaseBuffer(buffer);
 					}
-
-					/*
-					 * If this tuple is being updated by other transaction
-					 * then we have to wait for its commit/abort.
-					 */
-					xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ?
-						SnapshotDirty.xmin : SnapshotDirty.xmax;
-
-					if (TransactionIdIsValid(xwait))
+					else if (recheck)
 					{
-						if (nbuf != InvalidBuffer)
-							_bt_relbuf(rel, nbuf);
-						/* Tell _bt_doinsert to wait... */
-						*speculativeToken = SnapshotDirty.speculativeToken;
-						return xwait;
+						result = btrecheck(rel, indexInfo, curitup, heapRel, &heapTuple);
+						UnlockReleaseBuffer(buffer);
 					}
 
-					/*
-					 * Otherwise we have a definite conflict.  But before
-					 * complaining, look to see if the tuple we want to insert
-					 * is itself now committed dead --- if so, don't complain.
-					 * This is a waste of time in normal scenarios but we must
-					 * do it to support CREATE INDEX CONCURRENTLY.
-					 *
-					 * We must follow HOT-chains here because during
-					 * concurrent index build, we insert the root TID though
-					 * the actual tuple may be somewhere in the HOT-chain.
-					 * While following the chain we might not stop at the
-					 * exact tuple which triggered the insert, but that's OK
-					 * because if we find a live tuple anywhere in this chain,
-					 * we have a unique key conflict.  The other live tuple is
-					 * not part of this chain because it had a different index
-					 * entry.
-					 */
-					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
-					{
-						/* Normal case --- it's still live */
-					}
-					else
+					if (result)
 					{
 						/*
-						 * It's been deleted, so no error, and no need to
-						 * continue searching
+						 * It is a duplicate. If we are only doing a partial
+						 * check, then don't bother checking if the tuple is being
+						 * updated in another transaction. Just return the fact
+						 * that it is a potential conflict and leave the full
+						 * check till later.
 						 */
-						break;
-					}
+						if (checkUnique == UNIQUE_CHECK_PARTIAL)
+						{
+							if (nbuf != InvalidBuffer)
+								_bt_relbuf(rel, nbuf);
+							*is_unique = false;
+							return InvalidTransactionId;
+						}
 
-					/*
-					 * Check for a conflict-in as we would if we were going to
-					 * write to this page.  We aren't actually going to write,
-					 * but we want a chance to report SSI conflicts that would
-					 * otherwise be masked by this unique constraint
-					 * violation.
-					 */
-					CheckForSerializableConflictIn(rel, NULL, buf);
+						/*
+						 * If this tuple is being updated by other transaction
+						 * then we have to wait for its commit/abort.
+						 */
+						xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ?
+							SnapshotDirty.xmin : SnapshotDirty.xmax;
+
+						if (TransactionIdIsValid(xwait))
+						{
+							if (nbuf != InvalidBuffer)
+								_bt_relbuf(rel, nbuf);
+							/* Tell _bt_doinsert to wait... */
+							*speculativeToken = SnapshotDirty.speculativeToken;
+							return xwait;
+						}
 
-					/*
-					 * This is a definite conflict.  Break the tuple down into
-					 * datums and report the error.  But first, make sure we
-					 * release the buffer locks we're holding ---
-					 * BuildIndexValueDescription could make catalog accesses,
-					 * which in the worst case might touch this same index and
-					 * cause deadlocks.
-					 */
-					if (nbuf != InvalidBuffer)
-						_bt_relbuf(rel, nbuf);
-					_bt_relbuf(rel, buf);
+						/*
+						 * Otherwise we have a definite conflict.  But before
+						 * complaining, look to see if the tuple we want to insert
+						 * is itself now committed dead --- if so, don't complain.
+						 * This is a waste of time in normal scenarios but we must
+						 * do it to support CREATE INDEX CONCURRENTLY.
+						 *
+						 * We must follow HOT-chains here because during
+						 * concurrent index build, we insert the root TID though
+						 * the actual tuple may be somewhere in the HOT-chain.
+						 * While following the chain we might not stop at the
+						 * exact tuple which triggered the insert, but that's OK
+						 * because if we find a live tuple anywhere in this chain,
+						 * we have a unique key conflict.  The other live tuple is
+						 * not part of this chain because it had a different index
+						 * entry.
+						 */
+						recheck = false;
+						ItemPointerCopy(&itup->t_tid, &htid);
+						if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL,
+									&recheck, &buffer, &heapTuple))
+						{
+							bool result = true;
+							if (recheck)
+							{
+								/*
+								 * Recheck if the tuple actually satisfies the
+								 * index key. Otherwise, we might be following
+								 * a wrong index pointer and mustn't entertain
+								 * this tuple.
+								 */
+								result = btrecheck(rel, indexInfo, itup, heapRel, &heapTuple);
+								UnlockReleaseBuffer(buffer);
+							}
+							if (!result)
+								break;
+							/* Normal case --- it's still live */
+						}
+						else
+						{
+							/*
+							 * It's been deleted, so no error, and no need to
+							 * continue searching.
+							 */
+							break;
+						}
 
-					{
-						Datum		values[INDEX_MAX_KEYS];
-						bool		isnull[INDEX_MAX_KEYS];
-						char	   *key_desc;
-
-						index_deform_tuple(itup, RelationGetDescr(rel),
-										   values, isnull);
-
-						key_desc = BuildIndexValueDescription(rel, values,
-															  isnull);
-
-						ereport(ERROR,
-								(errcode(ERRCODE_UNIQUE_VIOLATION),
-								 errmsg("duplicate key value violates unique constraint \"%s\"",
-										RelationGetRelationName(rel)),
-							   key_desc ? errdetail("Key %s already exists.",
-													key_desc) : 0,
-								 errtableconstraint(heapRel,
-											 RelationGetRelationName(rel))));
+						/*
+						 * Check for a conflict-in as we would if we were going to
+						 * write to this page.  We aren't actually going to write,
+						 * but we want a chance to report SSI conflicts that would
+						 * otherwise be masked by this unique constraint
+						 * violation.
+						 */
+						CheckForSerializableConflictIn(rel, NULL, buf);
+
+						/*
+						 * This is a definite conflict.  Break the tuple down into
+						 * datums and report the error.  But first, make sure we
+						 * release the buffer locks we're holding ---
+						 * BuildIndexValueDescription could make catalog accesses,
+						 * which in the worst case might touch this same index and
+						 * cause deadlocks.
+						 */
+						if (nbuf != InvalidBuffer)
+							_bt_relbuf(rel, nbuf);
+						_bt_relbuf(rel, buf);
+
+						{
+							Datum		values[INDEX_MAX_KEYS];
+							bool		isnull[INDEX_MAX_KEYS];
+							char	   *key_desc;
+
+							index_deform_tuple(itup, RelationGetDescr(rel),
+									values, isnull);
+
+							key_desc = BuildIndexValueDescription(rel, values,
+									isnull);
+
+							ereport(ERROR,
+									(errcode(ERRCODE_UNIQUE_VIOLATION),
+									 errmsg("duplicate key value violates unique constraint \"%s\"",
+										 RelationGetRelationName(rel)),
+									 key_desc ? errdetail("Key %s already exists.",
+										 key_desc) : 0,
+									 errtableconstraint(heapRel,
+										 RelationGetRelationName(rel))));
+						}
 					}
 				}
 				else if (all_dead)
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index f815fd4..061c8d4 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -766,29 +766,20 @@ _bt_page_recyclable(Page page)
 }
 
 /*
- * Delete item(s) from a btree page during VACUUM.
+ * Delete item(s) and clear WARM item(s) on a btree page during VACUUM.
  *
  * This must only be used for deleting leaf items.  Deleting an item on a
  * non-leaf page has to be done as part of an atomic action that includes
- * deleting the page it points to.
+ * deleting the page it points to. We don't ever clear pointers on a non-leaf
+ * page.
  *
  * This routine assumes that the caller has pinned and locked the buffer.
  * Also, the given itemnos *must* appear in increasing order in the array.
- *
- * We record VACUUMs and b-tree deletes differently in WAL. InHotStandby
- * we need to be able to pin all of the blocks in the btree in physical
- * order when replaying the effects of a VACUUM, just as we do for the
- * original VACUUM itself. lastBlockVacuumed allows us to tell whether an
- * intermediate range of blocks has had no changes at all by VACUUM,
- * and so must be scanned anyway during replay. We always write a WAL record
- * for the last block in the index, whether or not it contained any items
- * to be removed. This allows us to scan right up to end of index to
- * ensure correct locking.
  */
 void
-_bt_delitems_vacuum(Relation rel, Buffer buf,
-					OffsetNumber *itemnos, int nitems,
-					BlockNumber lastBlockVacuumed)
+_bt_handleitems_vacuum(Relation rel, Buffer buf,
+					OffsetNumber *delitemnos, int ndelitems,
+					OffsetNumber *clearitemnos, int nclearitems)
 {
 	Page		page = BufferGetPage(buf);
 	BTPageOpaque opaque;
@@ -796,9 +787,20 @@ _bt_delitems_vacuum(Relation rel, Buffer buf,
 	/* No ereport(ERROR) until changes are logged */
 	START_CRIT_SECTION();
 
+	/*
+	 * Clear the WARM pointers.
+	 *
+	 * We must do this before dealing with the dead items because
+	 * PageIndexMultiDelete may move items around to compactify the array and
+	 * hence offnums recorded earlier won't make any sense after
+	 * PageIndexMultiDelete is called.
+	 */
+	if (nclearitems > 0)
+		_bt_clear_items(page, clearitemnos, nclearitems);
+
 	/* Fix the page */
-	if (nitems > 0)
-		PageIndexMultiDelete(page, itemnos, nitems);
+	if (ndelitems > 0)
+		PageIndexMultiDelete(page, delitemnos, ndelitems);
 
 	/*
 	 * We can clear the vacuum cycle ID since this page has certainly been
@@ -824,7 +826,8 @@ _bt_delitems_vacuum(Relation rel, Buffer buf,
 		XLogRecPtr	recptr;
 		xl_btree_vacuum xlrec_vacuum;
 
-		xlrec_vacuum.lastBlockVacuumed = lastBlockVacuumed;
+		xlrec_vacuum.ndelitems = ndelitems;
+		xlrec_vacuum.nclearitems = nclearitems;
 
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
@@ -835,8 +838,11 @@ _bt_delitems_vacuum(Relation rel, Buffer buf,
 		 * is.  When XLogInsert stores the whole buffer, the offsets array
 		 * need not be stored too.
 		 */
-		if (nitems > 0)
-			XLogRegisterBufData(0, (char *) itemnos, nitems * sizeof(OffsetNumber));
+		if (ndelitems > 0)
+			XLogRegisterBufData(0, (char *) delitemnos, ndelitems * sizeof(OffsetNumber));
+
+		if (nclearitems > 0)
+			XLogRegisterBufData(0, (char *) clearitemnos, nclearitems * sizeof(OffsetNumber));
 
 		recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_VACUUM);
 
@@ -1882,3 +1888,13 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
 
 	return true;
 }
+
+/*
+ * Currently just a wrapper arounf PageIndexClearWarmTuples, but in theory each
+ * index may have it's own way to handle WARM tuples.
+ */
+void
+_bt_clear_items(Page page, OffsetNumber *clearitemnos, uint16 nclearitems)
+{
+	PageIndexClearWarmTuples(page, clearitemnos, nclearitems);
+}
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 775f2ff..bf5f23f 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -146,6 +146,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->ambuild = btbuild;
 	amroutine->ambuildempty = btbuildempty;
 	amroutine->aminsert = btinsert;
+	amroutine->amwarminsert = btwarminsert;
 	amroutine->ambulkdelete = btbulkdelete;
 	amroutine->amvacuumcleanup = btvacuumcleanup;
 	amroutine->amcanreturn = btcanreturn;
@@ -163,6 +164,8 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->amrecheck = btrecheck;
+	amroutine->amiswarm = btiswarm;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -315,11 +318,12 @@ btbuildempty(Relation index)
  *		Descend the tree recursively, find the appropriate location for our
  *		new tuple, and put it there.
  */
-bool
-btinsert(Relation rel, Datum *values, bool *isnull,
+static bool
+btinsert_internal(Relation rel, Datum *values, bool *isnull,
 		 ItemPointer ht_ctid, Relation heapRel,
 		 IndexUniqueCheck checkUnique,
-		 IndexInfo *indexInfo)
+		 IndexInfo *indexInfo,
+		 bool warm_update)
 {
 	bool		result;
 	IndexTuple	itup;
@@ -328,6 +332,11 @@ btinsert(Relation rel, Datum *values, bool *isnull,
 	itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
 	itup->t_tid = *ht_ctid;
 
+	if (warm_update)
+		ItemPointerSetFlags(&itup->t_tid, BTREE_INDEX_WARM_POINTER);
+	else
+		ItemPointerClearFlags(&itup->t_tid);
+
 	result = _bt_doinsert(rel, itup, checkUnique, heapRel);
 
 	pfree(itup);
@@ -335,6 +344,26 @@ btinsert(Relation rel, Datum *values, bool *isnull,
 	return result;
 }
 
+bool
+btinsert(Relation rel, Datum *values, bool *isnull,
+		 ItemPointer ht_ctid, Relation heapRel,
+		 IndexUniqueCheck checkUnique,
+		 IndexInfo *indexInfo)
+{
+	return btinsert_internal(rel, values, isnull, ht_ctid, heapRel,
+			checkUnique, indexInfo, false);
+}
+
+bool
+btwarminsert(Relation rel, Datum *values, bool *isnull,
+		 ItemPointer ht_ctid, Relation heapRel,
+		 IndexUniqueCheck checkUnique,
+		 IndexInfo *indexInfo)
+{
+	return btinsert_internal(rel, values, isnull, ht_ctid, heapRel,
+			checkUnique, indexInfo, true);
+}
+
 /*
  *	btgettuple() -- Get the next tuple in the scan.
  */
@@ -393,6 +422,20 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				if (so->numKilled < MaxIndexTuplesPerPage)
 					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
 			}
+			else if (scan->warm_prior_tuple)
+			{
+				/*
+				 * Check if the previously fetched tuple should be marked with
+				 * a WARM flag. Similar to killedItems, we don't let to overrun
+				 * the array if the indexscan reverses the direction and we see
+				 * the same tuple twice.
+				 */
+				if (so->setWarmItems == NULL)
+					so->setWarmItems = (int *)
+						palloc(MaxIndexTuplesPerPage * sizeof(int));
+				if (so->numSet < MaxIndexTuplesPerPage)
+					so->setWarmItems[so->numSet++] = so->currPos.itemIndex;
+			}
 
 			/*
 			 * Now continue the scan.
@@ -499,6 +542,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->killedItems = NULL;		/* until needed */
 	so->numKilled = 0;
 
+	so->setWarmItems = NULL;
+	so->numSet = 0;
+
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
@@ -528,6 +574,9 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		/* Before leaving current page, deal with any killed items */
 		if (so->numKilled > 0)
 			_bt_killitems(scan);
+		/* Also deal with items which could be marked WARM */
+		if (so->numSet > 0)
+			_bt_warmitems(scan);
 		BTScanPosUnpinIfPinned(so->currPos);
 		BTScanPosInvalidate(so->currPos);
 	}
@@ -587,6 +636,9 @@ btendscan(IndexScanDesc scan)
 		/* Before leaving current page, deal with any killed items */
 		if (so->numKilled > 0)
 			_bt_killitems(scan);
+		/* Also deal with items which could be marked WARM */
+		if (so->numSet > 0)
+			_bt_warmitems(scan);
 		BTScanPosUnpinIfPinned(so->currPos);
 	}
 
@@ -603,6 +655,8 @@ btendscan(IndexScanDesc scan)
 		MemoryContextDelete(so->arrayContext);
 	if (so->killedItems != NULL)
 		pfree(so->killedItems);
+	if (so->setWarmItems != NULL)
+		pfree(so->setWarmItems);
 	if (so->currTuples != NULL)
 		pfree(so->currTuples);
 	/* so->markTuples should not be pfree'd, see btrescan */
@@ -675,6 +729,9 @@ btrestrpos(IndexScanDesc scan)
 			/* Before leaving current page, deal with any killed items */
 			if (so->numKilled > 0)
 				_bt_killitems(scan);
+			/* Also deal with items which could be marked WARM */
+			if (so->numSet > 0)
+				_bt_warmitems(scan);
 			BTScanPosUnpinIfPinned(so->currPos);
 		}
 
@@ -1103,7 +1160,7 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 								 RBM_NORMAL, info->strategy);
 		LockBufferForCleanup(buf);
 		_bt_checkpage(rel, buf);
-		_bt_delitems_vacuum(rel, buf, NULL, 0, vstate.lastBlockVacuumed);
+		_bt_handleitems_vacuum(rel, buf, NULL, 0, NULL, 0);
 		_bt_relbuf(rel, buf);
 	}
 
@@ -1201,6 +1258,8 @@ restart:
 	{
 		OffsetNumber deletable[MaxOffsetNumber];
 		int			ndeletable;
+		OffsetNumber clearwarm[MaxOffsetNumber];
+		int			nclearwarm;
 		OffsetNumber offnum,
 					minoff,
 					maxoff;
@@ -1239,7 +1298,7 @@ restart:
 		 * Scan over all items to see which ones need deleted according to the
 		 * callback function.
 		 */
-		ndeletable = 0;
+		ndeletable = nclearwarm = 0;
 		minoff = P_FIRSTDATAKEY(opaque);
 		maxoff = PageGetMaxOffsetNumber(page);
 		if (callback)
@@ -1250,6 +1309,9 @@ restart:
 			{
 				IndexTuple	itup;
 				ItemPointer htup;
+				int			flags;
+				bool		is_warm = false;
+				IndexBulkDeleteCallbackResult	result;
 
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offnum));
@@ -1276,16 +1338,36 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				flags = ItemPointerGetFlags(&itup->t_tid);
+				is_warm = ((flags & BTREE_INDEX_WARM_POINTER) != 0);
+
+				if (is_warm)
+					stats->num_warm_pointers++;
+				else
+					stats->num_clear_pointers++;
+
+				result = callback(htup, is_warm, callback_state);
+				if (result == IBDCR_DELETE)
+				{
+					if (is_warm)
+						stats->warm_pointers_removed++;
+					else
+						stats->clear_pointers_removed++;
 					deletable[ndeletable++] = offnum;
+				}
+				else if (result == IBDCR_CLEAR_WARM)
+				{
+					clearwarm[nclearwarm++] = offnum;
+				}
 			}
 		}
 
 		/*
-		 * Apply any needed deletes.  We issue just one _bt_delitems_vacuum()
-		 * call per page, so as to minimize WAL traffic.
+		 * Apply any needed deletes and clearing.  We issue just one
+		 * _bt_handleitems_vacuum() call per page, so as to minimize WAL
+		 * traffic.
 		 */
-		if (ndeletable > 0)
+		if (ndeletable > 0 || nclearwarm > 0)
 		{
 			/*
 			 * Notice that the issued XLOG_BTREE_VACUUM WAL record includes
@@ -1301,8 +1383,8 @@ restart:
 			 * doesn't seem worth the amount of bookkeeping it'd take to avoid
 			 * that.
 			 */
-			_bt_delitems_vacuum(rel, buf, deletable, ndeletable,
-								vstate->lastBlockVacuumed);
+			_bt_handleitems_vacuum(rel, buf, deletable, ndeletable,
+								clearwarm, nclearwarm);
 
 			/*
 			 * Remember highest leaf page number we've issued a
@@ -1312,6 +1394,7 @@ restart:
 				vstate->lastBlockVacuumed = blkno;
 
 			stats->tuples_removed += ndeletable;
+			stats->pointers_cleared += nclearwarm;
 			/* must recompute maxoff */
 			maxoff = PageGetMaxOffsetNumber(page);
 		}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 2f32b2e..d4d6063 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -1350,6 +1350,9 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 	/* Before leaving current page, deal with any killed items */
 	if (so->numKilled > 0)
 		_bt_killitems(scan);
+	/* Also deal with items which could be marked as WARM */
+	if (so->numSet > 0)
+		_bt_warmitems(scan);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
@@ -1948,4 +1951,6 @@ _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
 	}
 	so->numKilled = 0;			/* just paranoia */
 	so->markItemIndex = -1;		/* ditto */
+
+	so->numSet = 0;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 5b259a3..f361bdb 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,11 +20,14 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "access/tuptoaster.h"
+#include "catalog/index.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/datum.h"
 
 
 typedef struct BTSortArrayContext
@@ -1827,6 +1830,95 @@ _bt_killitems(IndexScanDesc scan)
 	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
 }
 
+/*
+ * This is almost identical to _bt_killitems, but we deal with items which
+ * should be marked as WARM.
+ */
+void
+_bt_warmitems(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Page		page;
+	BTPageOpaque opaque;
+	OffsetNumber minoff;
+	OffsetNumber maxoff;
+	int			i;
+	int			numSet = so->numSet;
+	bool		setWarmsomething = false;
+
+	Assert(BTScanPosIsValid(so->currPos));
+
+	/*
+	 * Always reset the scan state, so we don't look for same items on other
+	 * pages.
+	 */
+	so->numSet = 0;
+
+	if (BTScanPosIsPinned(so->currPos))
+	{
+		/*
+		 * We have held the pin on this page since we read the index tuples,
+		 * so all we need to do is lock it.  The pin will have prevented
+		 * re-use of any TID on the page, so there is no need to check the
+		 * LSN.
+		 */
+		LockBuffer(so->currPos.buf, BT_READ);
+
+		page = BufferGetPage(so->currPos.buf);
+	}
+	else
+	{
+		Buffer		buf;
+
+		/* Attempt to re-read the buffer, getting pin and lock. */
+		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+
+		/* It might not exist anymore; in which case we can't hint it. */
+		if (!BufferIsValid(buf))
+			return;
+
+		page = BufferGetPage(buf);
+		if (PageGetLSN(page) == so->currPos.lsn)
+			so->currPos.buf = buf;
+		else
+		{
+			/* Modified while not pinned means hinting is not safe. */
+			_bt_relbuf(scan->indexRelation, buf);
+			return;
+		}
+	}
+
+	for (i = 0; i < numSet; i++)
+	{
+		int			itemIndex = so->setWarmItems[i];
+		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		OffsetNumber offnum = kitem->indexOffset;
+
+		Assert(itemIndex >= so->currPos.firstItem &&
+			   itemIndex <= so->currPos.lastItem);
+		if (offnum < minoff)
+			continue;			/* pure paranoia */
+		while (offnum <= maxoff)
+		{
+			ItemId		iid = PageGetItemId(page, offnum);
+			IndexTuple	ituple = (IndexTuple) PageGetItem(page, iid);
+
+			if (ItemPointerEquals(&ituple->t_tid, &kitem->heapTid))
+			{
+				/* found the item */
+				ItemPointerSetFlags(&ituple->t_tid, BTREE_INDEX_WARM_POINTER);
+				setWarmsomething = true;
+				break;			/* out of inner search loop */
+			}
+			offnum = OffsetNumberNext(offnum);
+		}
+	}
+
+	if (setWarmsomething)
+		MarkBufferDirtyHint(so->currPos.buf, true);
+
+	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+}
 
 /*
  * The following routines manage a shared-memory area in which we track
@@ -2069,3 +2161,107 @@ btproperty(Oid index_oid, int attno,
 			return false;		/* punt to generic code */
 	}
 }
+
+/*
+ * Check if the index tuple's key matches the one computed from the given heap
+ * tuple's attribute
+ */
+bool
+btrecheck(Relation indexRel, IndexInfo *indexInfo, IndexTuple indexTuple1,
+		Relation heapRel, HeapTuple heapTuple)
+{
+	Datum		values[INDEX_MAX_KEYS];
+	bool		isnull[INDEX_MAX_KEYS];
+	bool		isavail[INDEX_MAX_KEYS];
+	int			i;
+	bool		equal;
+	int         natts = indexRel->rd_rel->relnatts;
+	Form_pg_attribute att;
+	IndexTuple	indexTuple2;
+
+	/*
+	 * Currently we don't allow enable_warm to be turned OFF after the table is
+	 * created. But if we ever do that, this assert must be removed since we
+	 * must exercise recheck for all existing WARM chains.
+	 */
+	Assert(RelationWarmUpdatesEnabled(heapRel));
+
+	/*
+	 * Get the index values, except for expression attributes. Since WARM is
+	 * not used when a column used by expressions in an index is modified, we
+	 * can safely assume that those index attributes are never changed by a
+	 * WARM update.
+	 *
+	 * We cannot use FormIndexDatum here because that requires access to
+	 * executor state and we don't have that here.
+	 */
+	FormIndexPlainDatum(indexInfo, heapRel, heapTuple, values, isnull, isavail);
+
+	/*
+	 * Form an index tuple using the heap values first. This allows to then
+	 * fetch index attributes from the current index tuple and the one that is
+	 * formed from the heap values and then do a binary comparison using
+	 * datumIsEqual().
+	 *
+	 * This takes care of doing the right comparison for compressed index
+	 * attributes (we just compare the compressed versions in both tuples) and
+	 * also ensure that we correctly detoast heap values, if need be.
+	 */
+	indexTuple2 = index_form_tuple(RelationGetDescr(indexRel), values, isnull);
+
+	equal = true;
+	for (i = 1; i <= natts; i++)
+	{
+		Datum 	indxvalue1;
+		bool	indxisnull1;
+		Datum	indxvalue2;
+		bool	indxisnull2;
+
+		/* No need to compare if the attribute value is not available */
+		if (!isavail[i - 1])
+			continue;
+
+		indxvalue1 = index_getattr(indexTuple1, i, indexRel->rd_att,
+								   &indxisnull1);
+		indxvalue2 = index_getattr(indexTuple2, i, indexRel->rd_att,
+								   &indxisnull2);
+
+		/*
+		 * If both are NULL, then they are equal
+		 */
+		if (indxisnull1 && indxisnull2)
+			continue;
+
+		/*
+		 * If just one is NULL, then they are not equal
+		 */
+		if (indxisnull1 || indxisnull2)
+		{
+			equal = false;
+			break;
+		}
+
+		/*
+		 * Now just do a raw memory comparison. If the index tuple was formed
+		 * using this heap tuple, the computed index values must match
+		 */
+		att = indexRel->rd_att->attrs[i - 1];
+		if (!datumIsEqual(indxvalue1, indxvalue2, att->attbyval,
+					att->attlen))
+		{
+			equal = false;
+			break;
+		}
+	}
+
+	pfree(indexTuple2);
+
+	return equal;
+}
+
+bool
+btiswarm(Relation indexRel, IndexTuple itup)
+{
+	int flags = ItemPointerGetFlags(&itup->t_tid);
+	return ((flags & BTREE_INDEX_WARM_POINTER) != 0);
+}
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index ac60db0..ef24738 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -390,8 +390,8 @@ btree_xlog_vacuum(XLogReaderState *record)
 	Buffer		buffer;
 	Page		page;
 	BTPageOpaque opaque;
-#ifdef UNUSED
 	xl_btree_vacuum *xlrec = (xl_btree_vacuum *) XLogRecGetData(record);
+#ifdef UNUSED
 
 	/*
 	 * This section of code is thought to be no longer needed, after analysis
@@ -482,19 +482,30 @@ btree_xlog_vacuum(XLogReaderState *record)
 
 		if (len > 0)
 		{
-			OffsetNumber *unused;
-			OffsetNumber *unend;
+			OffsetNumber *offnums = (OffsetNumber *) ptr;
 
-			unused = (OffsetNumber *) ptr;
-			unend = (OffsetNumber *) ((char *) ptr + len);
+			/*
+			 * Clear the WARM pointers.
+			 *
+			 * We must do this before dealing with the dead items because
+			 * PageIndexMultiDelete may move items around to compactify the
+			 * array and hence offnums recorded earlier won't make any sense
+			 * after PageIndexMultiDelete is called.
+			 */
+			if (xlrec->nclearitems > 0)
+				_bt_clear_items(page, offnums + xlrec->ndelitems,
+						xlrec->nclearitems);
 
-			if ((unend - unused) > 0)
-				PageIndexMultiDelete(page, unused, unend - unused);
+			/*
+			 * And handle the deleted items too
+			 */
+			if (xlrec->ndelitems > 0)
+				PageIndexMultiDelete(page, offnums, xlrec->ndelitems);
 		}
 
 		/*
 		 * Mark the page as not containing any LP_DEAD items --- see comments
-		 * in _bt_delitems_vacuum().
+		 * in _bt_handleitems_vacuum().
 		 */
 		opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 		opaque->btpo_flags &= ~BTP_HAS_GARBAGE;
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 44d2d63..d373e61 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -44,6 +44,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
 
 		appendStringInfo(buf, "off %u", xlrec->offnum);
 	}
+	else if (info == XLOG_HEAP_MULTI_INSERT)
+	{
+		xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
+
+		appendStringInfo(buf, "%d tuples", xlrec->ntuples);
+	}
 	else if (info == XLOG_HEAP_DELETE)
 	{
 		xl_heap_delete *xlrec = (xl_heap_delete *) rec;
@@ -102,7 +108,7 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 	char	   *rec = XLogRecGetData(record);
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
 
-	info &= XLOG_HEAP_OPMASK;
+	info &= XLOG_HEAP2_OPMASK;
 	if (info == XLOG_HEAP2_CLEAN)
 	{
 		xl_heap_clean *xlrec = (xl_heap_clean *) rec;
@@ -129,12 +135,6 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 		appendStringInfo(buf, "cutoff xid %u flags %d",
 						 xlrec->cutoff_xid, xlrec->flags);
 	}
-	else if (info == XLOG_HEAP2_MULTI_INSERT)
-	{
-		xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
-
-		appendStringInfo(buf, "%d tuples", xlrec->ntuples);
-	}
 	else if (info == XLOG_HEAP2_LOCK_UPDATED)
 	{
 		xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
@@ -171,6 +171,12 @@ heap_identify(uint8 info)
 		case XLOG_HEAP_INSERT | XLOG_HEAP_INIT_PAGE:
 			id = "INSERT+INIT";
 			break;
+		case XLOG_HEAP_MULTI_INSERT:
+			id = "MULTI_INSERT";
+			break;
+		case XLOG_HEAP_MULTI_INSERT | XLOG_HEAP_INIT_PAGE:
+			id = "MULTI_INSERT+INIT";
+			break;
 		case XLOG_HEAP_DELETE:
 			id = "DELETE";
 			break;
@@ -219,12 +225,6 @@ heap2_identify(uint8 info)
 		case XLOG_HEAP2_VISIBLE:
 			id = "VISIBLE";
 			break;
-		case XLOG_HEAP2_MULTI_INSERT:
-			id = "MULTI_INSERT";
-			break;
-		case XLOG_HEAP2_MULTI_INSERT | XLOG_HEAP_INIT_PAGE:
-			id = "MULTI_INSERT+INIT";
-			break;
 		case XLOG_HEAP2_LOCK_UPDATED:
 			id = "LOCK_UPDATED";
 			break;
diff --git a/src/backend/access/rmgrdesc/nbtdesc.c b/src/backend/access/rmgrdesc/nbtdesc.c
index fbde9d6..6b2c5d6 100644
--- a/src/backend/access/rmgrdesc/nbtdesc.c
+++ b/src/backend/access/rmgrdesc/nbtdesc.c
@@ -48,8 +48,8 @@ btree_desc(StringInfo buf, XLogReaderState *record)
 			{
 				xl_btree_vacuum *xlrec = (xl_btree_vacuum *) rec;
 
-				appendStringInfo(buf, "lastBlockVacuumed %u",
-								 xlrec->lastBlockVacuumed);
+				appendStringInfo(buf, "ndelitems %u, nclearitems %u",
+								 xlrec->ndelitems, xlrec->nclearitems);
 				break;
 			}
 		case XLOG_BTREE_DELETE:
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index e57ac49..59ef7f3 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -72,6 +72,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index cce9b3f..711d351 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -155,7 +155,8 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, false, bds->callback_state) ==
+					IBDCR_DELETE)
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -425,7 +426,8 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, false, bds->callback_state) ==
+					IBDCR_DELETE)
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,10 +904,10 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 }
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
-static bool
-dummy_callback(ItemPointer itemptr, void *state)
+static IndexBulkDeleteCallbackResult
+dummy_callback(ItemPointer itemptr, bool is_warm, void *state)
 {
-	return false;
+	return IBDCR_KEEP;
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1eb163f..2c27661 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/var.h"
 #include "parser/parser.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -114,7 +115,7 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static IndexBulkDeleteCallbackResult validate_index_callback(ItemPointer itemptr, bool is_warm, void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -1691,6 +1692,20 @@ BuildIndexInfo(Relation index)
 	ii->ii_AmCache = NULL;
 	ii->ii_Context = CurrentMemoryContext;
 
+	/* build a bitmap of all table attributes referred by this index */
+	for (i = 0; i < ii->ii_NumIndexAttrs; i++)
+	{
+		AttrNumber attr = ii->ii_KeyAttrNumbers[i];
+		ii->ii_indxattrs = bms_add_member(ii->ii_indxattrs, attr -
+				FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* Collect all attributes used in expressions, too */
+	pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_indxattrs);
+
+	/* Collect all attributes in the index predicate, too */
+	pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_indxattrs);
+
 	return ii;
 }
 
@@ -1815,6 +1830,51 @@ FormIndexDatum(IndexInfo *indexInfo,
 		elog(ERROR, "wrong number of index expressions");
 }
 
+/*
+ * This is same as FormIndexDatum but we don't compute any expression
+ * attributes and hence can be used when executor interfaces are not available.
+ * If i'th attribute is available then isavail[i] is set to true, else set to
+ * false. The caller must always check if an attribute value is available
+ * before trying to do anything useful with that.
+ */
+void
+FormIndexPlainDatum(IndexInfo *indexInfo,
+			   Relation heapRel,
+			   HeapTuple heapTup,
+			   Datum *values,
+			   bool *isnull,
+			   bool *isavail)
+{
+	int			i;
+
+	for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+	{
+		int			keycol = indexInfo->ii_KeyAttrNumbers[i];
+		Datum		iDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/*
+			 * Plain index column; get the value we need directly from the
+			 * heap tuple.
+			 */
+			iDatum = heap_getattr(heapTup, keycol, RelationGetDescr(heapRel), &isNull);
+			values[i] = iDatum;
+			isnull[i] = isNull;
+			isavail[i] = true;
+		}
+		else
+		{
+			/*
+			 * This is an expression attribute which can't be computed by us.
+			 * So just inform the caller about it.
+			 */
+			isavail[i] = false;
+			isnull[i] = true;
+		}
+	}
+}
 
 /*
  * index_update_stats --- update pg_class entry after CREATE INDEX or REINDEX
@@ -2929,15 +2989,15 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
 /*
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
-static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+static IndexBulkDeleteCallbackResult
+validate_index_callback(ItemPointer itemptr, bool is_warm, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
 
 	tuplesort_putdatum(state->tuplesort, Int64GetDatum(encoded), false);
 	state->itups += 1;
-	return false;				/* never actually delete anything */
+	return IBDCR_KEEP;				/* never actually delete anything */
 }
 
 /*
@@ -3156,7 +3216,8 @@ validate_index_heapscan(Relation heapRelation,
 						 heapRelation,
 						 indexInfo->ii_Unique ?
 						 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
-						 indexInfo);
+						 indexInfo,
+						 false);
 
 			state->tups_inserted += 1;
 		}
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index abc344a..6392f33 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -66,10 +66,15 @@ CatalogCloseIndexes(CatalogIndexState indstate)
  *
  * This should be called for each inserted or updated catalog tuple.
  *
+ * If the tuple was WARM updated, the modified_attrs contains the list of
+ * columns updated by the update. We must not insert new index entries for
+ * indexes which do not refer to any of the modified columns.
+ *
  * This is effectively a cut-down version of ExecInsertIndexTuples.
  */
 static void
-CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
+CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
+		Bitmapset *modified_attrs, bool warm_update)
 {
 	int			i;
 	int			numIndexes;
@@ -79,12 +84,28 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 	IndexInfo **indexInfoArray;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
+	ItemPointerData root_tid;
 
-	/* HOT update does not require index inserts */
-	if (HeapTupleIsHeapOnly(heapTuple))
+	/*
+	 * HOT update does not require index inserts, but WARM may need for some
+	 * indexes.
+	 */
+	if (HeapTupleIsHeapOnly(heapTuple) && !warm_update)
 		return;
 
 	/*
+	 * If we've done a WARM update, then we must index the TID of the root line
+	 * pointer and not the actual TID of the new tuple.
+	 */
+	if (warm_update)
+		ItemPointerSet(&root_tid,
+				ItemPointerGetBlockNumber(&(heapTuple->t_self)),
+				HeapTupleHeaderGetRootOffset(heapTuple->t_data));
+	else
+		ItemPointerCopy(&heapTuple->t_self, &root_tid);
+
+
+	/*
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
@@ -112,6 +133,17 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 			continue;
 
 		/*
+		 * If we've done WARM update, then we must not insert a new index tuple
+		 * if none of the index keys have changed. This is not just an
+		 * optimization, but a requirement for WARM to work correctly.
+		 */
+		if (warm_update)
+		{
+			if (!bms_overlap(modified_attrs, indexInfo->ii_indxattrs))
+				continue;
+		}
+
+		/*
 		 * Expressional and partial indexes on system catalogs are not
 		 * supported, nor exclusion constraints, nor deferred uniqueness
 		 */
@@ -136,11 +168,12 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 		index_insert(relationDescs[i],	/* index relation */
 					 values,	/* array of index Datums */
 					 isnull,	/* is-null flags */
-					 &(heapTuple->t_self),		/* tid of heap tuple */
+					 &root_tid,
 					 heapRelation,
 					 relationDescs[i]->rd_index->indisunique ?
 					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
-					 indexInfo);
+					 indexInfo,
+					 warm_update);
 	}
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -168,7 +201,7 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup)
 
 	oid = simple_heap_insert(heapRel, tup);
 
-	CatalogIndexInsert(indstate, tup);
+	CatalogIndexInsert(indstate, tup, NULL, false);
 	CatalogCloseIndexes(indstate);
 
 	return oid;
@@ -190,7 +223,7 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
 
 	oid = simple_heap_insert(heapRel, tup);
 
-	CatalogIndexInsert(indstate, tup);
+	CatalogIndexInsert(indstate, tup, NULL, false);
 
 	return oid;
 }
@@ -210,12 +243,14 @@ void
 CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
 {
 	CatalogIndexState indstate;
+	bool	warm_update;
+	Bitmapset	*modified_attrs;
 
 	indstate = CatalogOpenIndexes(heapRel);
 
-	simple_heap_update(heapRel, otid, tup);
+	simple_heap_update(heapRel, otid, tup, &modified_attrs, &warm_update);
 
-	CatalogIndexInsert(indstate, tup);
+	CatalogIndexInsert(indstate, tup, modified_attrs, warm_update);
 	CatalogCloseIndexes(indstate);
 }
 
@@ -231,9 +266,12 @@ void
 CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
 						   CatalogIndexState indstate)
 {
-	simple_heap_update(heapRel, otid, tup);
+	Bitmapset  *modified_attrs;
+	bool		warm_update;
+
+	simple_heap_update(heapRel, otid, tup, &modified_attrs, &warm_update);
 
-	CatalogIndexInsert(indstate, tup);
+	CatalogIndexInsert(indstate, tup, modified_attrs, warm_update);
 }
 
 /*
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 0217f39..4ef964f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -530,6 +530,7 @@ CREATE VIEW pg_stat_all_tables AS
             pg_stat_get_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_tuples_deleted(C.oid) AS n_tup_del,
             pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tuples_warm_updated(C.oid) AS n_tup_warm_upd,
             pg_stat_get_live_tuples(C.oid) AS n_live_tup,
             pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
             pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
@@ -560,7 +561,8 @@ CREATE VIEW pg_stat_xact_all_tables AS
             pg_stat_get_xact_tuples_inserted(C.oid) AS n_tup_ins,
             pg_stat_get_xact_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_xact_tuples_deleted(C.oid) AS n_tup_del,
-            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd
+            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_xact_tuples_warm_updated(C.oid) AS n_tup_warm_upd
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index e2544e5..330b661 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -40,6 +40,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	TriggerData *trigdata = castNode(TriggerData, fcinfo->context);
 	const char *funcname = "unique_key_recheck";
 	HeapTuple	new_row;
+	HeapTupleData heapTuple;
 	ItemPointerData tmptid;
 	Relation	indexRel;
 	IndexInfo  *indexInfo;
@@ -102,7 +103,8 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * removed.
 	 */
 	tmptid = new_row->t_self;
-	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL,
+				NULL, NULL, &heapTuple))
 	{
 		/*
 		 * All rows in the HOT chain are dead, so skip the check.
@@ -166,7 +168,8 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 		 */
 		index_insert(indexRel, values, isnull, &(new_row->t_self),
 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
-					 indexInfo);
+					 indexInfo,
+					 false);
 	}
 	else
 	{
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8c58808..1366398 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2689,6 +2689,8 @@ CopyFrom(CopyState cstate)
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
 															&(tuple->t_self),
+															&(tuple->t_self),
+															NULL,
 															   estate,
 															   false,
 															   NULL,
@@ -2843,6 +2845,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
+									  &(bufferedTuples[i]->t_self), NULL,
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
 								 bufferedTuples[i],
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4861799..b62b0e9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -694,7 +694,14 @@ DefineIndex(Oid relationId,
 	 * visible to other transactions before we start to build the index. That
 	 * will prevent them from making incompatible HOT updates.  The new index
 	 * will be marked not indisready and not indisvalid, so that no one else
-	 * tries to either insert into it or use it for queries.
+	 * tries to either insert into it or use it for queries. In addition to
+	 * that, WARM updates will be disallowed if an update is modifying one of
+	 * the columns used by this new index. This is necessary to ensure that we
+	 * don't create WARM tuples which do not have corresponding entry in this
+	 * index. It must be noted that during the second phase, we will index only
+	 * those heap tuples whose root line pointer is not already in the index,
+	 * hence it's important that all tuples in a given chain, has the same
+	 * value for any indexed column (including this new index).
 	 *
 	 * We must commit our current transaction so that the index becomes
 	 * visible; then start another.  Note that all the data structures we just
@@ -742,7 +749,10 @@ DefineIndex(Oid relationId,
 	 * marked as "not-ready-for-inserts".  The index is consulted while
 	 * deciding HOT-safety though.  This arrangement ensures that no new HOT
 	 * chains can be created where the new tuple and the old tuple in the
-	 * chain have different index keys.
+	 * chain have different index keys. Also, the new index is consulted for
+	 * deciding whether a WARM update is possible, and WARM update is not done
+	 * if a column used by this index is being updated. This ensures that we
+	 * don't create WARM tuples which are not indexed by this index.
 	 *
 	 * We now take a new snapshot, and build the index using all tuples that
 	 * are visible in this snapshot.  We can be sure that any HOT updates to
@@ -777,7 +787,8 @@ DefineIndex(Oid relationId,
 	/*
 	 * Update the pg_index row to mark the index as ready for inserts. Once we
 	 * commit this transaction, any new transactions that open the table must
-	 * insert new entries into the index for insertions and non-HOT updates.
+	 * insert new entries into the index for insertions and non-HOT updates or
+	 * WARM updates where this index needs new entry.
 	 */
 	index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d418d56..dbec153 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9925,6 +9925,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	Datum		datum;
 	bool		isnull;
 	Datum		newOptions;
+	Datum		std_options;
 	Datum		repl_val[Natts_pg_class];
 	bool		repl_null[Natts_pg_class];
 	bool		repl_repl[Natts_pg_class];
@@ -9969,7 +9970,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 		case RELKIND_PARTITIONED_TABLE:
-			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
+			std_options = heap_reloptions(rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_VIEW:
 			(void) view_reloptions(newOptions, true);
@@ -9985,6 +9986,17 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 			break;
 	}
 
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		bool new_enable_warm = ((StdRdOptions *)(std_options))->enable_warm;
+		if (RelationWarmUpdatesEnabled(rel) && !new_enable_warm)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("WARM updates cannot be disabled on the table \"%s\"",
+						 RelationGetRelationName(rel))));
+	}
+
 	/* Special-case validation of view options */
 	if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 5b43a66..c2d5705 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -104,6 +104,39 @@
  */
 #define PREFETCH_SIZE			((BlockNumber) 32)
 
+/*
+ * Structure to track WARM chains that can be converted into HOT chains during
+ * this run.
+ *
+ * To reduce space requirement, we're using bitfields. But the way things are
+ * laid down, we're still wasting 1-byte per candidate chain.
+ */
+typedef struct LVWarmChain
+{
+	ItemPointerData	chain_tid;			/* root of the chain */
+
+	/*
+	 * 1 - if the chain contains only post-warm tuples
+	 * 0 - if the chain contains only pre-warm tuples
+	 */
+	uint8			is_postwarm_chain:2;
+
+	/* 1 - if this chain must remain a WARM chain */
+	uint8			keep_warm_chain:2;
+
+	/*
+	 * Number of CLEAR pointers to this root TID found so far - must never be
+	 * more than 2.
+	 */
+	uint8			num_clear_pointers:2;
+
+	/*
+	 * Number of WARM pointers to this root TID found so far - must never be
+	 * more than 1.
+	 */
+	uint8			num_warm_pointers:2;
+} LVWarmChain;
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -122,6 +155,14 @@ typedef struct LVRelStats
 	BlockNumber pages_removed;
 	double		tuples_deleted;
 	BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
+
+	/* List of candidate WARM chains that can be converted into HOT chains */
+	/* NB: this list is ordered by TID of the root pointers */
+	int				num_warm_chains;	/* current # of entries */
+	int				max_warm_chains;	/* # slots allocated in array */
+	LVWarmChain 	*warm_chains;		/* array of LVWarmChain */
+	double			num_non_convertible_warm_chains;
+
 	/* List of TIDs of tuples we intend to delete */
 	/* NB: this list is ordered by TID address */
 	int			num_dead_tuples;	/* current # of entries */
@@ -150,6 +191,7 @@ static void lazy_scan_heap(Relation onerel, int options,
 static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats);
 static bool lazy_check_needs_freeze(Buffer buf, bool *hastup);
 static void lazy_vacuum_index(Relation indrel,
+				  bool clear_warm,
 				  IndexBulkDeleteResult **stats,
 				  LVRelStats *vacrelstats);
 static void lazy_cleanup_index(Relation indrel,
@@ -157,6 +199,10 @@ static void lazy_cleanup_index(Relation indrel,
 				   LVRelStats *vacrelstats);
 static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
 				 int tupindex, LVRelStats *vacrelstats, Buffer *vmbuffer);
+static int lazy_warmclear_page(Relation onerel, BlockNumber blkno,
+				 Buffer buffer, int chainindex, LVRelStats *vacrelstats,
+				 Buffer *vmbuffer, bool check_all_visible);
+static void lazy_reset_warm_pointer_count(LVRelStats *vacrelstats);
 static bool should_attempt_truncation(LVRelStats *vacrelstats);
 static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
 static BlockNumber count_nondeletable_pages(Relation onerel,
@@ -164,8 +210,15 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static void lazy_record_warm_chain(LVRelStats *vacrelstats,
+					   ItemPointer itemptr);
+static void lazy_record_clear_chain(LVRelStats *vacrelstats,
+					   ItemPointer itemptr);
+static IndexBulkDeleteCallbackResult lazy_tid_reaped(ItemPointer itemptr, bool is_warm, void *state);
+static IndexBulkDeleteCallbackResult lazy_indexvac_phase1(ItemPointer itemptr, bool is_warm, void *state);
+static IndexBulkDeleteCallbackResult lazy_indexvac_phase2(ItemPointer itemptr, bool is_warm, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
+static int vac_cmp_warm_chain(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 					 TransactionId *visibility_cutoff_xid, bool *all_frozen);
 
@@ -690,8 +743,10 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
 		 */
-		if ((vacrelstats->max_dead_tuples - vacrelstats->num_dead_tuples) < MaxHeapTuplesPerPage &&
-			vacrelstats->num_dead_tuples > 0)
+		if (((vacrelstats->max_dead_tuples - vacrelstats->num_dead_tuples) < MaxHeapTuplesPerPage &&
+			vacrelstats->num_dead_tuples > 0) ||
+			((vacrelstats->max_warm_chains - vacrelstats->num_warm_chains) < MaxHeapTuplesPerPage &&
+			 vacrelstats->num_warm_chains > 0))
 		{
 			const int	hvp_index[] = {
 				PROGRESS_VACUUM_PHASE,
@@ -721,6 +776,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
 				lazy_vacuum_index(Irel[i],
+								  (vacrelstats->num_warm_chains > 0),
 								  &indstats[i],
 								  vacrelstats);
 
@@ -743,6 +799,9 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			vacrelstats->num_warm_chains = 0;
+			memset(vacrelstats->warm_chains, 0,
+					vacrelstats->max_warm_chains * sizeof (LVWarmChain));
 			vacrelstats->num_index_scans++;
 
 			/* Report that we are once again scanning the heap */
@@ -947,15 +1006,33 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 				continue;
 			}
 
+			ItemPointerSet(&(tuple.t_self), blkno, offnum);
+
 			/* Redirect items mustn't be touched */
 			if (ItemIdIsRedirected(itemid))
 			{
+				HeapCheckWarmChainStatus status = 0;
+
+				if (RelationWarmUpdatesEnabled(onerel))
+					status = heap_check_warm_chain(page, &tuple.t_self, false);
+				if (HCWC_IS_WARM_UPDATED(status))
+				{
+					/*
+					 * A chain which is either complete WARM or CLEAR is a
+					 * candidate for chain conversion. Remember the chain and
+					 * whether the chain has all WARM tuples or not.
+					 */
+					if (HCWC_IS_ALL_WARM(status))
+						lazy_record_warm_chain(vacrelstats, &tuple.t_self);
+					else if (HCWC_IS_ALL_CLEAR(status))
+						lazy_record_clear_chain(vacrelstats, &tuple.t_self);
+					else
+						vacrelstats->num_non_convertible_warm_chains++;
+				}
 				hastup = true;	/* this page won't be truncatable */
 				continue;
 			}
 
-			ItemPointerSet(&(tuple.t_self), blkno, offnum);
-
 			/*
 			 * DEAD item pointers are to be vacuumed normally; but we don't
 			 * count them in tups_vacuumed, else we'd be double-counting (at
@@ -975,6 +1052,29 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			tuple.t_len = ItemIdGetLength(itemid);
 			tuple.t_tableOid = RelationGetRelid(onerel);
 
+			if (!HeapTupleIsHeapOnly(&tuple))
+			{
+				HeapCheckWarmChainStatus status = 0;
+
+				if (RelationWarmUpdatesEnabled(onerel))
+					status = heap_check_warm_chain(page, &tuple.t_self, false);
+
+				if (HCWC_IS_WARM_UPDATED(status))
+				{
+					/*
+					 * A chain which is either complete WARM or CLEAR is a
+					 * candidate for chain conversion. Remember the chain and
+					 * its color.
+					 */
+					if (HCWC_IS_ALL_WARM(status))
+						lazy_record_warm_chain(vacrelstats, &tuple.t_self);
+					else if (HCWC_IS_ALL_CLEAR(status))
+						lazy_record_clear_chain(vacrelstats, &tuple.t_self);
+					else
+						vacrelstats->num_non_convertible_warm_chains++;
+				}
+			}
+
 			tupgone = false;
 
 			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
@@ -1040,6 +1140,19 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 							break;
 						}
 
+						/*
+						 * If this tuple was ever WARM updated or is a WARM
+						 * tuple, there could be multiple index entries
+						 * pointing to the root of this chain. We can't do
+						 * index-only scans for such tuples without verifying
+						 * index key check. So mark the page as !all_visible
+						 */
+						if (HeapTupleHeaderIsWarmUpdated(tuple.t_data))
+						{
+							all_visible = false;
+							break;
+						}
+
 						/* Track newest xmin on page. */
 						if (TransactionIdFollows(xmin, visibility_cutoff_xid))
 							visibility_cutoff_xid = xmin;
@@ -1282,7 +1395,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 
 	/* If any tuples need to be deleted, perform final vacuum cycle */
 	/* XXX put a threshold on min number of tuples here? */
-	if (vacrelstats->num_dead_tuples > 0)
+	if (vacrelstats->num_dead_tuples > 0 || vacrelstats->num_warm_chains > 0)
 	{
 		const int	hvp_index[] = {
 			PROGRESS_VACUUM_PHASE,
@@ -1300,6 +1413,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
 			lazy_vacuum_index(Irel[i],
+							  (vacrelstats->num_warm_chains > 0),
 							  &indstats[i],
 							  vacrelstats);
 
@@ -1371,7 +1485,10 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
  *
  *		This routine marks dead tuples as unused and compacts out free
  *		space on their pages.  Pages not having dead tuples recorded from
- *		lazy_scan_heap are not visited at all.
+ *		lazy_scan_heap are not visited at all. This routine also converts
+ *		candidate WARM chains to HOT chains by clearing WARM related flags. The
+ *		candidate chains are determined by the preceeding index scans after
+ *		looking at the data collected by the first heap scan.
  *
  * Note: the reason for doing this as a second pass is we cannot remove
  * the tuples until we've removed their index entries, and we want to
@@ -1380,7 +1497,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 static void
 lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
 {
-	int			tupindex;
+	int			tupindex, chainindex;
 	int			npages;
 	PGRUsage	ru0;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -1389,33 +1506,69 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
 	npages = 0;
 
 	tupindex = 0;
-	while (tupindex < vacrelstats->num_dead_tuples)
+	chainindex = 0;
+	while (tupindex < vacrelstats->num_dead_tuples ||
+		   chainindex < vacrelstats->num_warm_chains)
 	{
-		BlockNumber tblk;
+		BlockNumber tblk, chainblk, vacblk;
 		Buffer		buf;
 		Page		page;
 		Size		freespace;
 
 		vacuum_delay_point();
 
-		tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);
-		buf = ReadBufferExtended(onerel, MAIN_FORKNUM, tblk, RBM_NORMAL,
+		tblk = chainblk = InvalidBlockNumber;
+		if (chainindex < vacrelstats->num_warm_chains)
+			chainblk =
+				ItemPointerGetBlockNumber(&(vacrelstats->warm_chains[chainindex].chain_tid));
+
+		if (tupindex < vacrelstats->num_dead_tuples)
+			tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);
+
+		if (tblk == InvalidBlockNumber)
+			vacblk = chainblk;
+		else if (chainblk == InvalidBlockNumber)
+			vacblk = tblk;
+		else
+			vacblk = Min(chainblk, tblk);
+
+		Assert(vacblk != InvalidBlockNumber);
+
+		buf = ReadBufferExtended(onerel, MAIN_FORKNUM, vacblk, RBM_NORMAL,
 								 vac_strategy);
-		if (!ConditionalLockBufferForCleanup(buf))
+
+
+		if (vacblk == chainblk)
+			LockBufferForCleanup(buf);
+		else if (!ConditionalLockBufferForCleanup(buf))
 		{
 			ReleaseBuffer(buf);
 			++tupindex;
 			continue;
 		}
-		tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats,
-									&vmbuffer);
+
+		/*
+		 * Convert WARM chains on this page. This should be done before
+		 * vacuuming the page to ensure that we can correctly set visibility
+		 * bits after clearing WARM chains.
+		 *
+		 * If we are going to vacuum this page then don't check for
+		 * all-visibility just yet.
+		*/
+		if (vacblk == chainblk)
+			chainindex = lazy_warmclear_page(onerel, chainblk, buf, chainindex,
+					vacrelstats, &vmbuffer, chainblk != tblk);
+
+		if (vacblk == tblk)
+			tupindex = lazy_vacuum_page(onerel, tblk, buf, tupindex, vacrelstats,
+					&vmbuffer);
 
 		/* Now that we've compacted the page, record its available space */
 		page = BufferGetPage(buf);
 		freespace = PageGetHeapFreeSpace(page);
 
 		UnlockReleaseBuffer(buf);
-		RecordPageWithFreeSpace(onerel, tblk, freespace);
+		RecordPageWithFreeSpace(onerel, vacblk, freespace);
 		npages++;
 	}
 
@@ -1434,6 +1587,107 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
 }
 
 /*
+ *	lazy_warmclear_page() -- clear various WARM bits on the tuples.
+ *
+ * Caller must hold pin and buffer cleanup lock on the buffer.
+ *
+ * chainindex is the index in vacrelstats->warm_chains of the first dead
+ * tuple for this page.  We assume the rest follow sequentially.
+ * The return value is the first tupindex after the tuples of this page.
+ *
+ * If check_all_visible is set then we also check if the page has now become
+ * all visible and update visibility map.
+ */
+static int
+lazy_warmclear_page(Relation onerel, BlockNumber blkno, Buffer buffer,
+				 int chainindex, LVRelStats *vacrelstats, Buffer *vmbuffer,
+				 bool check_all_visible)
+{
+	Page			page = BufferGetPage(buffer);
+	OffsetNumber	cleared_offnums[MaxHeapTuplesPerPage];
+	int				num_cleared = 0;
+	TransactionId	visibility_cutoff_xid;
+	bool			all_frozen;
+
+	pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_WARMCLEARED, blkno);
+
+	START_CRIT_SECTION();
+
+	for (; chainindex < vacrelstats->num_warm_chains ; chainindex++)
+	{
+		BlockNumber tblk;
+		LVWarmChain	*chain;
+
+		chain = &vacrelstats->warm_chains[chainindex];
+
+		tblk = ItemPointerGetBlockNumber(&chain->chain_tid);
+		if (tblk != blkno)
+			break;				/* past end of tuples for this block */
+
+		/*
+		 * Since a heap page can have no more than MaxHeapTuplesPerPage
+		 * offnums and we process each offnum only once, MaxHeapTuplesPerPage
+		 * size array should be enough to hold all cleared tuples in this page.
+		 */
+		if (!chain->keep_warm_chain)
+			num_cleared += heap_clear_warm_chain(page, &chain->chain_tid,
+					cleared_offnums + num_cleared);
+	}
+
+	/*
+	 * Mark buffer dirty before we write WAL.
+	 */
+	MarkBufferDirty(buffer);
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(onerel))
+	{
+		XLogRecPtr	recptr;
+
+		recptr = log_heap_warmclear(onerel, buffer,
+								cleared_offnums, num_cleared);
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	/* If not checking for all-visibility then we're done */
+	if (!check_all_visible)
+		return chainindex;
+
+	/*
+	 * The following code should match the corresponding code in
+	 * lazy_vacuum_page
+	 **/
+	if (heap_page_is_all_visible(onerel, buffer, &visibility_cutoff_xid,
+								 &all_frozen))
+		PageSetAllVisible(page);
+
+	/*
+	 * All the changes to the heap page have been done. If the all-visible
+	 * flag is now set, also set the VM all-visible bit (and, if possible, the
+	 * all-frozen bit) unless this has already been done previously.
+	 */
+	if (PageIsAllVisible(page))
+	{
+		uint8		vm_status = visibilitymap_get_status(onerel, blkno, vmbuffer);
+		uint8		flags = 0;
+
+		/* Set the VM all-frozen bit to flag, if needed */
+		if ((vm_status & VISIBILITYMAP_ALL_VISIBLE) == 0)
+			flags |= VISIBILITYMAP_ALL_VISIBLE;
+		if ((vm_status & VISIBILITYMAP_ALL_FROZEN) == 0 && all_frozen)
+			flags |= VISIBILITYMAP_ALL_FROZEN;
+
+		Assert(BufferIsValid(*vmbuffer));
+		if (flags != 0)
+			visibilitymap_set(onerel, blkno, buffer, InvalidXLogRecPtr,
+							  *vmbuffer, visibility_cutoff_xid, flags);
+	}
+	return chainindex;
+}
+
+/*
  *	lazy_vacuum_page() -- free dead tuples on a page
  *					 and repair its fragmentation.
  *
@@ -1586,6 +1840,24 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup)
 	return false;
 }
 
+/*
+ * Reset counters tracking number of WARM and CLEAR pointers per candidate TID.
+ * These counters are maintained per index and cleared when the next index is
+ * picked up for cleanup.
+ *
+ * We don't touch the keep_warm_chain since once a chain is known to be
+ * non-convertible, we must remember that across all indexes.
+ */
+static void
+lazy_reset_warm_pointer_count(LVRelStats *vacrelstats)
+{
+	int i;
+	for (i = 0; i < vacrelstats->num_warm_chains; i++)
+	{
+		LVWarmChain *chain = &vacrelstats->warm_chains[i];
+		chain->num_clear_pointers = chain->num_warm_pointers = 0;
+	}
+}
 
 /*
  *	lazy_vacuum_index() -- vacuum one index relation.
@@ -1595,6 +1867,7 @@ lazy_check_needs_freeze(Buffer buf, bool *hastup)
  */
 static void
 lazy_vacuum_index(Relation indrel,
+				  bool clear_warm,
 				  IndexBulkDeleteResult **stats,
 				  LVRelStats *vacrelstats)
 {
@@ -1610,15 +1883,87 @@ lazy_vacuum_index(Relation indrel,
 	ivinfo.num_heap_tuples = vacrelstats->old_rel_tuples;
 	ivinfo.strategy = vac_strategy;
 
-	/* Do bulk deletion */
-	*stats = index_bulk_delete(&ivinfo, *stats,
-							   lazy_tid_reaped, (void *) vacrelstats);
+	/*
+	 * If told, convert WARM chains into HOT chains.
+	 *
+	 * We must have already collected candidate WARM chains i.e. chains that
+	 * have either all tuples with HEAP_WARM_TUPLE flag set or none.
+	 *
+	 * This works in two phases. In the first phase, we do a complete index
+	 * scan and collect information about index pointers to the candidate
+	 * chains, but we don't do conversion. To be precise, we count the number
+	 * of WARM and CLEAR index pointers to each candidate chain and use that
+	 * knowledge to arrive at a decision and do the actual conversion during
+	 * the second phase (we kill known dead pointers though in this phase).
+	 *
+	 * In the second phase, for each candidate chain we check if we have seen a
+	 * WARM index pointer. For such chains, we kill the CLEAR pointer and
+	 * convert the WARM pointer into a CLEAR pointer. The heap tuples are
+	 * cleared of WARM flags in the second heap scan. If we did not find any
+	 * WARM pointer to a WARM chain, that means that the chain is reachable
+	 * from the CLEAR pointer (because say WARM update did not add a new entry
+	 * for this index). In that case, we do nothing.  There is a third case
+	 * where we find two CLEAR pointers to a candidate chain. This can happen
+	 * because of aborted vacuums. We don't handle that case yet, but it should
+	 * be possible to apply the same recheck logic and find which of the clear
+	 * pointers is redundant and should be removed.
+	 *
+	 * For CLEAR chains, we just kill the WARM pointer, if it exists, and keep
+	 * the CLEAR pointer.
+	 */
+	if (clear_warm)
+	{
+		/*
+		 * Before starting the index scan, reset the counters of WARM and CLEAR
+		 * pointers, probably carried forward from the previous index.
+		 */
+		lazy_reset_warm_pointer_count(vacrelstats);
+
+		*stats = index_bulk_delete(&ivinfo, *stats,
+				lazy_indexvac_phase1, (void *) vacrelstats);
+		ereport(elevel,
+				(errmsg("scanned index \"%s\" to remove %d row version, found "
+						"%0.f warm pointers, %0.f clear pointers, removed "
+						"%0.f warm pointers, removed %0.f clear pointers",
+						RelationGetRelationName(indrel),
+						vacrelstats->num_dead_tuples,
+						(*stats)->num_warm_pointers,
+						(*stats)->num_clear_pointers,
+						(*stats)->warm_pointers_removed,
+						(*stats)->clear_pointers_removed)));
+
+		(*stats)->num_warm_pointers = 0;
+		(*stats)->num_clear_pointers = 0;
+		(*stats)->warm_pointers_removed = 0;
+		(*stats)->clear_pointers_removed = 0;
+		(*stats)->pointers_cleared = 0;
+
+		*stats = index_bulk_delete(&ivinfo, *stats,
+				lazy_indexvac_phase2, (void *) vacrelstats);
+		ereport(elevel,
+				(errmsg("scanned index \"%s\" to convert WARM pointers, found "
+						"%0.f WARM pointers, %0.f CLEAR pointers, removed "
+						"%0.f WARM pointers, removed %0.f CLEAR pointers, "
+						"cleared %0.f WARM pointers",
+						RelationGetRelationName(indrel),
+						(*stats)->num_warm_pointers,
+						(*stats)->num_clear_pointers,
+						(*stats)->warm_pointers_removed,
+						(*stats)->clear_pointers_removed,
+						(*stats)->pointers_cleared)));
+	}
+	else
+	{
+		/* Do bulk deletion */
+		*stats = index_bulk_delete(&ivinfo, *stats,
+				lazy_tid_reaped, (void *) vacrelstats);
+		ereport(elevel,
+				(errmsg("scanned index \"%s\" to remove %d row versions",
+						RelationGetRelationName(indrel),
+						vacrelstats->num_dead_tuples),
+				 errdetail("%s.", pg_rusage_show(&ru0))));
+	}
 
-	ereport(elevel,
-			(errmsg("scanned index \"%s\" to remove %d row versions",
-					RelationGetRelationName(indrel),
-					vacrelstats->num_dead_tuples),
-			 errdetail("%s.", pg_rusage_show(&ru0))));
 }
 
 /*
@@ -1992,9 +2337,11 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
 
 	if (vacrelstats->hasindex)
 	{
-		maxtuples = (vac_work_mem * 1024L) / sizeof(ItemPointerData);
+		maxtuples = (vac_work_mem * 1024L) / (sizeof(ItemPointerData) +
+				sizeof(LVWarmChain));
 		maxtuples = Min(maxtuples, INT_MAX);
-		maxtuples = Min(maxtuples, MaxAllocSize / sizeof(ItemPointerData));
+		maxtuples = Min(maxtuples, MaxAllocSize / (sizeof(ItemPointerData) +
+					sizeof(LVWarmChain)));
 
 		/* curious coding here to ensure the multiplication can't overflow */
 		if ((BlockNumber) (maxtuples / LAZY_ALLOC_TUPLES) > relblocks)
@@ -2012,6 +2359,57 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
 	vacrelstats->max_dead_tuples = (int) maxtuples;
 	vacrelstats->dead_tuples = (ItemPointer)
 		palloc(maxtuples * sizeof(ItemPointerData));
+
+	/*
+	 * XXX Cheat for now and allocate the same size array for tracking warm
+	 * chains. maxtuples must have been already adjusted above to ensure we
+	 * don't cross vac_work_mem.
+	 */
+	vacrelstats->num_warm_chains = 0;
+	vacrelstats->max_warm_chains = (int) maxtuples;
+	vacrelstats->warm_chains = (LVWarmChain *)
+		palloc0(maxtuples * sizeof(LVWarmChain));
+
+}
+
+/*
+ * lazy_record_clear_chain - remember one CLEAR chain
+ */
+static void
+lazy_record_clear_chain(LVRelStats *vacrelstats,
+					   ItemPointer itemptr)
+{
+	/*
+	 * The array shouldn't overflow under normal behavior, but perhaps it
+	 * could if we are given a really small maintenance_work_mem. In that
+	 * case, just forget the last few tuples (we'll get 'em next time).
+	 */
+	if (vacrelstats->num_warm_chains < vacrelstats->max_warm_chains)
+	{
+		vacrelstats->warm_chains[vacrelstats->num_warm_chains].chain_tid = *itemptr;
+		vacrelstats->warm_chains[vacrelstats->num_warm_chains].is_postwarm_chain = 0;
+		vacrelstats->num_warm_chains++;
+	}
+}
+
+/*
+ * lazy_record_warm_chain - remember one WARM chain
+ */
+static void
+lazy_record_warm_chain(LVRelStats *vacrelstats,
+					   ItemPointer itemptr)
+{
+	/*
+	 * The array shouldn't overflow under normal behavior, but perhaps it
+	 * could if we are given a really small maintenance_work_mem. In that
+	 * case, just forget the last few tuples (we'll get 'em next time).
+	 */
+	if (vacrelstats->num_warm_chains < vacrelstats->max_warm_chains)
+	{
+		vacrelstats->warm_chains[vacrelstats->num_warm_chains].chain_tid = *itemptr;
+		vacrelstats->warm_chains[vacrelstats->num_warm_chains].is_postwarm_chain = 1;
+		vacrelstats->num_warm_chains++;
+	}
 }
 
 /*
@@ -2042,8 +2440,8 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *
  *		Assumes dead_tuples array is in sorted order.
  */
-static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+static IndexBulkDeleteCallbackResult
+lazy_tid_reaped(ItemPointer itemptr, bool is_warm, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
@@ -2054,7 +2452,207 @@ lazy_tid_reaped(ItemPointer itemptr, void *state)
 								sizeof(ItemPointerData),
 								vac_cmp_itemptr);
 
-	return (res != NULL);
+	return (res != NULL) ? IBDCR_DELETE : IBDCR_KEEP;
+}
+
+/*
+ *	lazy_indexvac_phase1() -- run first pass of index vacuum
+ *
+ *		This has the right signature to be an IndexBulkDeleteCallback.
+ */
+static IndexBulkDeleteCallbackResult
+lazy_indexvac_phase1(ItemPointer itemptr, bool is_warm, void *state)
+{
+	LVRelStats		*vacrelstats = (LVRelStats *) state;
+	ItemPointer		res;
+	LVWarmChain	*chain;
+
+	res = (ItemPointer) bsearch((void *) itemptr,
+								(void *) vacrelstats->dead_tuples,
+								vacrelstats->num_dead_tuples,
+								sizeof(ItemPointerData),
+								vac_cmp_itemptr);
+
+	if (res != NULL)
+		return IBDCR_DELETE;
+
+	chain = (LVWarmChain *) bsearch((void *) itemptr,
+								(void *) vacrelstats->warm_chains,
+								vacrelstats->num_warm_chains,
+								sizeof(LVWarmChain),
+								vac_cmp_warm_chain);
+	if (chain != NULL)
+	{
+		if (is_warm)
+			chain->num_warm_pointers++;
+		else
+			chain->num_clear_pointers++;
+	}
+	return IBDCR_KEEP;
+}
+
+/*
+ *	lazy_indexvac_phase2() -- run second pass of index vacuum
+ *
+ *		This has the right signature to be an IndexBulkDeleteCallback.
+ */
+static IndexBulkDeleteCallbackResult
+lazy_indexvac_phase2(ItemPointer itemptr, bool is_warm, void *state)
+{
+	LVRelStats		*vacrelstats = (LVRelStats *) state;
+	LVWarmChain	*chain;
+
+	chain = (LVWarmChain *) bsearch((void *) itemptr,
+								(void *) vacrelstats->warm_chains,
+								vacrelstats->num_warm_chains,
+								sizeof(LVWarmChain),
+								vac_cmp_warm_chain);
+
+	if (chain != NULL && (chain->keep_warm_chain != 1))
+	{
+		/*
+		 * At no point, we can have more than 1 warm pointer to any chain and
+		 * no more than 2 clear pointers.
+		 */
+		Assert(chain->num_warm_pointers <= 1);
+		Assert(chain->num_clear_pointers <= 2);
+
+		if (chain->is_postwarm_chain == 1)
+		{
+			if (is_warm)
+			{
+				/*
+				 * A WARM pointer, pointing to a WARM chain.
+				 *
+				 * Clear the warm pointer (and delete the CLEAR pointer). We
+				 * may have already seen the CLEAR pointer in the scan and
+				 * deleted that or we may see it later in the scan. It doesn't
+				 * matter if we fail at any point because we won't clear up
+				 * WARM bits on the heap tuples until we have dealt with the
+				 * index pointers cleanly.
+				 */
+				return IBDCR_CLEAR_WARM;
+			}
+			else
+			{
+				/*
+				 * CLEAR pointer to a WARM chain.
+				 */
+				if (chain->num_warm_pointers > 0 &&
+					chain->num_clear_pointers > 0)
+				{
+					/*
+					 * If there exists a WARM pointer to the chain, we can
+					 * delete the CLEAR pointer and clear the WARM bits on the
+					 * heap tuples.
+					 *
+					 * It might look like paranoia that we're also checking for
+					 * num_clear_pointers to be more than 0. After all we are
+					 * currently looking at a CLEAR pointer. But the reason why
+					 * this is important is because online cleanup of
+					 * WARM/CLEAR pointers may set a CLEAR pointer to a WARM
+					 * pointer, but that information may be lost if the buffer
+					 * is discarded before it's written to the disk. So we
+					 * could count the same pointer was a WARM pointer during
+					 * the first index scan and again see that as a CLEAR
+					 * pointer during the second index scan. Checking for both
+					 * WARM and CLEAR pointers ensures that we don't remove a
+					 * CLEAR pointer when a WARM pointer does not exist.
+					 */
+					return IBDCR_DELETE;
+				}
+				else if (chain->num_clear_pointers == 1)
+				{
+					/*
+					 * If this is the only pointer to a WARM chain, we must
+					 * keep the CLEAR pointer.
+					 *
+					 * The presence of WARM chain indicates that the WARM update
+					 * must have been committed good. But during the update
+					 * this index was probably not updated and hence it
+					 * contains just one, original CLEAR pointer to the chain.
+					 * We should be able to clear the WARM bits on heap tuples
+					 * unless we later find another index which prevents the
+					 * cleanup.
+					 */
+					return IBDCR_KEEP;
+				}
+			}
+		}
+		else
+		{
+			/*
+			 * This is a CLEAR chain.
+			 */
+			if (is_warm)
+			{
+				/*
+				 * A WARM pointer to a CLEAR chain.
+				 *
+				 * This can happen when a WARM update is aborted. Later the HOT
+				 * chain is pruned leaving behind only CLEAR tuples in the
+				 * chain. But the WARM index pointer inserted in the index
+				 * remains and it must now be deleted before we clear WARM bits
+				 * from the heap tuple.
+				 */
+				return IBDCR_DELETE;
+			}
+
+			/*
+			 * CLEAR pointer to a CLEAR chain.
+			 *
+			 * If this is the only surviving CLEAR pointer, keep it and clear
+			 * the WARM bits from the heap tuples.
+			 */
+			if (chain->num_clear_pointers == 1)
+				return IBDCR_KEEP;
+
+			/*
+			 * If there are more than 1 CLEAR pointers to this chain, we can
+			 * apply the recheck logic and kill the redudant CLEAR pointer and
+			 * convert the chain. But that's not yet done.
+			 */
+		}
+
+		/*
+		 * For everything else, we must keep the WARM bits and also keep the
+		 * index pointers.
+		 */
+		chain->keep_warm_chain = 1;
+		return IBDCR_KEEP;
+	}
+	return IBDCR_KEEP;
+}
+
+/*
+ * Comparator routines for use with qsort() and bsearch(). Similar to
+ * vac_cmp_itemptr, but right hand argument is LVWarmChain struct pointer.
+ */
+static int
+vac_cmp_warm_chain(const void *left, const void *right)
+{
+	BlockNumber lblk,
+				rblk;
+	OffsetNumber loff,
+				roff;
+
+	lblk = ItemPointerGetBlockNumber((ItemPointer) left);
+	rblk = ItemPointerGetBlockNumber(&((LVWarmChain *) right)->chain_tid);
+
+	if (lblk < rblk)
+		return -1;
+	if (lblk > rblk)
+		return 1;
+
+	loff = ItemPointerGetOffsetNumber((ItemPointer) left);
+	roff = ItemPointerGetOffsetNumber(&((LVWarmChain *) right)->chain_tid);
+
+	if (loff < roff)
+		return -1;
+	if (loff > roff)
+		return 1;
+
+	return 0;
 }
 
 /*
@@ -2170,6 +2768,18 @@ heap_page_is_all_visible(Relation rel, Buffer buf,
 						break;
 					}
 
+					/*
+					 * If this or any other tuple in the chain ever WARM
+					 * updated, there could be multiple index entries pointing
+					 * to the root of this chain. We can't do index-only scans
+					 * for such tuples without verifying index key check. So
+					 * mark the page as !all_visible
+					 */
+					if (HeapTupleHeaderIsWarmUpdated(tuple.t_data))
+					{
+						all_visible = false;
+					}
+
 					/* Track newest xmin on page. */
 					if (TransactionIdFollows(xmin, *visibility_cutoff_xid))
 						*visibility_cutoff_xid = xmin;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index c3f1873..2143978 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -270,6 +270,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
 					  ItemPointer tupleid,
+					  ItemPointer root_tid,
+					  Bitmapset *modified_attrs,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
@@ -324,6 +326,17 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 		if (!indexInfo->ii_ReadyForInserts)
 			continue;
 
+		/*
+		 * If modified_attrs is set, we only insert index entries for those
+		 * indexes whose column has changed. All other indexes can use their
+		 * existing index pointers to look up the new tuple
+		 */
+		if (modified_attrs)
+		{
+			if (!bms_overlap(modified_attrs, indexInfo->ii_indxattrs))
+				continue;
+		}
+
 		/* Check for partial index */
 		if (indexInfo->ii_Predicate != NIL)
 		{
@@ -387,10 +400,11 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 			index_insert(indexRelation, /* index relation */
 						 values,	/* array of index Datums */
 						 isnull,	/* null flags */
-						 tupleid,		/* tid of heap tuple */
+						 root_tid,		/* tid of heap or root tuple */
 						 heapRelation,	/* heap relation */
 						 checkUnique,	/* type of uniqueness check to do */
-						 indexInfo);	/* index AM may need this */
+						 indexInfo,	/* index AM may need this */
+						 (modified_attrs != NULL));	/* is it a WARM update? */
 
 		/*
 		 * If the index has an associated exclusion constraint, check that.
@@ -787,6 +801,9 @@ retry:
 		{
 			if (!HeapTupleHeaderIsHeapLatest(tup->t_data, &tup->t_self))
 				HeapTupleHeaderGetNextTid(tup->t_data, &ctid_wait);
+			else
+				ItemPointerCopy(&tup->t_self, &ctid_wait);
+
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index f20d728..747e4ce 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -399,6 +399,8 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		if (resultRelInfo->ri_NumIndices > 0)
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												   &(tuple->t_self),
+												   NULL,
 												   estate, false, NULL,
 												   NIL);
 
@@ -445,6 +447,8 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		bool		warm_update;
+		Bitmapset  *modified_attrs;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -455,13 +459,35 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+						   slot->tts_tuple, &modified_attrs, &warm_update);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
+			(!HeapTupleIsHeapOnly(slot->tts_tuple) || warm_update))
+		{
+			ItemPointerData root_tid;
+
+			/*
+			 * If we did a WARM update then we must index the tuple using its
+			 * root line pointer and not the tuple TID itself.
+			 */
+			if (warm_update)
+				ItemPointerSet(&root_tid,
+						ItemPointerGetBlockNumber(&(tuple->t_self)),
+						HeapTupleHeaderGetRootOffset(tuple->t_data));
+			else
+			{
+				ItemPointerCopy(&tuple->t_self,
+						&root_tid);
+				bms_free(modified_attrs);
+				modified_attrs = NULL;
+			}
+
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												   &root_tid,
+												   modified_attrs,
 												   estate, false, NULL,
 												   NIL);
+		}
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index d240f9c..0a8c2eb 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -39,6 +39,7 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "access/valid.h"
 #include "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
 #include "pgstat.h"
@@ -395,11 +396,21 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			OffsetNumber offnum = tbmres->offsets[curslot];
 			ItemPointerData tid;
 			HeapTupleData heapTuple;
+			HeapCheckWarmChainStatus status = 0;
 
 			ItemPointerSet(&tid, page, offnum);
 			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
+									   &heapTuple, NULL, true, &status))
+			{
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+
+				/*
+				 * If the heap tuple needs a recheck because of a WARM update,
+				 * it's a lossy case.
+				 */
+				if (HCWC_IS_WARM_UPDATED(status))
+					tbmres->recheck = true;
+			}
 		}
 	}
 	else
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 5afd02e..6e48c2e 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -142,8 +142,8 @@ IndexNext(IndexScanState *node)
 					   false);	/* don't pfree */
 
 		/*
-		 * If the index was lossy, we have to recheck the index quals using
-		 * the fetched tuple.
+		 * If the index was lossy or the tuple was WARM, we have to recheck
+		 * the index quals using the fetched tuple.
 		 */
 		if (scandesc->xs_recheck)
 		{
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0b524e0..2ad4a2c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -513,6 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
 
 			/* insert index entries for tuple */
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												 &(tuple->t_self), NULL,
 												 estate, true, &specConflict,
 												   arbiterIndexes);
 
@@ -559,6 +560,7 @@ ExecInsert(ModifyTableState *mtstate,
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
 				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+													   &(tuple->t_self), NULL,
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -892,6 +894,9 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	Bitmapset  *modified_attrs = NULL;
+	ItemPointerData	root_tid;
+	bool		warm_update;
 
 	/*
 	 * abort the operation if not running transactions
@@ -1008,7 +1013,7 @@ lreplace:;
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+							 &hufd, &lockmode, &modified_attrs, &warm_update);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1095,10 +1100,28 @@ lreplace:;
 		 * the t_self field.
 		 *
 		 * If it's a HOT update, we mustn't insert new index entries.
+		 *
+		 * If it's a WARM update, then we must insert new entries with TID
+		 * pointing to the root of the WARM chain.
 		 */
-		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
+		if (resultRelInfo->ri_NumIndices > 0 &&
+			(!HeapTupleIsHeapOnly(tuple) || warm_update))
+		{
+			if (warm_update)
+				ItemPointerSet(&root_tid,
+						ItemPointerGetBlockNumber(&(tuple->t_self)),
+						HeapTupleHeaderGetRootOffset(tuple->t_data));
+			else
+			{
+				ItemPointerCopy(&tuple->t_self, &root_tid);
+				bms_free(modified_attrs);
+				modified_attrs = NULL;
+			}
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												   &root_tid,
+												   modified_attrs,
 												   estate, false, NULL, NIL);
+		}
 	}
 
 	if (canSetTag)
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 56a8bf2..52fe4ba 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -1888,7 +1888,7 @@ pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
  * pgstat_count_heap_update - count a tuple update
  */
 void
-pgstat_count_heap_update(Relation rel, bool hot)
+pgstat_count_heap_update(Relation rel, bool hot, bool warm)
 {
 	PgStat_TableStatus *pgstat_info = rel->pgstat_info;
 
@@ -1906,6 +1906,8 @@ pgstat_count_heap_update(Relation rel, bool hot)
 		/* t_tuples_hot_updated is nontransactional, so just advance it */
 		if (hot)
 			pgstat_info->t_counts.t_tuples_hot_updated++;
+		else if (warm)
+			pgstat_info->t_counts.t_tuples_warm_updated++;
 	}
 }
 
@@ -4521,6 +4523,7 @@ pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry, Oid tableoid, bool create)
 		result->tuples_updated = 0;
 		result->tuples_deleted = 0;
 		result->tuples_hot_updated = 0;
+		result->tuples_warm_updated = 0;
 		result->n_live_tuples = 0;
 		result->n_dead_tuples = 0;
 		result->changes_since_analyze = 0;
@@ -5630,6 +5633,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 			tabentry->tuples_updated = tabmsg->t_counts.t_tuples_updated;
 			tabentry->tuples_deleted = tabmsg->t_counts.t_tuples_deleted;
 			tabentry->tuples_hot_updated = tabmsg->t_counts.t_tuples_hot_updated;
+			tabentry->tuples_warm_updated = tabmsg->t_counts.t_tuples_warm_updated;
 			tabentry->n_live_tuples = tabmsg->t_counts.t_delta_live_tuples;
 			tabentry->n_dead_tuples = tabmsg->t_counts.t_delta_dead_tuples;
 			tabentry->changes_since_analyze = tabmsg->t_counts.t_changed_tuples;
@@ -5657,6 +5661,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 			tabentry->tuples_updated += tabmsg->t_counts.t_tuples_updated;
 			tabentry->tuples_deleted += tabmsg->t_counts.t_tuples_deleted;
 			tabentry->tuples_hot_updated += tabmsg->t_counts.t_tuples_hot_updated;
+			tabentry->tuples_warm_updated += tabmsg->t_counts.t_tuples_warm_updated;
 			/* If table was truncated, first reset the live/dead counters */
 			if (tabmsg->t_counts.t_truncated)
 			{
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 5c13d26..7a9b48a 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -347,7 +347,7 @@ DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 static void
 DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 {
-	uint8		info = XLogRecGetInfo(buf->record) & XLOG_HEAP_OPMASK;
+	uint8		info = XLogRecGetInfo(buf->record) & XLOG_HEAP2_OPMASK;
 	TransactionId xid = XLogRecGetXid(buf->record);
 	SnapBuild  *builder = ctx->snapshot_builder;
 
@@ -359,10 +359,6 @@ DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	switch (info)
 	{
-		case XLOG_HEAP2_MULTI_INSERT:
-			if (SnapBuildProcessChange(builder, xid, buf->origptr))
-				DecodeMultiInsert(ctx, buf);
-			break;
 		case XLOG_HEAP2_NEW_CID:
 			{
 				xl_heap_new_cid *xlrec;
@@ -390,6 +386,7 @@ DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		case XLOG_HEAP2_CLEANUP_INFO:
 		case XLOG_HEAP2_VISIBLE:
 		case XLOG_HEAP2_LOCK_UPDATED:
+		case XLOG_HEAP2_WARMCLEAR:
 			break;
 		default:
 			elog(ERROR, "unexpected RM_HEAP2_ID record type: %u", info);
@@ -418,6 +415,10 @@ DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 			if (SnapBuildProcessChange(builder, xid, buf->origptr))
 				DecodeInsert(ctx, buf);
 			break;
+		case XLOG_HEAP_MULTI_INSERT:
+			if (SnapBuildProcessChange(builder, xid, buf->origptr))
+				DecodeMultiInsert(ctx, buf);
+			break;
 
 			/*
 			 * Treat HOT update as normal updates. There is no useful
@@ -809,7 +810,7 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 }
 
 /*
- * Decode XLOG_HEAP2_MULTI_INSERT_insert record into multiple tuplebufs.
+ * Decode XLOG_HEAP_MULTI_INSERT_insert record into multiple tuplebufs.
  *
  * Currently MULTI_INSERT will always contain the full tuples.
  */
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index fdf045a..8d23e92 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -1151,6 +1151,29 @@ PageIndexTupleOverwrite(Page page, OffsetNumber offnum,
 	return true;
 }
 
+/*
+ * PageIndexClearWarmTuples
+ *
+ * Clear the given WARM pointers by resetting the flags stored in the TID
+ * field. We assume there is nothing else in the TID flags other than the WARM
+ * information and clearing all flag bits is safe. If that changes, we must
+ * change this routine as well.
+ */
+void
+PageIndexClearWarmTuples(Page page, OffsetNumber *clearitemnos,
+						 uint16 nclearitems)
+{
+	int			i;
+	ItemId		itemid;
+	IndexTuple	itup;
+
+	for (i = 0; i < nclearitems; i++)
+	{
+		itemid = PageGetItemId(page, clearitemnos[i]);
+		itup = (IndexTuple) PageGetItem(page, itemid);
+		ItemPointerClearFlags(&itup->t_tid);
+	}
+}
 
 /*
  * Set checksum for a page in shared buffers.
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index e0cae1b..227a87d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -147,6 +147,22 @@ pg_stat_get_tuples_hot_updated(PG_FUNCTION_ARGS)
 
 
 Datum
+pg_stat_get_tuples_warm_updated(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *tabentry;
+
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (tabentry->tuples_warm_updated);
+
+	PG_RETURN_INT64(result);
+}
+
+
+Datum
 pg_stat_get_live_tuples(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
@@ -1674,6 +1690,21 @@ pg_stat_get_xact_tuples_hot_updated(PG_FUNCTION_ARGS)
 }
 
 Datum
+pg_stat_get_xact_tuples_warm_updated(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_TableStatus *tabentry;
+
+	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (tabentry->t_counts.t_tuples_warm_updated);
+
+	PG_RETURN_INT64(result);
+}
+
+Datum
 pg_stat_get_xact_blocks_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bc22098..7bf6c38 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2339,6 +2339,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	list_free_deep(relation->rd_fkeylist);
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
+	bms_free(relation->rd_exprindexattr);
 	bms_free(relation->rd_keyattr);
 	bms_free(relation->rd_pkattr);
 	bms_free(relation->rd_idattr);
@@ -4353,6 +4354,13 @@ RelationGetIndexList(Relation relation)
 		return list_copy(relation->rd_indexlist);
 
 	/*
+	 * If the index list was invalidated, we better also invalidate the index
+	 * attribute list (which should automatically invalidate other attributes
+	 * such as primary key and replica identity)
+	 */
+	relation->rd_indexattr = NULL;
+
+	/*
 	 * We build the list we intend to return (in the caller's context) while
 	 * doing the scan.  After successfully completing the scan, we copy that
 	 * list into the relcache entry.  This avoids cache-context memory leakage
@@ -4836,15 +4844,20 @@ Bitmapset *
 RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
 	Bitmapset  *indexattrs;		/* indexed columns */
+	Bitmapset  *exprindexattrs;	/* indexed columns in expression/prediacate
+									 indexes */
 	Bitmapset  *uindexattrs;	/* columns in unique indexes */
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
+	Bitmapset  *indxnotreadyattrs;	/* columns in not ready indexes */
 	List	   *indexoidlist;
 	List	   *newindexoidlist;
+	List	   *indexattrsList;
 	Oid			relpkindex;
 	Oid			relreplindex;
 	ListCell   *l;
 	MemoryContext oldcxt;
+	bool		supportswarm = true;/* True if the table can be WARM updated */
 
 	/* Quick exit if we already computed the result. */
 	if (relation->rd_indexattr != NULL)
@@ -4859,6 +4872,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_pkattr);
 			case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 				return bms_copy(relation->rd_idattr);
+			case INDEX_ATTR_BITMAP_EXPR_PREDICATE:
+				return bms_copy(relation->rd_exprindexattr);
+			case INDEX_ATTR_BITMAP_NOTREADY:
+				return bms_copy(relation->rd_indxnotreadyattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -4899,9 +4916,12 @@ restart:
 	 * won't be returned at all by RelationGetIndexList.
 	 */
 	indexattrs = NULL;
+	exprindexattrs = NULL;
 	uindexattrs = NULL;
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
+	indxnotreadyattrs = NULL;
+	indexattrsList = NIL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -4911,6 +4931,7 @@ restart:
 		bool		isKey;		/* candidate key */
 		bool		isPK;		/* primary key */
 		bool		isIDKey;	/* replica identity index */
+		Bitmapset	*thisindexattrs = NULL;
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -4935,9 +4956,16 @@ restart:
 
 			if (attrnum != 0)
 			{
+				thisindexattrs = bms_add_member(thisindexattrs,
+							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
 				indexattrs = bms_add_member(indexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
 
+				if (!indexInfo->ii_ReadyForInserts)
+					indxnotreadyattrs = bms_add_member(indxnotreadyattrs,
+							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
 				if (isKey)
 					uindexattrs = bms_add_member(uindexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
@@ -4953,10 +4981,31 @@ restart:
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &exprindexattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
+		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &exprindexattrs);
+
+		/*
+		 * indexattrs should include attributes referenced in index expressions
+		 * and predicates too.
+		 */
+		indexattrs = bms_add_members(indexattrs, exprindexattrs);
+		thisindexattrs = bms_add_members(thisindexattrs, exprindexattrs);
+
+		if (!indexInfo->ii_ReadyForInserts)
+			indxnotreadyattrs = bms_add_members(indxnotreadyattrs,
+					exprindexattrs);
+
+		/*
+		 * Check if the index has amrecheck method defined. If the method is
+		 * not defined, the index does not support WARM update. Completely
+		 * disable WARM updates on such tables.
+		 */
+		if (!indexDesc->rd_amroutine->amrecheck)
+			supportswarm = false;
+
+		indexattrsList = lappend(indexattrsList, thisindexattrs);
 
 		index_close(indexDesc, AccessShareLock);
 	}
@@ -4985,19 +5034,28 @@ restart:
 		bms_free(pkindexattrs);
 		bms_free(idindexattrs);
 		bms_free(indexattrs);
-
+		list_free_deep(indexattrsList);
 		goto restart;
 	}
 
+	/* Remember if the table can do WARM updates */
+	relation->rd_supportswarm = (RelationWarmUpdatesEnabled(relation) && supportswarm);
+
 	/* Don't leak the old values of these bitmaps, if any */
 	bms_free(relation->rd_indexattr);
 	relation->rd_indexattr = NULL;
+	bms_free(relation->rd_exprindexattr);
+	relation->rd_exprindexattr = NULL;
 	bms_free(relation->rd_keyattr);
 	relation->rd_keyattr = NULL;
 	bms_free(relation->rd_pkattr);
 	relation->rd_pkattr = NULL;
 	bms_free(relation->rd_idattr);
 	relation->rd_idattr = NULL;
+	bms_free(relation->rd_indxnotreadyattr);
+	relation->rd_indxnotreadyattr = NULL;
+	list_free_deep(relation->rd_indexattrsList);
+	relation->rd_indexattrsList = NIL;
 
 	/*
 	 * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5010,7 +5068,21 @@ restart:
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_pkattr = bms_copy(pkindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
-	relation->rd_indexattr = bms_copy(indexattrs);
+	relation->rd_exprindexattr = bms_copy(exprindexattrs);
+	relation->rd_indexattr = bms_copy(bms_union(indexattrs, exprindexattrs));
+	relation->rd_indxnotreadyattr = bms_copy(indxnotreadyattrs);
+
+	/*
+	 * create a deep copy of the list, copying each bitmap in the
+	 * CurrentMemoryContext.
+	 */
+	foreach(l, indexattrsList)
+	{
+		Bitmapset *b = (Bitmapset *) lfirst(l);
+		relation->rd_indexattrsList = lappend(relation->rd_indexattrsList,
+				bms_copy(b));
+	}
+
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
@@ -5024,6 +5096,10 @@ restart:
 			return bms_copy(relation->rd_pkattr);
 		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 			return idindexattrs;
+		case INDEX_ATTR_BITMAP_EXPR_PREDICATE:
+			return exprindexattrs;
+		case INDEX_ATTR_BITMAP_NOTREADY:
+			return indxnotreadyattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
@@ -5031,6 +5107,34 @@ restart:
 }
 
 /*
+ * Get a list of bitmaps, where each bitmap contains a list of attributes used
+ * by one index.
+ *
+ * The actual information is computed in RelationGetIndexAttrBitmap, but
+ * currently the only consumer of this function calls it immediately after
+ * calling RelationGetIndexAttrBitmap, we should be fine. We don't expect any
+ * relcache invalidation to come between these two calls and hence don't expect
+ * the cached information to change underneath.
+ */
+List *
+RelationGetIndexAttrList(Relation relation)
+{
+	ListCell   *l;
+	List	   *indexattrsList = NIL;
+
+	/*
+	 * Create a deep copy of the list by copying bitmaps in the
+	 * CurrentMemoryContext.
+	 */
+	foreach(l, relation->rd_indexattrsList)
+	{
+		Bitmapset *b = (Bitmapset *) lfirst(l);
+		indexattrsList = lappend(indexattrsList, bms_copy(b));
+	}
+	return indexattrsList;
+}
+
+/*
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
@@ -5636,6 +5740,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_keyattr = NULL;
 		rel->rd_pkattr = NULL;
 		rel->rd_idattr = NULL;
+		rel->rd_indxnotreadyattr = NULL;
 		rel->rd_pubactions = NULL;
 		rel->rd_statvalid = false;
 		rel->rd_statlist = NIL;
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index baff998..6a2e2f2 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -106,7 +106,7 @@ HeapTupleHeaderGetCmin(HeapTupleHeader tup)
 {
 	CommandId	cid = HeapTupleHeaderGetRawCommandId(tup);
 
-	Assert(!(tup->t_infomask & HEAP_MOVED));
+	Assert(!(HeapTupleHeaderIsMoved(tup)));
 	Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
 
 	if (tup->t_infomask & HEAP_COMBOCID)
@@ -120,7 +120,7 @@ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
 {
 	CommandId	cid = HeapTupleHeaderGetRawCommandId(tup);
 
-	Assert(!(tup->t_infomask & HEAP_MOVED));
+	Assert(!(HeapTupleHeaderIsMoved(tup)));
 
 	/*
 	 * Because GetUpdateXid() performs memory allocations if xmax is a
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 519f3b6..e54d0df 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -186,7 +186,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderIsMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -205,7 +205,7 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderIsMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -377,7 +377,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderIsMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -396,7 +396,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderIsMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -471,7 +471,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
 			return HeapTupleInvisible;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderIsMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -490,7 +490,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderIsMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -753,7 +753,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderIsMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -772,7 +772,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderIsMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -974,7 +974,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 			return false;
 
 		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		if (HeapTupleHeaderIsMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -993,7 +993,7 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 			}
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderIsMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -1180,7 +1180,7 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 		if (HeapTupleHeaderXminInvalid(tuple))
 			return HEAPTUPLE_DEAD;
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		else if (HeapTupleHeaderIsMovedOff(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
@@ -1198,7 +1198,7 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 						InvalidTransactionId);
 		}
 		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		else if (HeapTupleHeaderIsMovedIn(tuple))
 		{
 			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
 
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index f919cf8..b33bcda 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -13,6 +13,7 @@
 #define AMAPI_H
 
 #include "access/genam.h"
+#include "access/itup.h"
 
 /*
  * We don't wish to include planner header files here, since most of an index
@@ -74,6 +75,14 @@ typedef bool (*aminsert_function) (Relation indexRelation,
 											   Relation heapRelation,
 											   IndexUniqueCheck checkUnique,
 											   struct IndexInfo *indexInfo);
+/* insert this WARM tuple */
+typedef bool (*amwarminsert_function) (Relation indexRelation,
+											   Datum *values,
+											   bool *isnull,
+											   ItemPointer heap_tid,
+											   Relation heapRelation,
+											   IndexUniqueCheck checkUnique,
+											   struct IndexInfo *indexInfo);
 
 /* bulk delete */
 typedef IndexBulkDeleteResult *(*ambulkdelete_function) (IndexVacuumInfo *info,
@@ -152,6 +161,14 @@ typedef void (*aminitparallelscan_function) (void *target);
 /* (re)start parallel index scan */
 typedef void (*amparallelrescan_function) (IndexScanDesc scan);
 
+/* recheck index tuple and heap tuple match */
+typedef bool (*amrecheck_function) (Relation indexRel,
+		struct IndexInfo *indexInfo, IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple);
+
+/* return true if the given index tuple is a WARM tuple */
+typedef bool (*amiswarm_function) (Relation indexRel, IndexTuple indexTuple);
+
 /*
  * API struct for an index AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -198,6 +215,7 @@ typedef struct IndexAmRoutine
 	ambuild_function ambuild;
 	ambuildempty_function ambuildempty;
 	aminsert_function aminsert;
+	amwarminsert_function amwarminsert;
 	ambulkdelete_function ambulkdelete;
 	amvacuumcleanup_function amvacuumcleanup;
 	amcanreturn_function amcanreturn;	/* can be NULL */
@@ -217,6 +235,10 @@ typedef struct IndexAmRoutine
 	amestimateparallelscan_function amestimateparallelscan;		/* can be NULL */
 	aminitparallelscan_function aminitparallelscan;		/* can be NULL */
 	amparallelrescan_function amparallelrescan; /* can be NULL */
+
+	/* interface function to support WARM */
+	amrecheck_function amrecheck;		/* can be NULL */
+	amiswarm_function  amiswarm;
 } IndexAmRoutine;
 
 
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index f467b18..965be45 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -75,12 +75,29 @@ typedef struct IndexBulkDeleteResult
 	bool		estimated_count;	/* num_index_tuples is an estimate */
 	double		num_index_tuples;		/* tuples remaining */
 	double		tuples_removed; /* # removed during vacuum operation */
+	double		num_warm_pointers;	/* # WARM pointers found */
+	double		num_clear_pointers;	/* # CLEAR pointers found */
+	double		pointers_cleared;	/* # WARM pointers cleared */
+	double		warm_pointers_removed;	/* # WARM pointers removed */
+	double		clear_pointers_removed;	/* # CLEAR pointers removed */
 	BlockNumber pages_deleted;	/* # unused pages in index */
 	BlockNumber pages_free;		/* # pages available for reuse */
 } IndexBulkDeleteResult;
 
+/*
+ * IndexBulkDeleteCallback should return one of the following
+ */
+typedef enum IndexBulkDeleteCallbackResult
+{
+	IBDCR_KEEP,			/* index tuple should be preserved */
+	IBDCR_DELETE,		/* index tuple should be deleted */
+	IBDCR_CLEAR_WARM	/* index tuple should be cleared of WARM bit */
+} IndexBulkDeleteCallbackResult;
+
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef IndexBulkDeleteCallbackResult (*IndexBulkDeleteCallback) (
+										 ItemPointer itemptr,
+										 bool is_warm, void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
@@ -135,7 +152,8 @@ extern bool index_insert(Relation indexRelation,
 			 ItemPointer heap_t_ctid,
 			 Relation heapRelation,
 			 IndexUniqueCheck checkUnique,
-			 struct IndexInfo *indexInfo);
+			 struct IndexInfo *indexInfo,
+			 bool warm_update);
 
 extern IndexScanDesc index_beginscan(Relation heapRelation,
 				Relation indexRelation,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 5540e12..1d79467 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -72,6 +72,20 @@ typedef struct HeapUpdateFailureData
 	CommandId	cmax;
 } HeapUpdateFailureData;
 
+typedef int HeapCheckWarmChainStatus;
+
+#define HCWC_CLEAR_TUPLE		0x0001
+#define	HCWC_WARM_TUPLE			0x0002
+#define HCWC_WARM_UPDATED_TUPLE	0x0004
+
+#define HCWC_IS_MIXED(status) \
+	(((status) & (HCWC_CLEAR_TUPLE | HCWC_WARM_TUPLE)) != 0)
+#define HCWC_IS_ALL_WARM(status) \
+	(((status) & HCWC_CLEAR_TUPLE) == 0)
+#define HCWC_IS_ALL_CLEAR(status) \
+	(((status) & HCWC_WARM_TUPLE) == 0)
+#define HCWC_IS_WARM_UPDATED(status) \
+	(((status) & HCWC_WARM_UPDATED_TUPLE) != 0)
 
 /* ----------------
  *		function prototypes for heap access method
@@ -137,9 +151,11 @@ extern bool heap_fetch(Relation relation, Snapshot snapshot,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
+					   bool *all_dead, bool first_call,
+					   HeapCheckWarmChainStatus *status);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+				Snapshot snapshot, bool *all_dead,
+				bool *recheck, Buffer *buffer, HeapTuple heapTuple);
 
 extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 					ItemPointer tid);
@@ -161,7 +177,8 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			Bitmapset **modified_attrsp, bool *warm_update);
 extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
@@ -176,10 +193,16 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern Oid	simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-				   HeapTuple tup);
+				   HeapTuple tup,
+				   Bitmapset **modified_attrs,
+				   bool *warm_update);
 
 extern void heap_sync(Relation relation);
 extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern HeapCheckWarmChainStatus heap_check_warm_chain(Page dp,
+				   ItemPointer tid, bool stop_at_warm);
+extern int heap_clear_warm_chain(Page dp, ItemPointer tid,
+				   OffsetNumber *cleared_offnums);
 
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index e6019d5..66fd0ea 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -32,7 +32,7 @@
 #define XLOG_HEAP_INSERT		0x00
 #define XLOG_HEAP_DELETE		0x10
 #define XLOG_HEAP_UPDATE		0x20
-/* 0x030 is free, was XLOG_HEAP_MOVE */
+#define XLOG_HEAP_MULTI_INSERT	0x30
 #define XLOG_HEAP_HOT_UPDATE	0x40
 #define XLOG_HEAP_CONFIRM		0x50
 #define XLOG_HEAP_LOCK			0x60
@@ -47,18 +47,23 @@
 /*
  * We ran out of opcodes, so heapam.c now has a second RmgrId.  These opcodes
  * are associated with RM_HEAP2_ID, but are not logically different from
- * the ones above associated with RM_HEAP_ID.  XLOG_HEAP_OPMASK applies to
- * these, too.
+ * the ones above associated with RM_HEAP_ID.
+ *
+ * In PG 10, we moved XLOG_HEAP2_MULTI_INSERT to RM_HEAP_ID. That allows us to
+ * use 0x80 bit in RM_HEAP2_ID, thus potentially creating another 8 possible
+ * opcodes in RM_HEAP2_ID.
  */
 #define XLOG_HEAP2_REWRITE		0x00
 #define XLOG_HEAP2_CLEAN		0x10
 #define XLOG_HEAP2_FREEZE_PAGE	0x20
 #define XLOG_HEAP2_CLEANUP_INFO 0x30
 #define XLOG_HEAP2_VISIBLE		0x40
-#define XLOG_HEAP2_MULTI_INSERT 0x50
+#define XLOG_HEAP2_WARMCLEAR	0x50
 #define XLOG_HEAP2_LOCK_UPDATED 0x60
 #define XLOG_HEAP2_NEW_CID		0x70
 
+#define XLOG_HEAP2_OPMASK		0x70
+
 /*
  * xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available.
  */
@@ -80,6 +85,7 @@
 #define XLH_UPDATE_CONTAINS_NEW_TUPLE			(1<<4)
 #define XLH_UPDATE_PREFIX_FROM_OLD				(1<<5)
 #define XLH_UPDATE_SUFFIX_FROM_OLD				(1<<6)
+#define XLH_UPDATE_WARM_UPDATE					(1<<7)
 
 /* convenience macro for checking whether any form of old tuple was logged */
 #define XLH_UPDATE_CONTAINS_OLD						\
@@ -225,6 +231,14 @@ typedef struct xl_heap_clean
 
 #define SizeOfHeapClean (offsetof(xl_heap_clean, ndead) + sizeof(uint16))
 
+typedef struct xl_heap_warmclear
+{
+	uint16		ncleared;
+	/* OFFSET NUMBERS are in the block reference 0 */
+} xl_heap_warmclear;
+
+#define SizeOfHeapWarmClear (offsetof(xl_heap_warmclear, ncleared) + sizeof(uint16))
+
 /*
  * Cleanup_info is required in some cases during a lazy VACUUM.
  * Used for reporting the results of HeapTupleHeaderAdvanceLatestRemovedXid()
@@ -388,6 +402,8 @@ extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
 			   OffsetNumber *nowdead, int ndead,
 			   OffsetNumber *nowunused, int nunused,
 			   TransactionId latestRemovedXid);
+extern XLogRecPtr log_heap_warmclear(Relation reln, Buffer buffer,
+			   OffsetNumber *cleared, int ncleared);
 extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
 				TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
 				int ntuples);
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 4d614b7..bcefba6 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -201,6 +201,21 @@ struct HeapTupleHeaderData
 										 * upgrade support */
 #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
 
+/*
+ * A WARM chain usually consists of two parts. Each of these parts are HOT
+ * chains in themselves i.e. all indexed columns has the same value, but a WARM
+ * update separates these parts. We need a mechanism to identify which part a
+ * tuple belongs to. We can't just look at if it's a
+ * HeapTupleHeaderIsWarmUpdated() because during WARM update, both old and new
+ * tuples are marked as WARM tuples.
+ *
+ * We need another infomask bit for this. But we use the same infomask bit that
+ * was earlier used for by old-style VACUUM FULL. This is safe because
+ * HEAP_WARM_TUPLE flag will always be set along with HEAP_WARM_UPDATED. So if
+ * HEAP_WARM_TUPLE and HEAP_WARM_UPDATED is set then we know that it's
+ * referring to red part of the WARM chain.
+ */
+#define HEAP_WARM_TUPLE			0x4000
 #define HEAP_XACT_MASK			0xFFF0	/* visibility-related bits */
 
 /*
@@ -260,7 +275,11 @@ struct HeapTupleHeaderData
  * information stored in t_infomask2:
  */
 #define HEAP_NATTS_MASK			0x07FF	/* 11 bits for number of attributes */
-/* bits 0x0800 are available */
+#define HEAP_WARM_UPDATED		0x0800	/*
+										 * This or a prior version of this
+										 * tuple in the current HOT chain was
+										 * once WARM updated
+										 */
 #define HEAP_LATEST_TUPLE		0x1000	/*
 										 * This is the last tuple in chain and
 										 * ip_posid points to the root line
@@ -271,7 +290,7 @@ struct HeapTupleHeaderData
 #define HEAP_HOT_UPDATED		0x4000	/* tuple was HOT-updated */
 #define HEAP_ONLY_TUPLE			0x8000	/* this is heap-only tuple */
 
-#define HEAP2_XACT_MASK			0xF000	/* visibility-related bits */
+#define HEAP2_XACT_MASK			0xF800	/* visibility-related bits */
 
 
 /*
@@ -396,7 +415,7 @@ struct HeapTupleHeaderData
 /* SetCmin is reasonably simple since we never need a combo CID */
 #define HeapTupleHeaderSetCmin(tup, cid) \
 do { \
-	Assert(!((tup)->t_infomask & HEAP_MOVED)); \
+	Assert(!HeapTupleHeaderIsMoved(tup)); \
 	(tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
 	(tup)->t_infomask &= ~HEAP_COMBOCID; \
 } while (0)
@@ -404,7 +423,7 @@ do { \
 /* SetCmax must be used after HeapTupleHeaderAdjustCmax; see combocid.c */
 #define HeapTupleHeaderSetCmax(tup, cid, iscombo) \
 do { \
-	Assert(!((tup)->t_infomask & HEAP_MOVED)); \
+	Assert(!HeapTupleHeaderIsMoved(tup)); \
 	(tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
 	if (iscombo) \
 		(tup)->t_infomask |= HEAP_COMBOCID; \
@@ -414,7 +433,7 @@ do { \
 
 #define HeapTupleHeaderGetXvac(tup) \
 ( \
-	((tup)->t_infomask & HEAP_MOVED) ? \
+	HeapTupleHeaderIsMoved(tup) ? \
 		(tup)->t_choice.t_heap.t_field3.t_xvac \
 	: \
 		InvalidTransactionId \
@@ -422,7 +441,7 @@ do { \
 
 #define HeapTupleHeaderSetXvac(tup, xid) \
 do { \
-	Assert((tup)->t_infomask & HEAP_MOVED); \
+	Assert(HeapTupleHeaderIsMoved(tup)); \
 	(tup)->t_choice.t_heap.t_field3.t_xvac = (xid); \
 } while (0)
 
@@ -510,6 +529,21 @@ do { \
   ((tup)->t_infomask2 & HEAP_ONLY_TUPLE) != 0 \
 )
 
+#define HeapTupleHeaderSetWarmUpdated(tup) \
+do { \
+	(tup)->t_infomask2 |= HEAP_WARM_UPDATED; \
+} while (0)
+
+#define HeapTupleHeaderClearWarmUpdated(tup) \
+do { \
+	(tup)->t_infomask2 &= ~HEAP_WARM_UPDATED; \
+} while (0)
+
+#define HeapTupleHeaderIsWarmUpdated(tup) \
+( \
+  ((tup)->t_infomask2 & HEAP_WARM_UPDATED) != 0 \
+)
+
 /*
  * Mark this as the last tuple in the HOT chain. Before PG v10 we used to store
  * the TID of the tuple itself in t_ctid field to mark the end of the chain.
@@ -635,6 +669,58 @@ do { \
 )
 
 /*
+ * Macros to check if tuple is a moved-off/in tuple by VACUUM FULL in from
+ * pre-9.0 era. Such tuple must not have HEAP_WARM_TUPLE flag set.
+ *
+ * Beware of multiple evaluations of the argument.
+ */
+#define HeapTupleHeaderIsMovedOff(tuple) \
+( \
+	!HeapTupleHeaderIsWarmUpdated((tuple)) && \
+	((tuple)->t_infomask & HEAP_MOVED_OFF) \
+)
+
+#define HeapTupleHeaderIsMovedIn(tuple) \
+( \
+	!HeapTupleHeaderIsWarmUpdated((tuple)) && \
+	((tuple)->t_infomask & HEAP_MOVED_IN) \
+)
+
+#define HeapTupleHeaderIsMoved(tuple) \
+( \
+	!HeapTupleHeaderIsWarmUpdated((tuple)) && \
+	((tuple)->t_infomask & HEAP_MOVED) \
+)
+
+/*
+ * Check if tuple belongs to the second part of the WARM chain.
+ *
+ * Beware of multiple evaluations of the argument.
+ */
+#define HeapTupleHeaderIsWarm(tuple) \
+( \
+	HeapTupleHeaderIsWarmUpdated(tuple) && \
+	(((tuple)->t_infomask & HEAP_WARM_TUPLE) != 0) \
+)
+
+/*
+ * Mark tuple as a member of the second part of the chain. Must only be done on
+ * a tuple which is already marked a WARM-tuple.
+ *
+ * Beware of multiple evaluations of the argument.
+ */
+#define HeapTupleHeaderSetWarm(tuple) \
+( \
+	AssertMacro(HeapTupleHeaderIsWarmUpdated(tuple)), \
+	(tuple)->t_infomask |= HEAP_WARM_TUPLE \
+)
+
+#define HeapTupleHeaderClearWarm(tuple) \
+( \
+	(tuple)->t_infomask &= ~HEAP_WARM_TUPLE \
+)
+
+/*
  * BITMAPLEN(NATTS) -
  *		Computes size of null bitmap given number of data columns.
  */
@@ -785,6 +871,24 @@ struct MinimalTupleData
 #define HeapTupleClearHeapOnly(tuple) \
 		HeapTupleHeaderClearHeapOnly((tuple)->t_data)
 
+#define HeapTupleIsWarmUpdated(tuple) \
+		HeapTupleHeaderIsWarmUpdated((tuple)->t_data)
+
+#define HeapTupleSetWarmUpdated(tuple) \
+		HeapTupleHeaderSetWarmUpdated((tuple)->t_data)
+
+#define HeapTupleClearWarmUpdated(tuple) \
+		HeapTupleHeaderClearWarmUpdated((tuple)->t_data)
+
+#define HeapTupleIsWarm(tuple) \
+		HeapTupleHeaderIsWarm((tuple)->t_data)
+
+#define HeapTupleSetWarm(tuple) \
+		HeapTupleHeaderSetWarm((tuple)->t_data)
+
+#define HeapTupleClearWarm(tuple) \
+		HeapTupleHeaderClearWarm((tuple)->t_data)
+
 #define HeapTupleGetOid(tuple) \
 		HeapTupleHeaderGetOid((tuple)->t_data)
 
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index f9304db..b319199 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -391,6 +391,9 @@ typedef struct BTScanOpaqueData
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
 
+	/* info about items that are marked as WARM */
+	int		   *setWarmItems;
+	int			numSet;
 	/*
 	 * If we are doing an index-only scan, these are the tuple storage
 	 * workspaces for the currPos and markPos respectively.  Each is of size
@@ -427,6 +430,12 @@ typedef BTScanOpaqueData *BTScanOpaque;
 #define SK_BT_NULLS_FIRST	(INDOPTION_NULLS_FIRST << SK_BT_INDOPTION_SHIFT)
 
 /*
+ * Flags overloaded on t_tid.ip_posid field. They are managed by
+ * ItemPointerSetFlags and corresponing routines.
+ */
+#define BTREE_INDEX_WARM_POINTER	0x01
+
+/*
  * external entry points for btree, in nbtree.c
  */
 extern IndexBuildResult *btbuild(Relation heap, Relation index,
@@ -436,6 +445,10 @@ extern bool btinsert(Relation rel, Datum *values, bool *isnull,
 		 ItemPointer ht_ctid, Relation heapRel,
 		 IndexUniqueCheck checkUnique,
 		 struct IndexInfo *indexInfo);
+extern bool btwarminsert(Relation rel, Datum *values, bool *isnull,
+		 ItemPointer ht_ctid, Relation heapRel,
+		 IndexUniqueCheck checkUnique,
+		 struct IndexInfo *indexInfo);
 extern IndexScanDesc btbeginscan(Relation rel, int nkeys, int norderbys);
 extern Size btestimateparallelscan(void);
 extern void btinitparallelscan(void *target);
@@ -487,10 +500,12 @@ extern void _bt_pageinit(Page page, Size size);
 extern bool _bt_page_recyclable(Page page);
 extern void _bt_delitems_delete(Relation rel, Buffer buf,
 					OffsetNumber *itemnos, int nitems, Relation heapRel);
-extern void _bt_delitems_vacuum(Relation rel, Buffer buf,
-					OffsetNumber *itemnos, int nitems,
-					BlockNumber lastBlockVacuumed);
+extern void _bt_handleitems_vacuum(Relation rel, Buffer buf,
+					OffsetNumber *delitemnos, int ndelitems,
+					OffsetNumber *clearitemnos, int nclearitems);
 extern int	_bt_pagedel(Relation rel, Buffer buf);
+extern void	_bt_clear_items(Page page, OffsetNumber *clearitemnos,
+					uint16 nclearitems);
 
 /*
  * prototypes for functions in nbtsearch.c
@@ -527,6 +542,7 @@ extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
 extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_warmitems(IndexScanDesc scan);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -537,6 +553,10 @@ extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
+extern bool btrecheck(Relation indexRel, struct IndexInfo *indexInfo,
+		IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple);
+extern bool btiswarm(Relation indexRel, IndexTuple itup);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/nbtxlog.h b/src/include/access/nbtxlog.h
index d6a3085..6a86628 100644
--- a/src/include/access/nbtxlog.h
+++ b/src/include/access/nbtxlog.h
@@ -142,7 +142,8 @@ typedef struct xl_btree_reuse_page
 /*
  * This is what we need to know about vacuum of individual leaf index tuples.
  * The WAL record can represent deletion of any number of index tuples on a
- * single index page when executed by VACUUM.
+ * single index page when executed by VACUUM. It also includes tuples which
+ * are cleared of WARM bits by VACUUM.
  *
  * For MVCC scans, lastBlockVacuumed will be set to InvalidBlockNumber.
  * For a non-MVCC index scans there is an additional correctness requirement
@@ -165,11 +166,12 @@ typedef struct xl_btree_reuse_page
 typedef struct xl_btree_vacuum
 {
 	BlockNumber lastBlockVacuumed;
-
-	/* TARGET OFFSET NUMBERS FOLLOW */
+	uint16		ndelitems;
+	uint16		nclearitems;
+	/* ndelitems + nclearitems TARGET OFFSET NUMBERS FOLLOW */
 } xl_btree_vacuum;
 
-#define SizeOfBtreeVacuum	(offsetof(xl_btree_vacuum, lastBlockVacuumed) + sizeof(BlockNumber))
+#define SizeOfBtreeVacuum	(offsetof(xl_btree_vacuum, nclearitems) + sizeof(uint16))
 
 /*
  * This is what we need to know about marking an empty branch for deletion.
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 3fc726d..c36a682 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -101,9 +101,15 @@ typedef struct IndexScanDescData
 	bool		xactStartedInRecovery;	/* prevents killing/seeing killed
 										 * tuples */
 
+	/* signaling to index AM about setting the index pointer WARM */
+	bool		warm_prior_tuple;
+
 	/* index access method's private state */
 	void	   *opaque;			/* access-method-specific info */
 
+	/* IndexInfo structure for this index */
+	struct IndexInfo  *indexInfo;
+
 	/*
 	 * In an index-only scan, a successful amgettuple call must fill either
 	 * xs_itup (and xs_itupdesc) or xs_hitup (and xs_hitupdesc) to provide the
@@ -119,7 +125,7 @@ typedef struct IndexScanDescData
 	HeapTupleData xs_ctup;		/* current heap tuple, if any */
 	Buffer		xs_cbuf;		/* current heap buffer in scan, if any */
 	/* NB: if xs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
-	bool		xs_recheck;		/* T means scan keys must be rechecked */
+	bool		xs_recheck;		/* T means scan keys must be rechecked for each tuple */
 
 	/*
 	 * When fetching with an ordering operator, the values of the ORDER BY
@@ -134,6 +140,7 @@ typedef struct IndexScanDescData
 
 	/* state data for traversing HOT chains in index_getnext */
 	bool		xs_continue_hot;	/* T if must keep walking HOT chain */
+	HeapCheckWarmChainStatus	xs_hot_chain_status;
 
 	/* parallel index scan information, in shared memory */
 	ParallelIndexScanDesc parallel_scan;
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 20bec90..f92ec29 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -89,6 +89,13 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
 			   Datum *values,
 			   bool *isnull);
 
+extern void FormIndexPlainDatum(IndexInfo *indexInfo,
+			   Relation heapRel,
+			   HeapTuple heapTup,
+			   Datum *values,
+			   bool *isnull,
+			   bool *isavail);
+
 extern void index_build(Relation heapRelation,
 			Relation indexRelation,
 			IndexInfo *indexInfo,
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 711211d..3f1a142 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2789,6 +2789,8 @@ DATA(insert OID = 1933 (  pg_stat_get_tuples_deleted	PGNSP PGUID 12 1 0 0 0 f f
 DESCR("statistics: number of tuples deleted");
 DATA(insert OID = 1972 (  pg_stat_get_tuples_hot_updated PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_tuples_hot_updated _null_ _null_ _null_ ));
 DESCR("statistics: number of tuples hot updated");
+DATA(insert OID = 3402 (  pg_stat_get_tuples_warm_updated PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_tuples_warm_updated _null_ _null_ _null_ ));
+DESCR("statistics: number of tuples warm updated");
 DATA(insert OID = 2878 (  pg_stat_get_live_tuples	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_live_tuples _null_ _null_ _null_ ));
 DESCR("statistics: number of live tuples");
 DATA(insert OID = 2879 (  pg_stat_get_dead_tuples	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_dead_tuples _null_ _null_ _null_ ));
@@ -2941,6 +2943,8 @@ DATA(insert OID = 3042 (  pg_stat_get_xact_tuples_deleted		PGNSP PGUID 12 1 0 0
 DESCR("statistics: number of tuples deleted in current transaction");
 DATA(insert OID = 3043 (  pg_stat_get_xact_tuples_hot_updated	PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_tuples_hot_updated _null_ _null_ _null_ ));
 DESCR("statistics: number of tuples hot updated in current transaction");
+DATA(insert OID = 3405 (  pg_stat_get_xact_tuples_warm_updated	PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_tuples_warm_updated _null_ _null_ _null_ ));
+DESCR("statistics: number of tuples warm updated in current transaction");
 DATA(insert OID = 3044 (  pg_stat_get_xact_blocks_fetched		PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_blocks_fetched _null_ _null_ _null_ ));
 DESCR("statistics: number of blocks fetched in current transaction");
 DATA(insert OID = 3045 (  pg_stat_get_xact_blocks_hit			PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_blocks_hit _null_ _null_ _null_ ));
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index 9472ecc..b355b61 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -25,6 +25,7 @@
 #define PROGRESS_VACUUM_NUM_INDEX_VACUUMS		4
 #define PROGRESS_VACUUM_MAX_DEAD_TUPLES			5
 #define PROGRESS_VACUUM_NUM_DEAD_TUPLES			6
+#define PROGRESS_VACUUM_HEAP_BLKS_WARMCLEARED	7
 
 /* Phases of vacuum (as advertised via PROGRESS_VACUUM_PHASE) */
 #define PROGRESS_VACUUM_PHASE_SCAN_HEAP			1
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d3849b9..7e1ec56 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -506,6 +506,7 @@ extern int	ExecCleanTargetListLength(List *targetlist);
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
+					  ItemPointer root_tid, Bitmapset *modified_attrs,
 					  EState *estate, bool noDupErr, bool *specConflict,
 					  List *arbiterIndexes);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
diff --git a/src/include/executor/nodeIndexscan.h b/src/include/executor/nodeIndexscan.h
index ea3f3a5..ebeec74 100644
--- a/src/include/executor/nodeIndexscan.h
+++ b/src/include/executor/nodeIndexscan.h
@@ -41,5 +41,4 @@ extern void ExecIndexEvalRuntimeKeys(ExprContext *econtext,
 extern bool ExecIndexEvalArrayKeys(ExprContext *econtext,
 					   IndexArrayKeyInfo *arrayKeys, int numArrayKeys);
 extern bool ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *arrayKeys, int numArrayKeys);
-
 #endif   /* NODEINDEXSCAN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fa99244..eed75a8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -133,6 +133,7 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;
 	AttrNumber	ii_KeyAttrNumbers[INDEX_MAX_KEYS];
+	Bitmapset  *ii_indxattrs;	/* bitmap of all columns used in this index */
 	List	   *ii_Expressions; /* list of Expr */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index e29397f..99bdc8b 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -105,6 +105,7 @@ typedef struct PgStat_TableCounts
 	PgStat_Counter t_tuples_updated;
 	PgStat_Counter t_tuples_deleted;
 	PgStat_Counter t_tuples_hot_updated;
+	PgStat_Counter t_tuples_warm_updated;
 	bool		t_truncated;
 
 	PgStat_Counter t_delta_live_tuples;
@@ -625,6 +626,7 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter tuples_updated;
 	PgStat_Counter tuples_deleted;
 	PgStat_Counter tuples_hot_updated;
+	PgStat_Counter tuples_warm_updated;
 
 	PgStat_Counter n_live_tuples;
 	PgStat_Counter n_dead_tuples;
@@ -1285,7 +1287,7 @@ pgstat_report_wait_end(void)
 	(pgStatBlockWriteTime += (n))
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
-extern void pgstat_count_heap_update(Relation rel, bool hot);
+extern void pgstat_count_heap_update(Relation rel, bool hot, bool warm);
 extern void pgstat_count_heap_delete(Relation rel);
 extern void pgstat_count_truncate(Relation rel);
 extern void pgstat_update_heap_dead_tuples(Relation rel, int delta);
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index e956dc3..1852195 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -433,6 +433,8 @@ extern void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems);
 extern void PageIndexTupleDeleteNoCompact(Page page, OffsetNumber offset);
 extern bool PageIndexTupleOverwrite(Page page, OffsetNumber offnum,
 						Item newtup, Size newsize);
+extern void PageIndexClearWarmTuples(Page page, OffsetNumber *clearitemnos,
+						uint16 nclearitems);
 extern char *PageSetChecksumCopy(Page page, BlockNumber blkno);
 extern void PageSetChecksumInplace(Page page, BlockNumber blkno);
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ab875bb..2b86054 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -142,9 +142,16 @@ typedef struct RelationData
 
 	/* data managed by RelationGetIndexAttrBitmap: */
 	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
+	Bitmapset  *rd_exprindexattr; /* indentified columns used in expression or
+									 predicate indexes */
+	Bitmapset  *rd_indxnotreadyattr;	/* columns used by indexes not yet
+										   ready */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
 	Bitmapset  *rd_pkattr;		/* cols included in primary key */
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
+	List	   *rd_indexattrsList;	/* List of bitmaps, describing list of
+									   attributes for each index */
+	bool		rd_supportswarm;/* True if the table can be WARM updated */
 
 	PublicationActions  *rd_pubactions;	/* publication actions */
 
@@ -281,6 +288,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table;		/* use as an additional catalog
 										 * relation */
 	int			parallel_workers;		/* max number of parallel workers */
+	bool		enable_warm;	/* should WARM be allowed on this table */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -319,6 +327,17 @@ typedef struct StdRdOptions
 	  (relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \
 	 ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false)
 
+#define HEAP_DEFAULT_ENABLE_WARM	true
+/*
+ * RelationWarmUpdatesEnabled
+ * 		Returns whether the relation supports WARM update.
+ */
+#define RelationWarmUpdatesEnabled(relation) \
+	(((relation)->rd_options && \
+	 (relation)->rd_rel->relkind == RELKIND_RELATION) ? \
+	 ((StdRdOptions *) ((relation)->rd_options))->enable_warm : \
+		HEAP_DEFAULT_ENABLE_WARM)
+
 /*
  * RelationGetParallelWorkers
  *		Returns the relation's parallel_workers reloption setting.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 81af3ae..06c0183 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -51,11 +51,14 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_ALL,
 	INDEX_ATTR_BITMAP_KEY,
 	INDEX_ATTR_BITMAP_PRIMARY_KEY,
-	INDEX_ATTR_BITMAP_IDENTITY_KEY
+	INDEX_ATTR_BITMAP_IDENTITY_KEY,
+	INDEX_ATTR_BITMAP_EXPR_PREDICATE,
+	INDEX_ATTR_BITMAP_NOTREADY
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
 						   IndexAttrBitmapKind keyAttrs);
+extern List *RelationGetIndexAttrList(Relation relation);
 
 extern void RelationGetExclusionInfo(Relation indexRelation,
 						 Oid **operators,
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index ce581bb..85e4c70 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -161,15 +161,15 @@ ALTER SERVER alt_fserv1 RENAME TO alt_fserv3;   -- OK
 SELECT fdwname FROM pg_foreign_data_wrapper WHERE fdwname like 'alt_fdw%';
  fdwname  
 ----------
- alt_fdw2
  alt_fdw3
+ alt_fdw2
 (2 rows)
 
 SELECT srvname FROM pg_foreign_server WHERE srvname like 'alt_fserv%';
   srvname   
 ------------
- alt_fserv2
  alt_fserv3
+ alt_fserv2
 (2 rows)
 
 --
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index d706f42..f7dc4a4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1756,6 +1756,7 @@ pg_stat_all_tables| SELECT c.oid AS relid,
     pg_stat_get_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_tuples_deleted(c.oid) AS n_tup_del,
     pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tuples_warm_updated(c.oid) AS n_tup_warm_upd,
     pg_stat_get_live_tuples(c.oid) AS n_live_tup,
     pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
     pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
@@ -1903,6 +1904,7 @@ pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.n_tup_upd,
     pg_stat_all_tables.n_tup_del,
     pg_stat_all_tables.n_tup_hot_upd,
+    pg_stat_all_tables.n_tup_warm_upd,
     pg_stat_all_tables.n_live_tup,
     pg_stat_all_tables.n_dead_tup,
     pg_stat_all_tables.n_mod_since_analyze,
@@ -1946,6 +1948,7 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.n_tup_upd,
     pg_stat_all_tables.n_tup_del,
     pg_stat_all_tables.n_tup_hot_upd,
+    pg_stat_all_tables.n_tup_warm_upd,
     pg_stat_all_tables.n_live_tup,
     pg_stat_all_tables.n_dead_tup,
     pg_stat_all_tables.n_mod_since_analyze,
@@ -1983,7 +1986,8 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
     pg_stat_get_xact_tuples_inserted(c.oid) AS n_tup_ins,
     pg_stat_get_xact_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_xact_tuples_deleted(c.oid) AS n_tup_del,
-    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_xact_tuples_warm_updated(c.oid) AS n_tup_warm_upd
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -1999,7 +2003,8 @@ pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
     pg_stat_xact_all_tables.n_tup_ins,
     pg_stat_xact_all_tables.n_tup_upd,
     pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+    pg_stat_xact_all_tables.n_tup_hot_upd,
+    pg_stat_xact_all_tables.n_tup_warm_upd
    FROM pg_stat_xact_all_tables
   WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
@@ -2021,7 +2026,8 @@ pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
     pg_stat_xact_all_tables.n_tup_ins,
     pg_stat_xact_all_tables.n_tup_upd,
     pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+    pg_stat_xact_all_tables.n_tup_hot_upd,
+    pg_stat_xact_all_tables.n_tup_warm_upd
    FROM pg_stat_xact_all_tables
   WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
diff --git a/src/test/regress/expected/warm.out b/src/test/regress/expected/warm.out
new file mode 100644
index 0000000..1f07272
--- /dev/null
+++ b/src/test/regress/expected/warm.out
@@ -0,0 +1,930 @@
+CREATE TABLE updtst_tab1 (a integer unique, b int, c text, d text);
+CREATE INDEX updtst_indx1 ON updtst_tab1 (b);
+INSERT INTO updtst_tab1
+       SELECT generate_series(1,10000), generate_series(70001, 80000), 'foo', 'bar';
+-- This should be a HOT update as non-index key is updated, but the
+-- page won't have any free space, so probably a non-HOT update
+UPDATE updtst_tab1 SET c = 'foo1' WHERE a = 1;
+-- Next update should be a HOT update as dead space is recycled
+UPDATE updtst_tab1 SET c = 'foo2' WHERE a = 1;
+-- And next too
+UPDATE updtst_tab1 SET c = 'foo3' WHERE a = 1;
+-- Now update one of the index key columns
+UPDATE updtst_tab1 SET b = b + 70000 WHERE a = 1;
+-- Ensure that the correct row is fetched
+SELECT * FROM updtst_tab1 WHERE a = 1;
+ a |   b    |  c   |  d  
+---+--------+------+-----
+ 1 | 140001 | foo3 | bar
+(1 row)
+
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+ a |   b    |  c   |  d  
+---+--------+------+-----
+ 1 | 140001 | foo3 | bar
+(1 row)
+
+-- Even when seqscan is disabled and indexscan is forced
+SET enable_seqscan = false;
+EXPLAIN (costs off) SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+               QUERY PLAN                
+-----------------------------------------
+ Bitmap Heap Scan on updtst_tab1
+   Recheck Cond: (b = 140001)
+   ->  Bitmap Index Scan on updtst_indx1
+         Index Cond: (b = 140001)
+(4 rows)
+
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+ a |   b    |  c   |  d  
+---+--------+------+-----
+ 1 | 140001 | foo3 | bar
+(1 row)
+
+-- Check if index only scan works correctly
+EXPLAIN (costs off) SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+               QUERY PLAN                
+-----------------------------------------
+ Bitmap Heap Scan on updtst_tab1
+   Recheck Cond: (b = 140001)
+   ->  Bitmap Index Scan on updtst_indx1
+         Index Cond: (b = 140001)
+(4 rows)
+
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+   b    
+--------
+ 140001
+(1 row)
+
+-- Table must be vacuumed to force index-only scan
+VACUUM updtst_tab1;
+EXPLAIN (costs off) SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Index Only Scan using updtst_indx1 on updtst_tab1
+   Index Cond: (b = 140001)
+(2 rows)
+
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+   b    
+--------
+ 140001
+(1 row)
+
+SET enable_seqscan = true;
+DROP TABLE updtst_tab1;
+------------------
+CREATE TABLE updtst_tab2 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx2 ON updtst_tab2 (b);
+INSERT INTO updtst_tab2
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+UPDATE updtst_tab2 SET b = b + 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo1'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab2 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo6'  WHERE a = 1;
+SELECT count(*) FROM updtst_tab2 WHERE c = 'foo';
+ count 
+-------
+    99
+(1 row)
+
+SELECT * FROM updtst_tab2 WHERE c = 'foo6';
+ a |  b  |  c   |  d  
+---+-----+------+-----
+ 1 | 701 | foo6 | bar
+(1 row)
+
+EXPLAIN (costs off) SELECT * FROM updtst_tab2 WHERE b = 701;
+               QUERY PLAN                
+-----------------------------------------
+ Bitmap Heap Scan on updtst_tab2
+   Recheck Cond: (b = 701)
+   ->  Bitmap Index Scan on updtst_indx2
+         Index Cond: (b = 701)
+(4 rows)
+
+SELECT * FROM updtst_tab2 WHERE a = 1;
+ a |  b  |  c   |  d  
+---+-----+------+-----
+ 1 | 701 | foo6 | bar
+(1 row)
+
+SET enable_seqscan = false;
+EXPLAIN (costs off) SELECT * FROM updtst_tab2 WHERE b = 701;
+               QUERY PLAN                
+-----------------------------------------
+ Bitmap Heap Scan on updtst_tab2
+   Recheck Cond: (b = 701)
+   ->  Bitmap Index Scan on updtst_indx2
+         Index Cond: (b = 701)
+(4 rows)
+
+SELECT * FROM updtst_tab2 WHERE b = 701;
+ a |  b  |  c   |  d  
+---+-----+------+-----
+ 1 | 701 | foo6 | bar
+(1 row)
+
+VACUUM updtst_tab2;
+EXPLAIN (costs off) SELECT b FROM updtst_tab2 WHERE b = 701;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Index Only Scan using updtst_indx2 on updtst_tab2
+   Index Cond: (b = 701)
+(2 rows)
+
+SELECT b FROM updtst_tab2 WHERE b = 701;
+  b  
+-----
+ 701
+(1 row)
+
+SET enable_seqscan = true;
+DROP TABLE updtst_tab2;
+------------------
+CREATE TABLE updtst_tab3 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx3 ON updtst_tab3 (b);
+INSERT INTO updtst_tab3
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo1', b = b + 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo6'  WHERE a = 1;
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo11', b = b + 750 WHERE b = 701;
+UPDATE updtst_tab3 SET c = 'foo12'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 1;
+COMMIT;
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+ count 
+-------
+    99
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo6';
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo12';
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE a = 1;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+VACUUM updtst_tab3;
+EXPLAIN (costs off) SELECT b FROM updtst_tab3 WHERE b = 701;
+       QUERY PLAN        
+-------------------------
+ Seq Scan on updtst_tab3
+   Filter: (b = 701)
+(2 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 701;
+ b 
+---
+(0 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 1421;
+  b   
+------
+ 1421
+(1 row)
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo23'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo24'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo25'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo26'  WHERE a = 2;
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+SET enable_seqscan = false;
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 750 WHERE b = 702;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 2;
+COMMIT;
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+ count 
+-------
+    98
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo26';
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo22';
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE b = 702;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE a = 2;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+-- Try fetching both old and new value using updtst_indx3
+SELECT * FROM updtst_tab3 WHERE b = 702;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+VACUUM updtst_tab3;
+EXPLAIN (costs off) SELECT b FROM updtst_tab3 WHERE b = 702;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Index Only Scan using updtst_indx3 on updtst_tab3
+   Index Cond: (b = 702)
+(2 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 702;
+ b 
+---
+(0 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 1422;
+  b   
+------
+ 1422
+(1 row)
+
+SET enable_seqscan = true;
+DROP TABLE updtst_tab3;
+------------------
+CREATE TABLE test_warm (a text unique, b text);
+CREATE INDEX test_warmindx ON test_warm (lower(a));
+INSERT INTO test_warm values ('test', 'foo');
+UPDATE test_warm SET a = 'TEST';
+select *, ctid from test_warm where lower(a) = 'test';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+explain select * from test_warm where lower(a) = 'test';
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ Bitmap Heap Scan on test_warm  (cost=4.18..12.65 rows=4 width=64)
+   Recheck Cond: (lower(a) = 'test'::text)
+   ->  Bitmap Index Scan on test_warmindx  (cost=0.00..4.18 rows=4 width=0)
+         Index Cond: (lower(a) = 'test'::text)
+(4 rows)
+
+select *, ctid from test_warm where lower(a) = 'test';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+select *, ctid from test_warm where a = 'test';
+ a | b | ctid 
+---+---+------
+(0 rows)
+
+select *, ctid from test_warm where a = 'TEST';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+set enable_bitmapscan TO false;
+explain select * from test_warm where lower(a) = 'test';
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Index Scan using test_warmindx on test_warm  (cost=0.15..20.22 rows=4 width=64)
+   Index Cond: (lower(a) = 'test'::text)
+(2 rows)
+
+select *, ctid from test_warm where lower(a) = 'test';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+DROP TABLE test_warm;
+--- Test with toast data types
+CREATE TABLE test_toast_warm (a int unique, b text, c int);
+CREATE INDEX test_toast_warm_index ON test_toast_warm(b);
+-- insert a large enough value to cause index datum compression
+INSERT INTO test_toast_warm VALUES (1, repeat('a', 600), 100);
+INSERT INTO test_toast_warm VALUES (2, repeat('b', 2), 100);
+INSERT INTO test_toast_warm VALUES (3, repeat('c', 4), 100);
+INSERT INTO test_toast_warm VALUES (4, repeat('d', 63), 100);
+INSERT INTO test_toast_warm VALUES (5, repeat('e', 126), 100);
+INSERT INTO test_toast_warm VALUES (6, repeat('f', 127), 100);
+INSERT INTO test_toast_warm VALUES (7, repeat('g', 128), 100);
+INSERT INTO test_toast_warm VALUES (8, repeat('h', 3200), 100);
+UPDATE test_toast_warm SET b = repeat('q', 600) WHERE a = 1;
+UPDATE test_toast_warm SET b = repeat('r', 2) WHERE a = 2;
+UPDATE test_toast_warm SET b = repeat('s', 4) WHERE a = 3;
+UPDATE test_toast_warm SET b = repeat('t', 63) WHERE a = 4;
+UPDATE test_toast_warm SET b = repeat('u', 126) WHERE a = 5;
+UPDATE test_toast_warm SET b = repeat('v', 127) WHERE a = 6;
+UPDATE test_toast_warm SET b = repeat('w', 128) WHERE a = 7;
+UPDATE test_toast_warm SET b = repeat('x', 3200) WHERE a = 8;
+SET enable_seqscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE a = 1;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using test_toast_warm_a_key on test_toast_warm
+   Index Cond: (a = 1)
+(2 rows)
+
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = repeat('a', 600);
+                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Scan using test_toast_warm_index on test_toast_warm
+   Index Cond: (b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = repeat('a', 600);
+                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using test_toast_warm_index on test_toast_warm
+   Index Cond: (b = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::text)
+(2 rows)
+
+SELECT a, b FROM test_toast_warm WHERE a = 1;
+ a |                                                                                                                                                                                                                                                                                                            b                                                                                                                                                                                                                                                                                                             
+---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('a', 600);
+ a | b 
+---+---
+(0 rows)
+
+SELECT b FROM test_toast_warm WHERE b = repeat('a', 600);
+ b 
+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('q', 600);
+ a |                                                                                                                                                                                                                                                                                                            b                                                                                                                                                                                                                                                                                                             
+---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+(1 row)
+
+SELECT b FROM test_toast_warm WHERE b = repeat('q', 600);
+                                                                                                                                                                                                                                                                                                            b                                                                                                                                                                                                                                                                                                             
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('r', 2);
+ a | b  
+---+----
+ 2 | rr
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('s', 4);
+ a |  b   
+---+------
+ 3 | ssss
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('t', 63);
+ a |                                b                                
+---+-----------------------------------------------------------------
+ 4 | ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('u', 126);
+ a |                                                               b                                                                
+---+--------------------------------------------------------------------------------------------------------------------------------
+ 5 | uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('v', 127);
+ a |                                                                b                                                                
+---+---------------------------------------------------------------------------------------------------------------------------------
+ 6 | vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('w', 128);
+ a |                                                                b                                                                 
+---+----------------------------------------------------------------------------------------------------------------------------------
+ 7 | wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('x', 3200);
+ a |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                b                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
+---+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 8 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+(1 row)
+
+SET enable_seqscan TO true;
+SET enable_indexscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = repeat('q', 600);
+                                                                                                                                                                                                                                                                                                                    QUERY PLAN                                                                                                                                                                                                                                                                                                                    
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on test_toast_warm
+   Filter: (b = 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = repeat('q', 600);
+                                                                                                                                                                                                                                                                                                                    QUERY PLAN                                                                                                                                                                                                                                                                                                                    
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on test_toast_warm
+   Filter: (b = 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'::text)
+(2 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('q', 600);
+ a |                                                                                                                                                                                                                                                                                                            b                                                                                                                                                                                                                                                                                                             
+---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1 | qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+(1 row)
+
+SELECT b FROM test_toast_warm WHERE b = repeat('q', 600);
+                                                                                                                                                                                                                                                                                                            b                                                                                                                                                                                                                                                                                                             
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('r', 2);
+ a | b  
+---+----
+ 2 | rr
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('s', 4);
+ a |  b   
+---+------
+ 3 | ssss
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('t', 63);
+ a |                                b                                
+---+-----------------------------------------------------------------
+ 4 | ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('u', 126);
+ a |                                                               b                                                                
+---+--------------------------------------------------------------------------------------------------------------------------------
+ 5 | uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('v', 127);
+ a |                                                                b                                                                
+---+---------------------------------------------------------------------------------------------------------------------------------
+ 6 | vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('w', 128);
+ a |                                                                b                                                                 
+---+----------------------------------------------------------------------------------------------------------------------------------
+ 7 | wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('x', 3200);
+ a |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                b                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
+---+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 8 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+(1 row)
+
+DROP TABLE test_toast_warm;
+-- Test with numeric data type
+CREATE TABLE test_toast_warm (a int unique, b numeric(10,2), c int);
+CREATE INDEX test_toast_warm_index ON test_toast_warm(b);
+INSERT INTO test_toast_warm VALUES (1, 10.2, 100);
+INSERT INTO test_toast_warm VALUES (2, 11.22, 100);
+INSERT INTO test_toast_warm VALUES (3, 12.222, 100);
+INSERT INTO test_toast_warm VALUES (4, 13.20, 100);
+INSERT INTO test_toast_warm VALUES (5, 14.201, 100);
+UPDATE test_toast_warm SET b = 100.2 WHERE a = 1;
+UPDATE test_toast_warm SET b = 101.22 WHERE a = 2;
+UPDATE test_toast_warm SET b = 102.222 WHERE a = 3;
+UPDATE test_toast_warm SET b = 103.20 WHERE a = 4;
+UPDATE test_toast_warm SET b = 104.201 WHERE a = 5;
+SELECT * FROM test_toast_warm;
+ a |   b    |  c  
+---+--------+-----
+ 1 | 100.20 | 100
+ 2 | 101.22 | 100
+ 3 | 102.22 | 100
+ 4 | 103.20 | 100
+ 5 | 104.20 | 100
+(5 rows)
+
+SET enable_seqscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE a = 1;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Scan using test_toast_warm_a_key on test_toast_warm
+   Index Cond: (a = 1)
+(2 rows)
+
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Bitmap Heap Scan on test_toast_warm
+   Recheck Cond: (b = 10.2)
+   ->  Bitmap Index Scan on test_toast_warm_index
+         Index Cond: (b = 10.2)
+(4 rows)
+
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = 100.2;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Bitmap Heap Scan on test_toast_warm
+   Recheck Cond: (b = 100.2)
+   ->  Bitmap Index Scan on test_toast_warm_index
+         Index Cond: (b = 100.2)
+(4 rows)
+
+SELECT a, b FROM test_toast_warm WHERE a = 1;
+ a |   b    
+---+--------
+ 1 | 100.20
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+ a | b 
+---+---
+(0 rows)
+
+SELECT b FROM test_toast_warm WHERE b = 10.2;
+ b 
+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = 100.2;
+ a |   b    
+---+--------
+ 1 | 100.20
+(1 row)
+
+SELECT b FROM test_toast_warm WHERE b = 100.2;
+   b    
+--------
+ 100.20
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 101.22;
+ a |   b    
+---+--------
+ 2 | 101.22
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 102.222;
+ a | b 
+---+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = 102.22;
+ a |   b    
+---+--------
+ 3 | 102.22
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 103.20;
+ a |   b    
+---+--------
+ 4 | 103.20
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 104.201;
+ a | b 
+---+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = 104.20;
+ a |   b    
+---+--------
+ 5 | 104.20
+(1 row)
+
+SET enable_seqscan TO true;
+SET enable_indexscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE a = 1;
+         QUERY PLAN          
+-----------------------------
+ Seq Scan on test_toast_warm
+   Filter: (a = 1)
+(2 rows)
+
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+         QUERY PLAN          
+-----------------------------
+ Seq Scan on test_toast_warm
+   Filter: (b = 10.2)
+(2 rows)
+
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = 100.2;
+         QUERY PLAN          
+-----------------------------
+ Seq Scan on test_toast_warm
+   Filter: (b = 100.2)
+(2 rows)
+
+SELECT a, b FROM test_toast_warm WHERE a = 1;
+ a |   b    
+---+--------
+ 1 | 100.20
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+ a | b 
+---+---
+(0 rows)
+
+SELECT b FROM test_toast_warm WHERE b = 10.2;
+ b 
+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = 100.2;
+ a |   b    
+---+--------
+ 1 | 100.20
+(1 row)
+
+SELECT b FROM test_toast_warm WHERE b = 100.2;
+   b    
+--------
+ 100.20
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 101.22;
+ a |   b    
+---+--------
+ 2 | 101.22
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 102.222;
+ a | b 
+---+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = 102.22;
+ a |   b    
+---+--------
+ 3 | 102.22
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 103.20;
+ a |   b    
+---+--------
+ 4 | 103.20
+(1 row)
+
+SELECT a, b FROM test_toast_warm WHERE b = 104.201;
+ a | b 
+---+---
+(0 rows)
+
+SELECT a, b FROM test_toast_warm WHERE b = 104.20;
+ a |   b    
+---+--------
+ 5 | 104.20
+(1 row)
+
+DROP TABLE test_toast_warm;
+-- Toasted heap attributes
+CREATE TABLE toasttest(descr text , cnt int DEFAULT 0, f1 text, f2 text);
+CREATE INDEX testindx1 ON toasttest(descr);
+CREATE INDEX testindx2 ON toasttest(f1);
+INSERT INTO toasttest(descr, f1, f2) VALUES('two-compressed', repeat('1234567890',1000), repeat('1234567890',1000));
+INSERT INTO toasttest(descr, f1, f2) VALUES('two-toasted', repeat('1234567890',20000), repeat('1234567890',50000));
+INSERT INTO toasttest(descr, f1, f2) VALUES('one-compressed,one-toasted', repeat('1234567890',1000), repeat('1234567890',50000));
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+ ctid  |                                                                                                substring                                                                                                 
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,1) | (two-compressed,0,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (0,2) | (two-toasted,0,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ (0,3) | ("one-compressed,one-toasted",0,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
+(3 rows)
+
+-- UPDATE f1 by doing string manipulation, but the updated value remains the
+-- same as the old value
+UPDATE toasttest SET cnt = cnt +1, f1 = trim(leading '-' from '-'||f1) RETURNING substring(toasttest::text, 1, 200);
+                                                                                                substring                                                                                                 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (two-toasted,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ ("one-compressed,one-toasted",1,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
+(3 rows)
+
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Seq Scan on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+ ctid  |                                                                                                substring                                                                                                 
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,4) | (two-compressed,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (0,5) | (two-toasted,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ (0,6) | ("one-compressed,one-toasted",1,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
+(3 rows)
+
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Index Scan using testindx2 on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+ ctid  |                                                                                                substring                                                                                                 
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,6) | ("one-compressed,one-toasted",1,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
+ (0,4) | (two-compressed,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
+ (0,5) | (two-toasted,1,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+(3 rows)
+
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+-- UPDATE f1 for real this time
+UPDATE toasttest SET cnt = cnt +1, f1 = '-'||f1 RETURNING substring(toasttest::text, 1, 200);
+                                                                                                substring                                                                                                 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (two-toasted,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ ("one-compressed,one-toasted",2,-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
+(3 rows)
+
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Seq Scan on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+ ctid  |                                                                                                substring                                                                                                 
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,7) | (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (0,8) | (two-toasted,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+ (0,9) | ("one-compressed,one-toasted",2,-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
+(3 rows)
+
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Index Scan using testindx2 on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+ ctid  |                                                                                                substring                                                                                                 
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,9) | ("one-compressed,one-toasted",2,-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
+ (0,7) | (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (0,8) | (two-toasted,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
+(3 rows)
+
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+-- UPDATE f1 from toasted to compressed
+UPDATE toasttest SET cnt = cnt +1, f1 = repeat('1234567890',1000) WHERE descr = 'two-toasted';
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Seq Scan on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+  ctid  |                                                                                                substring                                                                                                 
+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,7)  | (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (0,9)  | ("one-compressed,one-toasted",2,-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
+ (0,10) | (two-toasted,3,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+(3 rows)
+
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Index Scan using testindx2 on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+  ctid  |                                                                                                substring                                                                                                 
+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,9)  | ("one-compressed,one-toasted",2,-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
+ (0,7)  | (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (0,10) | (two-toasted,3,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+(3 rows)
+
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+-- UPDATE f1 from compressed to toasted
+UPDATE toasttest SET cnt = cnt +1, f1 = repeat('1234567890',2000) WHERE descr = 'one-compressed,one-toasted';
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Seq Scan on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+  ctid  |                                                                                                substring                                                                                                 
+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,7)  | (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (0,10) | (two-toasted,3,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ (0,11) | ("one-compressed,one-toasted",3,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
+(3 rows)
+
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Index Scan using testindx2 on toasttest (actual rows=3 loops=1)
+(1 row)
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+  ctid  |                                                                                                substring                                                                                                 
+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (0,7)  | (two-compressed,2,-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
+ (0,10) | (two-toasted,3,12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+ (0,11) | ("one-compressed,one-toasted",3,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
+(3 rows)
+
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+DROP TABLE toasttest;
+-- Test enable_warm reloption
+CREATE TABLE testrelopt (a int unique, b int, c int) WITH (enable_warm = true);
+ALTER TABLE testrelopt SET (enable_warm = true);
+ALTER TABLE testrelopt RESET (enable_warm);
+-- should fail since we don't allow turning WARM off
+ALTER TABLE testrelopt SET (enable_warm = false);
+ERROR:  WARM updates cannot be disabled on the table "testrelopt"
+DROP TABLE testrelopt;
+CREATE TABLE testrelopt (a int unique, b int, c int) WITH (enable_warm = false);
+-- should be ok since the default is ON and we support turning WARM ON
+ALTER TABLE testrelopt RESET (enable_warm);
+ALTER TABLE testrelopt SET (enable_warm = true);
+-- should fail since we don't allow turning WARM off
+ALTER TABLE testrelopt SET (enable_warm = false);
+ERROR:  WARM updates cannot be disabled on the table "testrelopt"
+DROP TABLE testrelopt;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 9f95b01..cd99f88 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -42,6 +42,8 @@ test: create_type
 test: create_table
 test: create_function_2
 
+test: warm
+
 # ----------
 # Load huge amounts of data
 # We should split the data files into single files and then
diff --git a/src/test/regress/sql/warm.sql b/src/test/regress/sql/warm.sql
new file mode 100644
index 0000000..fc80c0f
--- /dev/null
+++ b/src/test/regress/sql/warm.sql
@@ -0,0 +1,360 @@
+
+CREATE TABLE updtst_tab1 (a integer unique, b int, c text, d text);
+CREATE INDEX updtst_indx1 ON updtst_tab1 (b);
+INSERT INTO updtst_tab1
+       SELECT generate_series(1,10000), generate_series(70001, 80000), 'foo', 'bar';
+
+-- This should be a HOT update as non-index key is updated, but the
+-- page won't have any free space, so probably a non-HOT update
+UPDATE updtst_tab1 SET c = 'foo1' WHERE a = 1;
+
+-- Next update should be a HOT update as dead space is recycled
+UPDATE updtst_tab1 SET c = 'foo2' WHERE a = 1;
+
+-- And next too
+UPDATE updtst_tab1 SET c = 'foo3' WHERE a = 1;
+
+-- Now update one of the index key columns
+UPDATE updtst_tab1 SET b = b + 70000 WHERE a = 1;
+
+-- Ensure that the correct row is fetched
+SELECT * FROM updtst_tab1 WHERE a = 1;
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+-- Even when seqscan is disabled and indexscan is forced
+SET enable_seqscan = false;
+EXPLAIN (costs off) SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+-- Check if index only scan works correctly
+EXPLAIN (costs off) SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+-- Table must be vacuumed to force index-only scan
+VACUUM updtst_tab1;
+EXPLAIN (costs off) SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+SET enable_seqscan = true;
+
+DROP TABLE updtst_tab1;
+
+------------------
+
+CREATE TABLE updtst_tab2 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx2 ON updtst_tab2 (b);
+INSERT INTO updtst_tab2
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+
+UPDATE updtst_tab2 SET b = b + 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo1'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab2 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo6'  WHERE a = 1;
+
+SELECT count(*) FROM updtst_tab2 WHERE c = 'foo';
+SELECT * FROM updtst_tab2 WHERE c = 'foo6';
+
+EXPLAIN (costs off) SELECT * FROM updtst_tab2 WHERE b = 701;
+SELECT * FROM updtst_tab2 WHERE a = 1;
+
+SET enable_seqscan = false;
+EXPLAIN (costs off) SELECT * FROM updtst_tab2 WHERE b = 701;
+SELECT * FROM updtst_tab2 WHERE b = 701;
+
+VACUUM updtst_tab2;
+EXPLAIN (costs off) SELECT b FROM updtst_tab2 WHERE b = 701;
+SELECT b FROM updtst_tab2 WHERE b = 701;
+
+SET enable_seqscan = true;
+
+DROP TABLE updtst_tab2;
+------------------
+
+CREATE TABLE updtst_tab3 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx3 ON updtst_tab3 (b);
+INSERT INTO updtst_tab3
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo1', b = b + 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo6'  WHERE a = 1;
+
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo11', b = b + 750 WHERE b = 701;
+UPDATE updtst_tab3 SET c = 'foo12'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 1;
+COMMIT;
+
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+SELECT * FROM updtst_tab3 WHERE c = 'foo6';
+SELECT * FROM updtst_tab3 WHERE c = 'foo12';
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+SELECT * FROM updtst_tab3 WHERE a = 1;
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+
+VACUUM updtst_tab3;
+EXPLAIN (costs off) SELECT b FROM updtst_tab3 WHERE b = 701;
+SELECT b FROM updtst_tab3 WHERE b = 701;
+SELECT b FROM updtst_tab3 WHERE b = 1421;
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo23'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo24'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo25'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo26'  WHERE a = 2;
+
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+
+SET enable_seqscan = false;
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 750 WHERE b = 702;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 2;
+COMMIT;
+
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+SELECT * FROM updtst_tab3 WHERE c = 'foo26';
+SELECT * FROM updtst_tab3 WHERE c = 'foo22';
+
+SELECT * FROM updtst_tab3 WHERE b = 702;
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+SELECT * FROM updtst_tab3 WHERE a = 2;
+
+-- Try fetching both old and new value using updtst_indx3
+SELECT * FROM updtst_tab3 WHERE b = 702;
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+
+VACUUM updtst_tab3;
+EXPLAIN (costs off) SELECT b FROM updtst_tab3 WHERE b = 702;
+SELECT b FROM updtst_tab3 WHERE b = 702;
+SELECT b FROM updtst_tab3 WHERE b = 1422;
+
+SET enable_seqscan = true;
+
+DROP TABLE updtst_tab3;
+------------------
+
+CREATE TABLE test_warm (a text unique, b text);
+CREATE INDEX test_warmindx ON test_warm (lower(a));
+INSERT INTO test_warm values ('test', 'foo');
+UPDATE test_warm SET a = 'TEST';
+select *, ctid from test_warm where lower(a) = 'test';
+explain select * from test_warm where lower(a) = 'test';
+select *, ctid from test_warm where lower(a) = 'test';
+select *, ctid from test_warm where a = 'test';
+select *, ctid from test_warm where a = 'TEST';
+set enable_bitmapscan TO false;
+explain select * from test_warm where lower(a) = 'test';
+select *, ctid from test_warm where lower(a) = 'test';
+DROP TABLE test_warm;
+
+--- Test with toast data types
+
+CREATE TABLE test_toast_warm (a int unique, b text, c int);
+CREATE INDEX test_toast_warm_index ON test_toast_warm(b);
+
+-- insert a large enough value to cause index datum compression
+INSERT INTO test_toast_warm VALUES (1, repeat('a', 600), 100);
+INSERT INTO test_toast_warm VALUES (2, repeat('b', 2), 100);
+INSERT INTO test_toast_warm VALUES (3, repeat('c', 4), 100);
+INSERT INTO test_toast_warm VALUES (4, repeat('d', 63), 100);
+INSERT INTO test_toast_warm VALUES (5, repeat('e', 126), 100);
+INSERT INTO test_toast_warm VALUES (6, repeat('f', 127), 100);
+INSERT INTO test_toast_warm VALUES (7, repeat('g', 128), 100);
+INSERT INTO test_toast_warm VALUES (8, repeat('h', 3200), 100);
+
+UPDATE test_toast_warm SET b = repeat('q', 600) WHERE a = 1;
+UPDATE test_toast_warm SET b = repeat('r', 2) WHERE a = 2;
+UPDATE test_toast_warm SET b = repeat('s', 4) WHERE a = 3;
+UPDATE test_toast_warm SET b = repeat('t', 63) WHERE a = 4;
+UPDATE test_toast_warm SET b = repeat('u', 126) WHERE a = 5;
+UPDATE test_toast_warm SET b = repeat('v', 127) WHERE a = 6;
+UPDATE test_toast_warm SET b = repeat('w', 128) WHERE a = 7;
+UPDATE test_toast_warm SET b = repeat('x', 3200) WHERE a = 8;
+
+
+SET enable_seqscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE a = 1;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = repeat('a', 600);
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = repeat('a', 600);
+SELECT a, b FROM test_toast_warm WHERE a = 1;
+SELECT a, b FROM test_toast_warm WHERE b = repeat('a', 600);
+SELECT b FROM test_toast_warm WHERE b = repeat('a', 600);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('q', 600);
+SELECT b FROM test_toast_warm WHERE b = repeat('q', 600);
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('r', 2);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('s', 4);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('t', 63);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('u', 126);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('v', 127);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('w', 128);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('x', 3200);
+
+SET enable_seqscan TO true;
+SET enable_indexscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = repeat('q', 600);
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = repeat('q', 600);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('q', 600);
+SELECT b FROM test_toast_warm WHERE b = repeat('q', 600);
+
+SELECT a, b FROM test_toast_warm WHERE b = repeat('r', 2);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('s', 4);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('t', 63);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('u', 126);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('v', 127);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('w', 128);
+SELECT a, b FROM test_toast_warm WHERE b = repeat('x', 3200);
+
+DROP TABLE test_toast_warm;
+
+-- Test with numeric data type
+
+CREATE TABLE test_toast_warm (a int unique, b numeric(10,2), c int);
+CREATE INDEX test_toast_warm_index ON test_toast_warm(b);
+
+INSERT INTO test_toast_warm VALUES (1, 10.2, 100);
+INSERT INTO test_toast_warm VALUES (2, 11.22, 100);
+INSERT INTO test_toast_warm VALUES (3, 12.222, 100);
+INSERT INTO test_toast_warm VALUES (4, 13.20, 100);
+INSERT INTO test_toast_warm VALUES (5, 14.201, 100);
+
+UPDATE test_toast_warm SET b = 100.2 WHERE a = 1;
+UPDATE test_toast_warm SET b = 101.22 WHERE a = 2;
+UPDATE test_toast_warm SET b = 102.222 WHERE a = 3;
+UPDATE test_toast_warm SET b = 103.20 WHERE a = 4;
+UPDATE test_toast_warm SET b = 104.201 WHERE a = 5;
+
+SELECT * FROM test_toast_warm;
+
+SET enable_seqscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE a = 1;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = 100.2;
+SELECT a, b FROM test_toast_warm WHERE a = 1;
+SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+SELECT b FROM test_toast_warm WHERE b = 10.2;
+SELECT a, b FROM test_toast_warm WHERE b = 100.2;
+SELECT b FROM test_toast_warm WHERE b = 100.2;
+
+SELECT a, b FROM test_toast_warm WHERE b = 101.22;
+SELECT a, b FROM test_toast_warm WHERE b = 102.222;
+SELECT a, b FROM test_toast_warm WHERE b = 102.22;
+SELECT a, b FROM test_toast_warm WHERE b = 103.20;
+SELECT a, b FROM test_toast_warm WHERE b = 104.201;
+SELECT a, b FROM test_toast_warm WHERE b = 104.20;
+
+SET enable_seqscan TO true;
+SET enable_indexscan TO false;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE a = 1;
+EXPLAIN (costs off) SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+EXPLAIN (costs off) SELECT b FROM test_toast_warm WHERE b = 100.2;
+SELECT a, b FROM test_toast_warm WHERE a = 1;
+SELECT a, b FROM test_toast_warm WHERE b = 10.2;
+SELECT b FROM test_toast_warm WHERE b = 10.2;
+SELECT a, b FROM test_toast_warm WHERE b = 100.2;
+SELECT b FROM test_toast_warm WHERE b = 100.2;
+
+SELECT a, b FROM test_toast_warm WHERE b = 101.22;
+SELECT a, b FROM test_toast_warm WHERE b = 102.222;
+SELECT a, b FROM test_toast_warm WHERE b = 102.22;
+SELECT a, b FROM test_toast_warm WHERE b = 103.20;
+SELECT a, b FROM test_toast_warm WHERE b = 104.201;
+SELECT a, b FROM test_toast_warm WHERE b = 104.20;
+
+DROP TABLE test_toast_warm;
+
+-- Toasted heap attributes
+CREATE TABLE toasttest(descr text , cnt int DEFAULT 0, f1 text, f2 text);
+CREATE INDEX testindx1 ON toasttest(descr);
+CREATE INDEX testindx2 ON toasttest(f1);
+
+INSERT INTO toasttest(descr, f1, f2) VALUES('two-compressed', repeat('1234567890',1000), repeat('1234567890',1000));
+INSERT INTO toasttest(descr, f1, f2) VALUES('two-toasted', repeat('1234567890',20000), repeat('1234567890',50000));
+INSERT INTO toasttest(descr, f1, f2) VALUES('one-compressed,one-toasted', repeat('1234567890',1000), repeat('1234567890',50000));
+
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+
+-- UPDATE f1 by doing string manipulation, but the updated value remains the
+-- same as the old value
+UPDATE toasttest SET cnt = cnt +1, f1 = trim(leading '-' from '-'||f1) RETURNING substring(toasttest::text, 1, 200);
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+
+-- UPDATE f1 for real this time
+UPDATE toasttest SET cnt = cnt +1, f1 = '-'||f1 RETURNING substring(toasttest::text, 1, 200);
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+
+-- UPDATE f1 from toasted to compressed
+UPDATE toasttest SET cnt = cnt +1, f1 = repeat('1234567890',1000) WHERE descr = 'two-toasted';
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+
+-- UPDATE f1 from compressed to toasted
+UPDATE toasttest SET cnt = cnt +1, f1 = repeat('1234567890',2000) WHERE descr = 'one-compressed,one-toasted';
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest;
+SET enable_seqscan TO false;
+SET seq_page_cost = 10000;
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SELECT ctid, substring(toasttest::text, 1, 200) FROM toasttest ORDER BY f1;
+SET enable_seqscan TO true;
+SET seq_page_cost TO default;
+
+DROP TABLE toasttest;
+
+-- Test enable_warm reloption
+CREATE TABLE testrelopt (a int unique, b int, c int) WITH (enable_warm = true);
+ALTER TABLE testrelopt SET (enable_warm = true);
+ALTER TABLE testrelopt RESET (enable_warm);
+-- should fail since we don't allow turning WARM off
+ALTER TABLE testrelopt SET (enable_warm = false);
+DROP TABLE testrelopt;
+
+CREATE TABLE testrelopt (a int unique, b int, c int) WITH (enable_warm = false);
+-- should be ok since the default is ON and we support turning WARM ON
+ALTER TABLE testrelopt RESET (enable_warm);
+ALTER TABLE testrelopt SET (enable_warm = true);
+-- should fail since we don't allow turning WARM off
+ALTER TABLE testrelopt SET (enable_warm = false);
+DROP TABLE testrelopt;
-- 
2.9.3 (Apple Git-75)



  [application/octet-stream] 0001-Track-root-line-pointer-v23_v26.patch (39.4K, ../../CABOikdOTstHK2y0rDk+Y3Wx9HRe+bZtj3zuYGU=VngneiHo5KQ@mail.gmail.com/5-0001-Track-root-line-pointer-v23_v26.patch)
  download | inline diff:
From 6b9ff9be78d8b8d51e63549ab620096a95031606 Mon Sep 17 00:00:00 2001
From: Pavan Deolasee <[email protected]>
Date: Tue, 28 Feb 2017 10:34:30 +0530
Subject: [PATCH 1/4] Track root line pointer - v23

Store the root line pointer of the WARM chain in the t_ctid.ip_posid field of
the last tuple in the chain and mark the tuple header with HEAP_TUPLE_LATEST
flag to record that fact.
---
 src/backend/access/heap/heapam.c      | 209 ++++++++++++++++++++++++++++------
 src/backend/access/heap/hio.c         |  25 +++-
 src/backend/access/heap/pruneheap.c   | 126 ++++++++++++++++++--
 src/backend/access/heap/rewriteheap.c |  21 +++-
 src/backend/executor/execIndexing.c   |   3 +-
 src/backend/executor/execMain.c       |   4 +-
 src/include/access/heapam.h           |   1 +
 src/include/access/heapam_xlog.h      |   4 +-
 src/include/access/hio.h              |   4 +-
 src/include/access/htup_details.h     |  97 +++++++++++++++-
 10 files changed, 428 insertions(+), 66 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0c3e2b0..30262ef 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,7 +94,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
-				HeapTuple newtup, HeapTuple old_key_tup,
+				HeapTuple newtup, OffsetNumber root_offnum,
+				HeapTuple old_key_tup,
 				bool all_visible_cleared, bool new_all_visible_cleared);
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
@@ -2264,13 +2265,13 @@ heap_get_latest_tid(Relation relation,
 		 */
 		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
 			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+			HeapTupleHeaderIsHeapLatest(tp.t_data, &ctid))
 		{
 			UnlockReleaseBuffer(buffer);
 			break;
 		}
 
-		ctid = tp.t_data->t_ctid;
+		HeapTupleHeaderGetNextTid(tp.t_data, &ctid);
 		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
 		UnlockReleaseBuffer(buffer);
 	}							/* end of loop */
@@ -2401,6 +2402,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	bool		all_visible_cleared = false;
+	OffsetNumber	root_offnum;
 
 	/*
 	 * Fill in tuple header fields, assign an OID, and toast the tuple if
@@ -2439,8 +2441,13 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
-	RelationPutHeapTuple(relation, buffer, heaptup,
-						 (options & HEAP_INSERT_SPECULATIVE) != 0);
+	root_offnum = RelationPutHeapTuple(relation, buffer, heaptup,
+						 (options & HEAP_INSERT_SPECULATIVE) != 0,
+						 InvalidOffsetNumber);
+
+	/* We must not overwrite the speculative insertion token. */
+	if ((options & HEAP_INSERT_SPECULATIVE) == 0)
+		HeapTupleHeaderSetHeapLatest(heaptup->t_data, root_offnum);
 
 	if (PageIsAllVisible(BufferGetPage(buffer)))
 	{
@@ -2668,6 +2675,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 	Size		saveFreeSpace;
 	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
 	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+	OffsetNumber	root_offnum;
 
 	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
 	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
@@ -2738,7 +2746,12 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 		 * RelationGetBufferForTuple has ensured that the first tuple fits.
 		 * Put that on the page, and then as many other tuples as fit.
 		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		root_offnum = RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false,
+				InvalidOffsetNumber);
+
+		/* Mark this tuple as the latest and also set root offset. */
+		HeapTupleHeaderSetHeapLatest(heaptuples[ndone]->t_data, root_offnum);
+
 		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
 		{
 			HeapTuple	heaptup = heaptuples[ndone + nthispage];
@@ -2746,7 +2759,10 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
 				break;
 
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
+			root_offnum = RelationPutHeapTuple(relation, buffer, heaptup, false,
+					InvalidOffsetNumber);
+			/* Mark each tuple as the latest and also set root offset. */
+			HeapTupleHeaderSetHeapLatest(heaptup->t_data, root_offnum);
 
 			/*
 			 * We don't use heap_multi_insert for catalog tuples yet, but
@@ -3018,6 +3034,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	HeapTupleData tp;
 	Page		page;
 	BlockNumber block;
+	OffsetNumber	offnum;
 	Buffer		buffer;
 	Buffer		vmbuffer = InvalidBuffer;
 	TransactionId new_xmax;
@@ -3028,6 +3045,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	bool		all_visible_cleared = false;
 	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
 	bool		old_key_copied = false;
+	OffsetNumber	root_offnum;
 
 	Assert(ItemPointerIsValid(tid));
 
@@ -3069,7 +3087,8 @@ heap_delete(Relation relation, ItemPointer tid,
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 	}
 
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	lp = PageGetItemId(page, offnum);
 	Assert(ItemIdIsNormal(lp));
 
 	tp.t_tableOid = RelationGetRelid(relation);
@@ -3199,7 +3218,17 @@ l1:
 			   result == HeapTupleUpdated ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tp.t_data->t_ctid;
+
+		/*
+		 * If we're at the end of the chain, then just return the same TID back
+		 * to the caller. The caller uses that as a hint to know if we have hit
+		 * the end of the chain.
+		 */
+		if (!HeapTupleHeaderIsHeapLatest(tp.t_data, &tp.t_self))
+			HeapTupleHeaderGetNextTid(tp.t_data, &hufd->ctid);
+		else
+			ItemPointerCopy(&tp.t_self, &hufd->ctid);
+
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
 		if (result == HeapTupleSelfUpdated)
 			hufd->cmax = HeapTupleHeaderGetCmax(tp.t_data);
@@ -3248,6 +3277,22 @@ l1:
 							  xid, LockTupleExclusive, true,
 							  &new_xmax, &new_infomask, &new_infomask2);
 
+	/*
+	 * heap_get_root_tuple_one() may call palloc, which is disallowed once we
+	 * enter the critical section. So check if the root offset is cached in the
+	 * tuple and if not, fetch that information hard way before entering the
+	 * critical section.
+	 *
+	 * Most often and unless we are dealing with a pg-upgraded cluster, the
+	 * root offset information should be cached. So there should not be too
+	 * much overhead of fetching this information. Also, once a tuple is
+	 * updated, the information will be copied to the new version. So it's not
+	 * as if we're going to pay this price forever.
+	 */
+	if (!HeapTupleHeaderHasRootOffset(tp.t_data))
+		root_offnum = heap_get_root_tuple(page,
+				ItemPointerGetOffsetNumber(&tp.t_self));
+
 	START_CRIT_SECTION();
 
 	/*
@@ -3275,8 +3320,10 @@ l1:
 	HeapTupleHeaderClearHotUpdated(tp.t_data);
 	HeapTupleHeaderSetXmax(tp.t_data, new_xmax);
 	HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo);
-	/* Make sure there is no forward chain link in t_ctid */
-	tp.t_data->t_ctid = tp.t_self;
+
+	/* Mark this tuple as the latest tuple in the update chain. */
+	if (!HeapTupleHeaderHasRootOffset(tp.t_data))
+		HeapTupleHeaderSetHeapLatest(tp.t_data, root_offnum);
 
 	MarkBufferDirty(buffer);
 
@@ -3477,6 +3524,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		old_key_copied = false;
 	Page		page;
 	BlockNumber block;
+	OffsetNumber	offnum;
+	OffsetNumber	root_offnum;
 	MultiXactStatus mxact_status;
 	Buffer		buffer,
 				newbuf,
@@ -3536,6 +3585,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 
 
 	block = ItemPointerGetBlockNumber(otid);
+	offnum = ItemPointerGetOffsetNumber(otid);
 	buffer = ReadBuffer(relation, block);
 	page = BufferGetPage(buffer);
 
@@ -3839,7 +3889,12 @@ l2:
 			   result == HeapTupleUpdated ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = oldtup.t_data->t_ctid;
+
+		if (!HeapTupleHeaderIsHeapLatest(oldtup.t_data, &oldtup.t_self))
+			HeapTupleHeaderGetNextTid(oldtup.t_data, &hufd->ctid);
+		else
+			ItemPointerCopy(&oldtup.t_self, &hufd->ctid);
+
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
 		if (result == HeapTupleSelfUpdated)
 			hufd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
@@ -3979,6 +4034,7 @@ l2:
 		uint16		infomask_lock_old_tuple,
 					infomask2_lock_old_tuple;
 		bool		cleared_all_frozen = false;
+		OffsetNumber	root_offnum;
 
 		/*
 		 * To prevent concurrent sessions from updating the tuple, we have to
@@ -4006,6 +4062,14 @@ l2:
 
 		Assert(HEAP_XMAX_IS_LOCKED_ONLY(infomask_lock_old_tuple));
 
+		/*
+		 * Fetch root offset before entering the critical section. We do this
+		 * only if the information is not already available.
+		 */
+		if (!HeapTupleHeaderHasRootOffset(oldtup.t_data))
+			root_offnum = heap_get_root_tuple(page,
+					ItemPointerGetOffsetNumber(&oldtup.t_self));
+
 		START_CRIT_SECTION();
 
 		/* Clear obsolete visibility flags ... */
@@ -4020,7 +4084,8 @@ l2:
 		HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
 
 		/* temporarily make it look not-updated, but locked */
-		oldtup.t_data->t_ctid = oldtup.t_self;
+		if (!HeapTupleHeaderHasRootOffset(oldtup.t_data))
+			HeapTupleHeaderSetHeapLatest(oldtup.t_data, root_offnum);
 
 		/*
 		 * Clear all-frozen bit on visibility map if needed. We could
@@ -4179,6 +4244,10 @@ l2:
 										   bms_overlap(modified_attrs, id_attrs),
 										   &old_key_copied);
 
+	if (!HeapTupleHeaderHasRootOffset(oldtup.t_data))
+		root_offnum = heap_get_root_tuple(page,
+				ItemPointerGetOffsetNumber(&(oldtup.t_self)));
+
 	/* NO EREPORT(ERROR) from here till changes are logged */
 	START_CRIT_SECTION();
 
@@ -4204,6 +4273,17 @@ l2:
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
 		HeapTupleSetHeapOnly(newtup);
+		/*
+		 * For HOT (or WARM) updated tuples, we store the offset of the root
+		 * line pointer of this chain in the ip_posid field of the new tuple.
+		 * Usually this information will be available in the corresponding
+		 * field of the old tuple. But for aborted updates or pg_upgraded
+		 * databases, we might be seeing the old-style CTID chains and hence
+		 * the information must be obtained by hard way (we should have done
+		 * that before entering the critical section above).
+		 */
+		if (HeapTupleHeaderHasRootOffset(oldtup.t_data))
+			root_offnum = HeapTupleHeaderGetRootOffset(oldtup.t_data);
 	}
 	else
 	{
@@ -4211,10 +4291,22 @@ l2:
 		HeapTupleClearHotUpdated(&oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
+		root_offnum = InvalidOffsetNumber;
 	}
 
-	RelationPutHeapTuple(relation, newbuf, heaptup, false);		/* insert new tuple */
-
+	/* insert new tuple */
+	root_offnum = RelationPutHeapTuple(relation, newbuf, heaptup, false,
+									   root_offnum);
+	/*
+	 * Also mark both copies as latest and set the root offset information. If
+	 * we're doing a HOT/WARM update, then we just copy the information from
+	 * old tuple, if available or computed above. For regular updates,
+	 * RelationPutHeapTuple must have returned us the actual offset number
+	 * where the new version was inserted and we store the same value since the
+	 * update resulted in a new HOT-chain.
+	 */
+	HeapTupleHeaderSetHeapLatest(heaptup->t_data, root_offnum);
+	HeapTupleHeaderSetHeapLatest(newtup->t_data, root_offnum);
 
 	/* Clear obsolete visibility flags, possibly set by ourselves above... */
 	oldtup.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
@@ -4227,7 +4319,7 @@ l2:
 	HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
 
 	/* record address of new tuple in t_ctid of old one */
-	oldtup.t_data->t_ctid = heaptup->t_self;
+	HeapTupleHeaderSetNextTid(oldtup.t_data, &(heaptup->t_self));
 
 	/* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */
 	if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4266,6 +4358,7 @@ l2:
 
 		recptr = log_heap_update(relation, buffer,
 								 newbuf, &oldtup, heaptup,
+								 root_offnum,
 								 old_key_tuple,
 								 all_visible_cleared,
 								 all_visible_cleared_new);
@@ -4546,7 +4639,8 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	ItemId		lp;
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
+	BlockNumber	block;
+	OffsetNumber	offnum;
 	TransactionId xid,
 				xmax;
 	uint16		old_infomask,
@@ -4555,9 +4649,11 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
+	OffsetNumber	root_offnum;
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
+	offnum = ItemPointerGetOffsetNumber(tid);
 
 	/*
 	 * Before locking the buffer, pin the visibility map page if it appears to
@@ -4577,6 +4673,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	tuple->t_len = ItemIdGetLength(lp);
 	tuple->t_tableOid = RelationGetRelid(relation);
+	tuple->t_self = *tid;
 
 l3:
 	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
@@ -4604,7 +4701,11 @@ l3:
 		xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
 		infomask = tuple->t_data->t_infomask;
 		infomask2 = tuple->t_data->t_infomask2;
-		ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
+
+		if (!HeapTupleHeaderIsHeapLatest(tuple->t_data, tid))
+			HeapTupleHeaderGetNextTid(tuple->t_data, &t_ctid);
+		else
+			ItemPointerCopy(tid, &t_ctid);
 
 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -5042,7 +5143,12 @@ failed:
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
 			   result == HeapTupleWouldBlock);
 		Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tuple->t_data->t_ctid;
+
+		if (!HeapTupleHeaderIsHeapLatest(tuple->t_data, tid))
+			HeapTupleHeaderGetNextTid(tuple->t_data, &hufd->ctid);
+		else
+			ItemPointerCopy(tid, &hufd->ctid);
+
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
 		if (result == HeapTupleSelfUpdated)
 			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
@@ -5090,6 +5196,10 @@ failed:
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
+	if (!HeapTupleHeaderHasRootOffset(tuple->t_data))
+		root_offnum = heap_get_root_tuple(page,
+				ItemPointerGetOffsetNumber(&tuple->t_self));
+
 	START_CRIT_SECTION();
 
 	/*
@@ -5118,7 +5228,10 @@ failed:
 	 * the tuple as well.
 	 */
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
+	{
+		if (!HeapTupleHeaderHasRootOffset(tuple->t_data))
+			HeapTupleHeaderSetHeapLatest(tuple->t_data, root_offnum);
+	}
 
 	/* Clear only the all-frozen bit on visibility map if needed */
 	if (PageIsAllVisible(page) &&
@@ -5632,6 +5745,7 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 	bool		cleared_all_frozen = false;
 	Buffer		vmbuffer = InvalidBuffer;
 	BlockNumber block;
+	OffsetNumber offnum;
 
 	ItemPointerCopy(tid, &tupid);
 
@@ -5640,6 +5754,8 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 		new_infomask = 0;
 		new_xmax = InvalidTransactionId;
 		block = ItemPointerGetBlockNumber(&tupid);
+		offnum = ItemPointerGetOffsetNumber(&tupid);
+
 		ItemPointerCopy(&tupid, &(mytup.t_self));
 
 		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
@@ -5869,7 +5985,7 @@ l4:
 
 		/* if we find the end of update chain, we're done. */
 		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+			HeapTupleHeaderIsHeapLatest(mytup.t_data, &mytup.t_self) ||
 			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
 		{
 			result = HeapTupleMayBeUpdated;
@@ -5878,7 +5994,7 @@ l4:
 
 		/* tail recursion */
 		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		HeapTupleHeaderGetNextTid(mytup.t_data, &tupid);
 		UnlockReleaseBuffer(buf);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
@@ -5995,7 +6111,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	 * Replace the speculative insertion token with a real t_ctid, pointing to
 	 * itself like it does on regular tuples.
 	 */
-	htup->t_ctid = tuple->t_self;
+	HeapTupleHeaderSetHeapLatest(htup, offnum);
 
 	/* XLOG stuff */
 	if (RelationNeedsWAL(relation))
@@ -6121,8 +6237,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
 
 	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
-
+	HeapTupleHeaderSetHeapLatest(tp.t_data, ItemPointerGetOffsetNumber(tid));
 	MarkBufferDirty(buffer);
 
 	/*
@@ -7470,6 +7585,7 @@ log_heap_visible(RelFileNode rnode, Buffer heap_buffer, Buffer vm_buffer,
 static XLogRecPtr
 log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
+				OffsetNumber root_offnum,
 				HeapTuple old_key_tuple,
 				bool all_visible_cleared, bool new_all_visible_cleared)
 {
@@ -7590,6 +7706,9 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	xlrec.new_offnum = ItemPointerGetOffsetNumber(&newtup->t_self);
 	xlrec.new_xmax = HeapTupleHeaderGetRawXmax(newtup->t_data);
 
+	Assert(OffsetNumberIsValid(root_offnum));
+	xlrec.root_offnum = root_offnum;
+
 	bufflags = REGBUF_STANDARD;
 	if (init)
 		bufflags |= REGBUF_WILL_INIT;
@@ -8244,7 +8363,13 @@ heap_xlog_delete(XLogReaderState *record)
 			PageClearAllVisible(page);
 
 		/* Make sure there is no forward chain link in t_ctid */
-		htup->t_ctid = target_tid;
+		if (!HeapTupleHeaderHasRootOffset(htup))
+		{
+			OffsetNumber	root_offnum;
+			root_offnum = heap_get_root_tuple(page, xlrec->offnum); 
+			HeapTupleHeaderSetHeapLatest(htup, root_offnum);
+		}
+
 		PageSetLSN(page, lsn);
 		MarkBufferDirty(buffer);
 	}
@@ -8334,7 +8459,8 @@ heap_xlog_insert(XLogReaderState *record)
 		htup->t_hoff = xlhdr.t_hoff;
 		HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
 		HeapTupleHeaderSetCmin(htup, FirstCommandId);
-		htup->t_ctid = target_tid;
+
+		HeapTupleHeaderSetHeapLatest(htup, xlrec->offnum);
 
 		if (PageAddItem(page, (Item) htup, newlen, xlrec->offnum,
 						true, true) == InvalidOffsetNumber)
@@ -8469,8 +8595,8 @@ heap_xlog_multi_insert(XLogReaderState *record)
 			htup->t_hoff = xlhdr->t_hoff;
 			HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
 			HeapTupleHeaderSetCmin(htup, FirstCommandId);
-			ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
-			ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
+
+			HeapTupleHeaderSetHeapLatest(htup, offnum);
 
 			offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
 			if (offnum == InvalidOffsetNumber)
@@ -8606,7 +8732,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 		HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
 		HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
 		/* Set forward chain link in t_ctid */
-		htup->t_ctid = newtid;
+		HeapTupleHeaderSetNextTid(htup, &newtid);
 
 		/* Mark the page as a candidate for pruning */
 		PageSetPrunable(page, XLogRecGetXid(record));
@@ -8739,13 +8865,17 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 		HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
 		HeapTupleHeaderSetCmin(htup, FirstCommandId);
 		HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
-		/* Make sure there is no forward chain link in t_ctid */
-		htup->t_ctid = newtid;
 
 		offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
 		if (offnum == InvalidOffsetNumber)
 			elog(PANIC, "failed to add tuple");
 
+		/*
+		 * Make sure the tuple is marked as the latest and root offset
+		 * information is restored.
+		 */
+		HeapTupleHeaderSetHeapLatest(htup, xlrec->root_offnum);
+
 		if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED)
 			PageClearAllVisible(page);
 
@@ -8808,6 +8938,9 @@ heap_xlog_confirm(XLogReaderState *record)
 		 */
 		ItemPointerSet(&htup->t_ctid, BufferGetBlockNumber(buffer), offnum);
 
+		/* For newly inserted tuple, set root offset to itself. */
+		HeapTupleHeaderSetHeapLatest(htup, offnum);
+
 		PageSetLSN(page, lsn);
 		MarkBufferDirty(buffer);
 	}
@@ -8871,11 +9004,17 @@ heap_xlog_lock(XLogReaderState *record)
 		 */
 		if (HEAP_XMAX_IS_LOCKED_ONLY(htup->t_infomask))
 		{
+			ItemPointerData	target_tid;
+
+			ItemPointerSet(&target_tid, BufferGetBlockNumber(buffer), offnum);
 			HeapTupleHeaderClearHotUpdated(htup);
 			/* Make sure there is no forward chain link in t_ctid */
-			ItemPointerSet(&htup->t_ctid,
-						   BufferGetBlockNumber(buffer),
-						   offnum);
+			if (!HeapTupleHeaderHasRootOffset(htup))
+			{
+				OffsetNumber	root_offnum;
+				root_offnum = heap_get_root_tuple(page, offnum);
+				HeapTupleHeaderSetHeapLatest(htup, root_offnum);
+			}
 		}
 		HeapTupleHeaderSetXmax(htup, xlrec->locking_xid);
 		HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 6529fe3..8052519 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -31,12 +31,20 @@
  * !!! EREPORT(ERROR) IS DISALLOWED HERE !!!  Must PANIC on failure!!!
  *
  * Note - caller must hold BUFFER_LOCK_EXCLUSIVE on the buffer.
+ *
+ * The caller can optionally tell us to set the root offset to the given value.
+ * Otherwise, the root offset is set to the offset of the new location once its
+ * known. The former is used while updating an existing tuple where the caller
+ * tells us about the root line pointer of the chain.  The latter is used
+ * during insertion of a new row, hence root line pointer is set to the offset
+ * where this tuple is inserted.
  */
-void
+OffsetNumber
 RelationPutHeapTuple(Relation relation,
 					 Buffer buffer,
 					 HeapTuple tuple,
-					 bool token)
+					 bool token,
+					 OffsetNumber root_offnum)
 {
 	Page		pageHeader;
 	OffsetNumber offnum;
@@ -60,17 +68,24 @@ RelationPutHeapTuple(Relation relation,
 	ItemPointerSet(&(tuple->t_self), BufferGetBlockNumber(buffer), offnum);
 
 	/*
-	 * Insert the correct position into CTID of the stored tuple, too (unless
-	 * this is a speculative insertion, in which case the token is held in
-	 * CTID field instead)
+	 * Set block number and the root offset into CTID of the stored tuple, too
+	 * (unless this is a speculative insertion, in which case the token is held
+	 * in CTID field instead).
 	 */
 	if (!token)
 	{
 		ItemId		itemId = PageGetItemId(pageHeader, offnum);
 		Item		item = PageGetItem(pageHeader, itemId);
 
+		/* Copy t_ctid to set the correct block number. */
 		((HeapTupleHeader) item)->t_ctid = tuple->t_self;
+
+		if (!OffsetNumberIsValid(root_offnum))
+			root_offnum = offnum;
+		HeapTupleHeaderSetHeapLatest((HeapTupleHeader) item, root_offnum);
 	}
+
+	return root_offnum;
 }
 
 /*
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index d69a266..f54337c 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -55,6 +55,8 @@ static void heap_prune_record_redirect(PruneState *prstate,
 static void heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum);
 static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum);
 
+static void heap_get_root_tuples_internal(Page page,
+				OffsetNumber target_offnum, OffsetNumber *root_offsets);
 
 /*
  * Optionally prune and repair fragmentation in the specified page.
@@ -553,6 +555,17 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		if (!HeapTupleHeaderIsHotUpdated(htup))
 			break;
 
+
+		/*
+		 * If the tuple was HOT-updated and the update was later
+		 * aborted, someone could mark this tuple to be the last tuple
+		 * in the chain, without clearing the HOT-updated flag. So we must
+		 * check if this is the last tuple in the chain and stop following the
+		 * CTID, else we risk getting into an infinite recursion (though
+		 * prstate->marked[] currently protects against that).
+		 */
+		if (HeapTupleHeaderHasRootOffset(htup))
+			break;
 		/*
 		 * Advance to next chain member.
 		 */
@@ -726,27 +739,47 @@ heap_page_prune_execute(Buffer buffer,
 
 
 /*
- * For all items in this page, find their respective root line pointers.
- * If item k is part of a HOT-chain with root at item j, then we set
- * root_offsets[k - 1] = j.
+ * Either for all items in this page or for the given item, find their
+ * respective root line pointers.
+ *
+ * When target_offnum is a valid offset number, the caller is interested in
+ * just one item. In that case, the root line pointer is returned in
+ * root_offsets.
  *
- * The passed-in root_offsets array must have MaxHeapTuplesPerPage entries.
- * We zero out all unused entries.
+ * When target_offnum is a InvalidOffsetNumber then the caller wants to know
+ * the root line pointers of all the items in this page. The root_offsets array
+ * must have MaxHeapTuplesPerPage entries in that case. If item k is part of a
+ * HOT-chain with root at item j, then we set root_offsets[k - 1] = j. We zero
+ * out all unused entries.
  *
  * The function must be called with at least share lock on the buffer, to
  * prevent concurrent prune operations.
  *
+ * This is not a cheap function since it must scan through all line pointers
+ * and tuples on the page in order to find the root line pointers. To minimize
+ * the cost, we break early if target_offnum is specified and root line pointer
+ * to target_offnum is found.
+ *
  * Note: The information collected here is valid only as long as the caller
  * holds a pin on the buffer. Once pin is released, a tuple might be pruned
  * and reused by a completely unrelated tuple.
+ *
+ * Note: This function must not be called inside a critical section because it
+ * internally calls HeapTupleHeaderGetUpdateXid which somewhere down the stack
+ * may try to allocate heap memory. Memory allocation is disallowed in a
+ * critical section.
  */
-void
-heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
+static void
+heap_get_root_tuples_internal(Page page, OffsetNumber target_offnum,
+		OffsetNumber *root_offsets)
 {
 	OffsetNumber offnum,
 				maxoff;
 
-	MemSet(root_offsets, 0, MaxHeapTuplesPerPage * sizeof(OffsetNumber));
+	if (OffsetNumberIsValid(target_offnum))
+		*root_offsets = InvalidOffsetNumber;
+	else
+		MemSet(root_offsets, 0, MaxHeapTuplesPerPage * sizeof(OffsetNumber));
 
 	maxoff = PageGetMaxOffsetNumber(page);
 	for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
@@ -774,9 +807,28 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
 
 			/*
 			 * This is either a plain tuple or the root of a HOT-chain.
-			 * Remember it in the mapping.
+			 *
+			 * If the target_offnum is specified and if we found its mapping,
+			 * return.
 			 */
-			root_offsets[offnum - 1] = offnum;
+			if (OffsetNumberIsValid(target_offnum))
+			{
+				if (target_offnum == offnum)
+				{
+					root_offsets[0] = offnum;
+					return;
+				}
+				/*
+				 * No need to remember mapping for any other item. The
+				 * root_offsets array may not even has place for them. So be
+				 * careful about not writing past the array.
+				 */
+			}
+			else
+			{
+				/* Remember it in the mapping. */
+				root_offsets[offnum - 1] = offnum;
+			}
 
 			/* If it's not the start of a HOT-chain, we're done with it */
 			if (!HeapTupleHeaderIsHotUpdated(htup))
@@ -817,15 +869,65 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
 				!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup)))
 				break;
 
-			/* Remember the root line pointer for this item */
-			root_offsets[nextoffnum - 1] = offnum;
+			/*
+			 * If target_offnum is specified and we found its mapping, return.
+			 */
+			if (OffsetNumberIsValid(target_offnum))
+			{
+				if (nextoffnum == target_offnum)
+				{
+					root_offsets[0] = offnum;
+					return;
+				}
+				/*
+				 * No need to remember mapping for any other item. The
+				 * root_offsets array may not even has place for them. So be
+				 * careful about not writing past the array.
+				 */
+			}
+			else
+			{
+				/* Remember the root line pointer for this item. */
+				root_offsets[nextoffnum - 1] = offnum;
+			}
 
 			/* Advance to next chain member, if any */
 			if (!HeapTupleHeaderIsHotUpdated(htup))
 				break;
 
+			/*
+			 * If the tuple was HOT-updated and the update was later aborted,
+			 * someone could mark this tuple to be the last tuple in the chain
+			 * and store root offset in CTID, without clearing the HOT-updated
+			 * flag. So we must check if CTID is actually root offset and break
+			 * to avoid infinite recursion.
+			 */
+			if (HeapTupleHeaderHasRootOffset(htup))
+				break;
+
 			nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
 			priorXmax = HeapTupleHeaderGetUpdateXid(htup);
 		}
 	}
 }
+
+/*
+ * Get root line pointer for the given tuple.
+ */
+OffsetNumber
+heap_get_root_tuple(Page page, OffsetNumber target_offnum)
+{
+	OffsetNumber offnum = InvalidOffsetNumber;
+	heap_get_root_tuples_internal(page, target_offnum, &offnum);
+	return offnum;
+}
+
+/*
+ * Get root line pointers for all tuples in the page
+ */
+void
+heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
+{
+	return heap_get_root_tuples_internal(page, InvalidOffsetNumber,
+			root_offsets);
+}
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index d7f65a5..2d3ae9b 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -421,14 +421,18 @@ rewrite_heap_tuple(RewriteState state,
 	 */
 	if (!((old_tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
 		  HeapTupleHeaderIsOnlyLocked(old_tuple->t_data)) &&
-		!(ItemPointerEquals(&(old_tuple->t_self),
-							&(old_tuple->t_data->t_ctid))))
+		!(HeapTupleHeaderIsHeapLatest(old_tuple->t_data, &old_tuple->t_self)))
 	{
 		OldToNewMapping mapping;
 
 		memset(&hashkey, 0, sizeof(hashkey));
 		hashkey.xmin = HeapTupleHeaderGetUpdateXid(old_tuple->t_data);
-		hashkey.tid = old_tuple->t_data->t_ctid;
+
+		/* 
+		 * We've already checked that this is not the last tuple in the chain,
+		 * so fetch the next TID in the chain.
+		 */
+		HeapTupleHeaderGetNextTid(old_tuple->t_data, &hashkey.tid);
 
 		mapping = (OldToNewMapping)
 			hash_search(state->rs_old_new_tid_map, &hashkey,
@@ -441,7 +445,7 @@ rewrite_heap_tuple(RewriteState state,
 			 * set the ctid of this tuple to point to the new location, and
 			 * insert it right away.
 			 */
-			new_tuple->t_data->t_ctid = mapping->new_tid;
+			HeapTupleHeaderSetNextTid(new_tuple->t_data, &mapping->new_tid);
 
 			/* We don't need the mapping entry anymore */
 			hash_search(state->rs_old_new_tid_map, &hashkey,
@@ -527,7 +531,7 @@ rewrite_heap_tuple(RewriteState state,
 				new_tuple = unresolved->tuple;
 				free_new = true;
 				old_tid = unresolved->old_tid;
-				new_tuple->t_data->t_ctid = new_tid;
+				HeapTupleHeaderSetNextTid(new_tuple->t_data, &new_tid);
 
 				/*
 				 * We don't need the hash entry anymore, but don't free its
@@ -733,7 +737,12 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 		newitemid = PageGetItemId(page, newoff);
 		onpage_tup = (HeapTupleHeader) PageGetItem(page, newitemid);
 
-		onpage_tup->t_ctid = tup->t_self;
+		/* 
+		 * Set t_ctid just to ensure that block number is copied correctly, but
+		 * then immediately mark the tuple as the latest.
+		 */
+		HeapTupleHeaderSetNextTid(onpage_tup, &tup->t_self);
+		HeapTupleHeaderSetHeapLatest(onpage_tup, newoff);
 	}
 
 	/* If heaptup is a private copy, release it. */
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 108060a..c3f1873 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -785,7 +785,8 @@ retry:
 			  DirtySnapshot.speculativeToken &&
 			  TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
 		{
-			ctid_wait = tup->t_data->t_ctid;
+			if (!HeapTupleHeaderIsHeapLatest(tup->t_data, &tup->t_self))
+				HeapTupleHeaderGetNextTid(tup->t_data, &ctid_wait);
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 920b120..02f3f32 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2628,7 +2628,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * As above, it should be safe to examine xmax and t_ctid without the
 		 * buffer content lock, because they can't be changing.
 		 */
-		if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
+		if (HeapTupleHeaderIsHeapLatest(tuple.t_data, &tuple.t_self))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2636,7 +2636,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		}
 
 		/* updated, so look at the updated row */
-		tuple.t_self = tuple.t_data->t_ctid;
+		HeapTupleHeaderGetNextTid(tuple.t_data, &tuple.t_self);
 		/* updated row should have xmin matching this xmax */
 		priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
 		ReleaseBuffer(buffer);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 7e85510..5540e12 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -190,6 +190,7 @@ extern void heap_page_prune_execute(Buffer buffer,
 						OffsetNumber *redirected, int nredirected,
 						OffsetNumber *nowdead, int ndead,
 						OffsetNumber *nowunused, int nunused);
+extern OffsetNumber heap_get_root_tuple(Page page, OffsetNumber target_offnum);
 extern void heap_get_root_tuples(Page page, OffsetNumber *root_offsets);
 
 /* in heap/syncscan.c */
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index b285f17..e6019d5 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -193,6 +193,8 @@ typedef struct xl_heap_update
 	uint8		flags;
 	TransactionId new_xmax;		/* xmax of the new tuple */
 	OffsetNumber new_offnum;	/* new tuple's offset */
+	OffsetNumber root_offnum;	/* offset of the root line pointer in case of
+								   HOT or WARM update */
 
 	/*
 	 * If XLOG_HEAP_CONTAINS_OLD_TUPLE or XLOG_HEAP_CONTAINS_OLD_KEY flags are
@@ -200,7 +202,7 @@ typedef struct xl_heap_update
 	 */
 } xl_heap_update;
 
-#define SizeOfHeapUpdate	(offsetof(xl_heap_update, new_offnum) + sizeof(OffsetNumber))
+#define SizeOfHeapUpdate	(offsetof(xl_heap_update, root_offnum) + sizeof(OffsetNumber))
 
 /*
  * This is what we need to know about vacuum page cleanup/redirect
diff --git a/src/include/access/hio.h b/src/include/access/hio.h
index 2824f23..921cb37 100644
--- a/src/include/access/hio.h
+++ b/src/include/access/hio.h
@@ -35,8 +35,8 @@ typedef struct BulkInsertStateData
 }	BulkInsertStateData;
 
 
-extern void RelationPutHeapTuple(Relation relation, Buffer buffer,
-					 HeapTuple tuple, bool token);
+extern OffsetNumber RelationPutHeapTuple(Relation relation, Buffer buffer,
+					 HeapTuple tuple, bool token, OffsetNumber root_offnum);
 extern Buffer RelationGetBufferForTuple(Relation relation, Size len,
 						  Buffer otherBuffer, int options,
 						  BulkInsertState bistate,
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 7b6285d..24433c7 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -260,13 +260,19 @@ struct HeapTupleHeaderData
  * information stored in t_infomask2:
  */
 #define HEAP_NATTS_MASK			0x07FF	/* 11 bits for number of attributes */
-/* bits 0x1800 are available */
+/* bits 0x0800 are available */
+#define HEAP_LATEST_TUPLE		0x1000	/*
+										 * This is the last tuple in chain and
+										 * ip_posid points to the root line
+										 * pointer
+										 */
 #define HEAP_KEYS_UPDATED		0x2000	/* tuple was updated and key cols
 										 * modified, or tuple deleted */
 #define HEAP_HOT_UPDATED		0x4000	/* tuple was HOT-updated */
 #define HEAP_ONLY_TUPLE			0x8000	/* this is heap-only tuple */
 
-#define HEAP2_XACT_MASK			0xE000	/* visibility-related bits */
+#define HEAP2_XACT_MASK			0xF000	/* visibility-related bits */
+
 
 /*
  * HEAP_TUPLE_HAS_MATCH is a temporary flag used during hash joins.  It is
@@ -504,6 +510,43 @@ do { \
   ((tup)->t_infomask2 & HEAP_ONLY_TUPLE) != 0 \
 )
 
+/*
+ * Mark this as the last tuple in the HOT chain. Before PG v10 we used to store
+ * the TID of the tuple itself in t_ctid field to mark the end of the chain.
+ * But starting PG v10, we use a special flag HEAP_LATEST_TUPLE to identify the
+ * last tuple and store the root line pointer of the HOT chain in t_ctid field
+ * instead.
+ *
+ * Note: beware of multiple evaluations of "tup" argument.
+ */
+#define HeapTupleHeaderSetHeapLatest(tup, offnum) \
+do { \
+	AssertMacro(OffsetNumberIsValid(offnum)); \
+	(tup)->t_infomask2 |= HEAP_LATEST_TUPLE; \
+	ItemPointerSetOffsetNumber(&(tup)->t_ctid, (offnum)); \
+} while (0)
+
+#define HeapTupleHeaderClearHeapLatest(tup) \
+( \
+	(tup)->t_infomask2 &= ~HEAP_LATEST_TUPLE \
+)
+
+/*
+ * Starting from PostgreSQL 10, the latest tuple in an update chain has
+ * HEAP_LATEST_TUPLE set; but tuples upgraded from earlier versions do not.
+ * For those, we determine whether a tuple is latest by testing that its t_ctid
+ * points to itself.
+ *
+ * Note: beware of multiple evaluations of "tup" and "tid" arguments.
+ */
+#define HeapTupleHeaderIsHeapLatest(tup, tid) \
+( \
+  (((tup)->t_infomask2 & HEAP_LATEST_TUPLE) != 0) || \
+  ((ItemPointerGetBlockNumber(&(tup)->t_ctid) == ItemPointerGetBlockNumber(tid)) && \
+   (ItemPointerGetOffsetNumber(&(tup)->t_ctid) == ItemPointerGetOffsetNumber(tid))) \
+)
+
+
 #define HeapTupleHeaderSetHeapOnly(tup) \
 ( \
   (tup)->t_infomask2 |= HEAP_ONLY_TUPLE \
@@ -542,6 +585,56 @@ do { \
 
 
 /*
+ * Set the t_ctid chain and also clear the HEAP_LATEST_TUPLE flag since we
+ * now have a new tuple in the chain and this is no longer the last tuple of
+ * the chain.
+ *
+ * Note: beware of multiple evaluations of "tup" argument.
+ */
+#define HeapTupleHeaderSetNextTid(tup, tid) \
+do { \
+		ItemPointerCopy((tid), &((tup)->t_ctid)); \
+		HeapTupleHeaderClearHeapLatest((tup)); \
+} while (0)
+
+/*
+ * Get TID of next tuple in the update chain. Caller must have checked that
+ * we are not already at the end of the chain because in that case t_ctid may
+ * actually store the root line pointer of the HOT chain.
+ *
+ * Note: beware of multiple evaluations of "tup" argument.
+ */
+#define HeapTupleHeaderGetNextTid(tup, next_ctid) \
+do { \
+	AssertMacro(!((tup)->t_infomask2 & HEAP_LATEST_TUPLE)); \
+	ItemPointerCopy(&(tup)->t_ctid, (next_ctid)); \
+} while (0)
+
+/*
+ * Get the root line pointer of the HOT chain. The caller should have confirmed
+ * that the root offset is cached before calling this macro.
+ *
+ * Note: beware of multiple evaluations of "tup" argument.
+ */
+#define HeapTupleHeaderGetRootOffset(tup) \
+( \
+	AssertMacro(((tup)->t_infomask2 & HEAP_LATEST_TUPLE) != 0), \
+	ItemPointerGetOffsetNumber(&(tup)->t_ctid) \
+)
+
+/*
+ * Return whether the tuple has a cached root offset.  We don't use
+ * HeapTupleHeaderIsHeapLatest because that one also considers the case of
+ * t_ctid pointing to itself, for tuples migrated from pre v10 clusters. Here
+ * we are only interested in the tuples which are marked with HEAP_LATEST_TUPLE
+ * flag.
+ */
+#define HeapTupleHeaderHasRootOffset(tup) \
+( \
+	((tup)->t_infomask2 & HEAP_LATEST_TUPLE) != 0 \
+)
+
+/*
  * BITMAPLEN(NATTS) -
  *		Computes size of null bitmap given number of data columns.
  */
-- 
2.9.3 (Apple Git-75)



  [application/octet-stream] 0004-Provide-control-knobs-to-decide-when-to-do-heap-_v26.patch (69.9K, ../../CABOikdOTstHK2y0rDk+Y3Wx9HRe+bZtj3zuYGU=VngneiHo5KQ@mail.gmail.com/6-0004-Provide-control-knobs-to-decide-when-to-do-heap-_v26.patch)
  download | inline diff:
From 51843cbf0ca6459c4dc9f1f0936f88a038fadf20 Mon Sep 17 00:00:00 2001
From: Pavan Deolasee <[email protected]>
Date: Wed, 29 Mar 2017 11:16:29 +0530
Subject: [PATCH 4/4] Provide control knobs to decide when to do heap and index
 WARM cleanup.

We provide two knobs to control maintenance activity on WARM. A guc
autovacuum_warm_cleanup_scale_factor can be set to trigger WARM cleanup.
Similarly, a GUC autovacuum_warm_cleanup_index_scale_factor can be set to
determine when to do index cleanup. Note that in the current design VACUUM
needs two index scans to remove a WARM index pointer. Hence we want to do that
work only when it makes sense (i.e. the index has significant number of WARM
pointers)

Similarly, VACUUM command is enhanced to accept another parameter, WARMCLEAN,
and if specified then only WARM cleanup will be carried out.
---
 src/backend/access/common/reloptions.c |  22 +++
 src/backend/catalog/system_views.sql   |   1 +
 src/backend/commands/analyze.c         |  60 +++++--
 src/backend/commands/vacuum.c          |   2 +
 src/backend/commands/vacuumlazy.c      | 320 +++++++++++++++++++++++++--------
 src/backend/parser/gram.y              |  26 ++-
 src/backend/postmaster/autovacuum.c    |  58 +++++-
 src/backend/postmaster/pgstat.c        |  50 +++++-
 src/backend/utils/adt/pgstatfuncs.c    |  15 ++
 src/backend/utils/init/globals.c       |   3 +
 src/backend/utils/misc/guc.c           |  30 ++++
 src/include/catalog/pg_proc.h          |   2 +
 src/include/commands/vacuum.h          |   2 +
 src/include/foreign/fdwapi.h           |   3 +-
 src/include/miscadmin.h                |   1 +
 src/include/nodes/parsenodes.h         |   3 +-
 src/include/parser/kwlist.h            |   1 +
 src/include/pgstat.h                   |  11 +-
 src/include/postmaster/autovacuum.h    |   2 +
 src/include/utils/guc_tables.h         |   1 +
 src/include/utils/rel.h                |   2 +
 src/test/regress/expected/rules.out    |   3 +
 src/test/regress/expected/warm.out     |  59 ++++++
 src/test/regress/sql/warm.sql          |  47 +++++
 24 files changed, 614 insertions(+), 110 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ce7d4da..c8c7bba 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -361,6 +361,24 @@ static relopt_real realRelOpts[] =
 	},
 	{
 		{
+			"autovacuum_warmcleanup_scale_factor",
+			"Number of WARM chains prior to WARM cleanup as a fraction of reltuples",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		-1, 0.0, 100.0
+	},
+	{
+		{
+			"autovacuum_warmcleanup_index_scale_factor",
+			"Number of WARM pointers in an index prior to WARM cleanup as a fraction of total WARM chains",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		-1, 0.0, 100.0
+	},
+	{
+		{
 			"autovacuum_analyze_scale_factor",
 			"Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples",
 			RELOPT_KIND_HEAP,
@@ -1362,6 +1380,10 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_scale_factor)},
 		{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_scale_factor)},
+		{"autovacuum_warmcleanup_scale_factor", RELOPT_TYPE_REAL,
+		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, warmcleanup_scale_factor)},
+		{"autovacuum_warmcleanup_index_scale_factor", RELOPT_TYPE_REAL,
+		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, warmcleanup_index_scale)},
 		{"user_catalog_table", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, user_catalog_table)},
 		{"parallel_workers", RELOPT_TYPE_INT,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4ef964f..363fdf0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -533,6 +533,7 @@ CREATE VIEW pg_stat_all_tables AS
             pg_stat_get_tuples_warm_updated(C.oid) AS n_tup_warm_upd,
             pg_stat_get_live_tuples(C.oid) AS n_live_tup,
             pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_warm_chains(C.oid) AS n_warm_chains,
             pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
             pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
             pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 404acb2..6c4fc4e 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -93,7 +93,8 @@ static VacAttrStats *examine_attribute(Relation onerel, int attnum,
 				  Node *index_expr);
 static int acquire_sample_rows(Relation onerel, int elevel,
 					HeapTuple *rows, int targrows,
-					double *totalrows, double *totaldeadrows);
+					double *totalrows, double *totaldeadrows,
+					double *totalwarmchains);
 static int	compare_rows(const void *a, const void *b);
 static int acquire_inherited_sample_rows(Relation onerel, int elevel,
 							  HeapTuple *rows, int targrows,
@@ -320,7 +321,8 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	int			targrows,
 				numrows;
 	double		totalrows,
-				totaldeadrows;
+				totaldeadrows,
+				totalwarmchains;
 	HeapTuple  *rows;
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
@@ -501,7 +503,8 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	else
 		numrows = (*acquirefunc) (onerel, elevel,
 								  rows, targrows,
-								  &totalrows, &totaldeadrows);
+								  &totalrows, &totaldeadrows,
+								  &totalwarmchains);
 
 	/*
 	 * Compute the statistics.  Temporary results during the calculations for
@@ -631,7 +634,7 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	 */
 	if (!inh)
 		pgstat_report_analyze(onerel, totalrows, totaldeadrows,
-							  (va_cols == NIL));
+							  totalwarmchains, (va_cols == NIL));
 
 	/* If this isn't part of VACUUM ANALYZE, let index AMs do cleanup */
 	if (!(options & VACOPT_VACUUM))
@@ -991,12 +994,14 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr)
 static int
 acquire_sample_rows(Relation onerel, int elevel,
 					HeapTuple *rows, int targrows,
-					double *totalrows, double *totaldeadrows)
+					double *totalrows, double *totaldeadrows,
+					double *totalwarmchains)
 {
 	int			numrows = 0;	/* # rows now in reservoir */
 	double		samplerows = 0; /* total # rows collected */
 	double		liverows = 0;	/* # live rows seen */
 	double		deadrows = 0;	/* # dead rows seen */
+	double		warmchains = 0;
 	double		rowstoskip = -1;	/* -1 means not set yet */
 	BlockNumber totalblocks;
 	TransactionId OldestXmin;
@@ -1023,9 +1028,14 @@ acquire_sample_rows(Relation onerel, int elevel,
 		Page		targpage;
 		OffsetNumber targoffset,
 					maxoffset;
+		bool		marked[MaxHeapTuplesPerPage];
+		OffsetNumber root_offsets[MaxHeapTuplesPerPage];
 
 		vacuum_delay_point();
 
+		/* Track which root line pointers are already counted. */
+		memset(marked, 0, sizeof (marked));
+
 		/*
 		 * We must maintain a pin on the target page's buffer to ensure that
 		 * the maxoffset value stays good (else concurrent VACUUM might delete
@@ -1041,6 +1051,9 @@ acquire_sample_rows(Relation onerel, int elevel,
 		targpage = BufferGetPage(targbuffer);
 		maxoffset = PageGetMaxOffsetNumber(targpage);
 
+		/* Get all root line pointers first */
+		heap_get_root_tuples(targpage, root_offsets);
+
 		/* Inner loop over all tuples on the selected page */
 		for (targoffset = FirstOffsetNumber; targoffset <= maxoffset; targoffset++)
 		{
@@ -1069,6 +1082,22 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
+			/*
+			 * If this is a WARM-updated tuple, check if we have already seen
+			 * the root line pointer. If not, count this as a WARM chain. This
+			 * ensures that we count every WARM-chain just once, irrespective
+			 * of how many tuples exist in the chain.
+			 */
+			if (HeapTupleHeaderIsWarmUpdated(targtuple.t_data))
+			{
+				OffsetNumber root_offnum = root_offsets[targoffset];
+				if (!marked[root_offnum])
+				{
+					warmchains += 1;
+					marked[root_offnum] = true;
+				}
+			}
+
 			switch (HeapTupleSatisfiesVacuum(&targtuple,
 											 OldestXmin,
 											 targbuffer))
@@ -1200,18 +1229,24 @@ acquire_sample_rows(Relation onerel, int elevel,
 
 	/*
 	 * Estimate total numbers of rows in relation.  For live rows, use
-	 * vac_estimate_reltuples; for dead rows, we have no source of old
-	 * information, so we have to assume the density is the same in unseen
-	 * pages as in the pages we scanned.
+	 * vac_estimate_reltuples; for dead rows and WARM chains, we have no source
+	 * of old information, so we have to assume the density is the same in
+	 * unseen pages as in the pages we scanned.
 	 */
 	*totalrows = vac_estimate_reltuples(onerel, true,
 										totalblocks,
 										bs.m,
 										liverows);
 	if (bs.m > 0)
+	{
 		*totaldeadrows = floor((deadrows / bs.m) * totalblocks + 0.5);
+		*totalwarmchains = floor((warmchains / bs.m) * totalblocks + 0.5);
+	}
 	else
+	{
 		*totaldeadrows = 0.0;
+		*totalwarmchains = 0.0;
+	}
 
 	/*
 	 * Emit some interesting relation info
@@ -1219,11 +1254,13 @@ acquire_sample_rows(Relation onerel, int elevel,
 	ereport(elevel,
 			(errmsg("\"%s\": scanned %d of %u pages, "
 					"containing %.0f live rows and %.0f dead rows; "
-					"%d rows in sample, %.0f estimated total rows",
+					"%d rows in sample, %.0f estimated total rows; "
+					"%.0f warm chains",
 					RelationGetRelationName(onerel),
 					bs.m, totalblocks,
 					liverows, deadrows,
-					numrows, *totalrows)));
+					numrows, *totalrows,
+					*totalwarmchains)));
 
 	return numrows;
 }
@@ -1428,11 +1465,12 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 				int			childrows;
 				double		trows,
 							tdrows;
+				double		twarmchains;
 
 				/* Fetch a random sample of the child's rows */
 				childrows = (*acquirefunc) (childrel, elevel,
 											rows + numrows, childtargrows,
-											&trows, &tdrows);
+											&trows, &tdrows, &twarmchains);
 
 				/* We may need to convert from child's rowtype to parent's */
 				if (childrows > 0 &&
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9fbb0eb..52a7838 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -103,6 +103,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
 		params.freeze_table_age = 0;
 		params.multixact_freeze_min_age = 0;
 		params.multixact_freeze_table_age = 0;
+		params.warmcleanup_index_scale = -1;
 	}
 	else
 	{
@@ -110,6 +111,7 @@ ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel)
 		params.freeze_table_age = -1;
 		params.multixact_freeze_min_age = -1;
 		params.multixact_freeze_table_age = -1;
+		params.warmcleanup_index_scale = -1;
 	}
 
 	/* user-invoked vacuum is never "for wraparound" */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index c2d5705..6cf942a 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -156,18 +156,23 @@ typedef struct LVRelStats
 	double		tuples_deleted;
 	BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
 
+	int			maxtuples;		/* maxtuples computed while allocating space */
+	Size		work_area_size;	/* working area size */
+	char		*work_area;		/* working area for storing dead tuples and
+								 * warm chains */
 	/* List of candidate WARM chains that can be converted into HOT chains */
-	/* NB: this list is ordered by TID of the root pointers */
+	/* 
+	 * NB: this list grows from bottom to top and is ordered by TID of the root
+	 * pointers, with the lowest entry at the bottom
+	 */
 	int				num_warm_chains;	/* current # of entries */
-	int				max_warm_chains;	/* # slots allocated in array */
 	LVWarmChain 	*warm_chains;		/* array of LVWarmChain */
 	double			num_non_convertible_warm_chains;
-
 	/* List of TIDs of tuples we intend to delete */
 	/* NB: this list is ordered by TID address */
 	int			num_dead_tuples;	/* current # of entries */
-	int			max_dead_tuples;	/* # slots allocated in array */
 	ItemPointer dead_tuples;	/* array of ItemPointerData */
+
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
@@ -187,11 +192,12 @@ static BufferAccessStrategy vac_strategy;
 /* non-export function prototypes */
 static void lazy_scan_heap(Relation onerel, int options,
 			   LVRelStats *vacrelstats, Relation *Irel, int nindexes,
-			   bool aggressive);
+			   bool aggressive, double warmcleanup_index_scale);
 static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats);
 static bool lazy_check_needs_freeze(Buffer buf, bool *hastup);
 static void lazy_vacuum_index(Relation indrel,
 				  bool clear_warm,
+				  double warmcleanup_index_scale,
 				  IndexBulkDeleteResult **stats,
 				  LVRelStats *vacrelstats);
 static void lazy_cleanup_index(Relation indrel,
@@ -207,7 +213,8 @@ static bool should_attempt_truncation(LVRelStats *vacrelstats);
 static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
 static BlockNumber count_nondeletable_pages(Relation onerel,
 						 LVRelStats *vacrelstats);
-static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
+static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks,
+					   bool dowarmcleanup);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
 static void lazy_record_warm_chain(LVRelStats *vacrelstats,
@@ -283,6 +290,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 						  &OldestXmin, &FreezeLimit, &xidFullScanLimit,
 						  &MultiXactCutoff, &mxactFullScanLimit);
 
+	/* Use default if the caller hasn't specified any value */
+	if (params->warmcleanup_index_scale == -1)
+		params->warmcleanup_index_scale = VacuumWarmCleanupIndexScale;
 	/*
 	 * We request an aggressive scan if the table's frozen Xid is now older
 	 * than or equal to the requested Xid full-table scan limit; or if the
@@ -309,7 +319,8 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	vacrelstats->hasindex = (nindexes > 0);
 
 	/* Do the vacuuming */
-	lazy_scan_heap(onerel, options, vacrelstats, Irel, nindexes, aggressive);
+	lazy_scan_heap(onerel, options, vacrelstats, Irel, nindexes, aggressive,
+			params->warmcleanup_index_scale);
 
 	/* Done with indexes */
 	vac_close_indexes(nindexes, Irel, NoLock);
@@ -396,7 +407,8 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	pgstat_report_vacuum(RelationGetRelid(onerel),
 						 onerel->rd_rel->relisshared,
 						 new_live_tuples,
-						 vacrelstats->new_dead_tuples);
+						 vacrelstats->new_dead_tuples,
+						 vacrelstats->num_non_convertible_warm_chains);
 	pgstat_progress_end_command();
 
 	/* and log the action if appropriate */
@@ -507,10 +519,19 @@ vacuum_log_cleanup_info(Relation rel, LVRelStats *vacrelstats)
  *		If there are no indexes then we can reclaim line pointers on the fly;
  *		dead line pointers need only be retained until all index pointers that
  *		reference them have been killed.
+ *
+ *		warmcleanup_index_scale specifies the number of WARM pointers in an
+ *		index as a fraction of total candidate WARM chains. If we find less
+ *		WARM pointers in an index than the specified fraction, then we don't
+ *		invoke cleanup that index. If WARM cleanup is skipped for any one
+ *		index, the WARM chain can't be cleared in the heap and no further WARM
+ *		updates are possible to such chains. Such chains are also not
+ *		considered for WARM cleanup in other indexes.
  */
 static void
 lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
-			   Relation *Irel, int nindexes, bool aggressive)
+			   Relation *Irel, int nindexes, bool aggressive,
+			   double warmcleanup_index_scale)
 {
 	BlockNumber nblocks,
 				blkno;
@@ -536,6 +557,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		PROGRESS_VACUUM_MAX_DEAD_TUPLES
 	};
 	int64		initprog_val[3];
+	bool		dowarmcleanup = ((options & VACOPT_WARM_CLEANUP) != 0);
 
 	pg_rusage_init(&ru0);
 
@@ -558,13 +580,13 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 	vacrelstats->nonempty_pages = 0;
 	vacrelstats->latestRemovedXid = InvalidTransactionId;
 
-	lazy_space_alloc(vacrelstats, nblocks);
+	lazy_space_alloc(vacrelstats, nblocks, dowarmcleanup);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
 	/* Report that we're scanning the heap, advertising total # of blocks */
 	initprog_val[0] = PROGRESS_VACUUM_PHASE_SCAN_HEAP;
 	initprog_val[1] = nblocks;
-	initprog_val[2] = vacrelstats->max_dead_tuples;
+	initprog_val[2] = vacrelstats->maxtuples;
 	pgstat_progress_update_multi_param(3, initprog_index, initprog_val);
 
 	/*
@@ -656,6 +678,11 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		bool		all_frozen = true;	/* provided all_visible is also true */
 		bool		has_dead_tuples;
 		TransactionId visibility_cutoff_xid = InvalidTransactionId;
+		char		*end_deads;
+		char		*end_warms;
+		Size		free_work_area;
+		int			avail_dead_tuples;
+		int			avail_warm_chains;
 
 		/* see note above about forcing scanning of last page */
 #define FORCE_CHECK_PAGE() \
@@ -740,13 +767,39 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 
 		/*
+		 * The dead tuples are stored starting from the start of the work
+		 * area and growing downwards. The candidate warm chains are stored
+		 * starting from the bottom on the work area and growing upwards. Once
+		 * the difference between these two segments is too small to accomodate
+		 * potentially all tuples in the current page, we stop and do one round
+		 * of index cleanup.
+		 */
+		end_deads = (char *)(vacrelstats->dead_tuples + vacrelstats->num_dead_tuples);
+
+		/*
+		 * If we are not doing WARM cleanup, then the entire work area is used
+		 * by the dead tuples.
+		 */
+		if (vacrelstats->warm_chains)
+		{
+			end_warms = (char *)(vacrelstats->warm_chains - vacrelstats->num_warm_chains);
+			free_work_area = end_warms - end_deads;
+			avail_warm_chains = (free_work_area / sizeof (LVWarmChain));
+		}
+		else
+		{
+			free_work_area = vacrelstats->work_area +
+				vacrelstats->work_area_size - end_deads;
+			avail_warm_chains = 0;
+		}
+		avail_dead_tuples = (free_work_area / sizeof (ItemPointerData));
+
+		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
 		 */
-		if (((vacrelstats->max_dead_tuples - vacrelstats->num_dead_tuples) < MaxHeapTuplesPerPage &&
-			vacrelstats->num_dead_tuples > 0) ||
-			((vacrelstats->max_warm_chains - vacrelstats->num_warm_chains) < MaxHeapTuplesPerPage &&
-			 vacrelstats->num_warm_chains > 0))
+		if ((avail_dead_tuples < MaxHeapTuplesPerPage && vacrelstats->num_dead_tuples > 0) ||
+			(avail_warm_chains < MaxHeapTuplesPerPage && vacrelstats->num_warm_chains > 0))
 		{
 			const int	hvp_index[] = {
 				PROGRESS_VACUUM_PHASE,
@@ -776,7 +829,8 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
 				lazy_vacuum_index(Irel[i],
-								  (vacrelstats->num_warm_chains > 0),
+								  dowarmcleanup && (vacrelstats->num_warm_chains > 0),
+								  warmcleanup_index_scale,
 								  &indstats[i],
 								  vacrelstats);
 
@@ -800,8 +854,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_warm_chains = 0;
-			memset(vacrelstats->warm_chains, 0,
-					vacrelstats->max_warm_chains * sizeof (LVWarmChain));
+			memset(vacrelstats->work_area, 0, vacrelstats->work_area_size);
 			vacrelstats->num_index_scans++;
 
 			/* Report that we are once again scanning the heap */
@@ -1413,7 +1466,8 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
 			lazy_vacuum_index(Irel[i],
-							  (vacrelstats->num_warm_chains > 0),
+							  dowarmcleanup && (vacrelstats->num_warm_chains > 0),
+							  warmcleanup_index_scale,
 							  &indstats[i],
 							  vacrelstats);
 
@@ -1518,9 +1572,12 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
 		vacuum_delay_point();
 
 		tblk = chainblk = InvalidBlockNumber;
-		if (chainindex < vacrelstats->num_warm_chains)
-			chainblk =
-				ItemPointerGetBlockNumber(&(vacrelstats->warm_chains[chainindex].chain_tid));
+		if (vacrelstats->warm_chains &&
+			chainindex < vacrelstats->num_warm_chains)
+		{
+			LVWarmChain *chain = vacrelstats->warm_chains - (chainindex + 1);
+			chainblk = ItemPointerGetBlockNumber(&chain->chain_tid);
+		}
 
 		if (tupindex < vacrelstats->num_dead_tuples)
 			tblk = ItemPointerGetBlockNumber(&vacrelstats->dead_tuples[tupindex]);
@@ -1618,7 +1675,8 @@ lazy_warmclear_page(Relation onerel, BlockNumber blkno, Buffer buffer,
 		BlockNumber tblk;
 		LVWarmChain	*chain;
 
-		chain = &vacrelstats->warm_chains[chainindex];
+		/* The warm chains are indexed from bottom */
+		chain = vacrelstats->warm_chains - (chainindex + 1);
 
 		tblk = ItemPointerGetBlockNumber(&chain->chain_tid);
 		if (tblk != blkno)
@@ -1852,9 +1910,11 @@ static void
 lazy_reset_warm_pointer_count(LVRelStats *vacrelstats)
 {
 	int i;
-	for (i = 0; i < vacrelstats->num_warm_chains; i++)
+
+	/* Start from the bottom and move upwards */
+	for (i = 1; i <= vacrelstats->num_warm_chains; i++)
 	{
-		LVWarmChain *chain = &vacrelstats->warm_chains[i];
+		LVWarmChain *chain = (vacrelstats->warm_chains - i);
 		chain->num_clear_pointers = chain->num_warm_pointers = 0;
 	}
 }
@@ -1868,6 +1928,7 @@ lazy_reset_warm_pointer_count(LVRelStats *vacrelstats)
 static void
 lazy_vacuum_index(Relation indrel,
 				  bool clear_warm,
+				  double warmcleanup_index_scale,
 				  IndexBulkDeleteResult **stats,
 				  LVRelStats *vacrelstats)
 {
@@ -1932,25 +1993,57 @@ lazy_vacuum_index(Relation indrel,
 						(*stats)->warm_pointers_removed,
 						(*stats)->clear_pointers_removed)));
 
-		(*stats)->num_warm_pointers = 0;
-		(*stats)->num_clear_pointers = 0;
-		(*stats)->warm_pointers_removed = 0;
-		(*stats)->clear_pointers_removed = 0;
-		(*stats)->pointers_cleared = 0;
+		/*
+		 * If the number of WARM pointers found in the index are more than the
+		 * configured fraction of total candidate WARM chains, then do the
+		 * second index scan to clean up WARM chains.
+		 *
+		 * Otherwise we must set these WARM chains as non-convertible chains.
+		 */
+		if ((*stats)->num_warm_pointers >
+				((double)vacrelstats->num_warm_chains * warmcleanup_index_scale))
+		{
+			(*stats)->num_warm_pointers = 0;
+			(*stats)->num_clear_pointers = 0;
+			(*stats)->warm_pointers_removed = 0;
+			(*stats)->clear_pointers_removed = 0;
+			(*stats)->pointers_cleared = 0;
+
+			*stats = index_bulk_delete(&ivinfo, *stats,
+					lazy_indexvac_phase2, (void *) vacrelstats);
+			ereport(elevel,
+					(errmsg("scanned index \"%s\" to convert WARM pointers, found "
+							"%0.f WARM pointers, %0.f CLEAR pointers, removed "
+							"%0.f WARM pointers, removed %0.f CLEAR pointers, "
+							"cleared %0.f WARM pointers",
+							RelationGetRelationName(indrel),
+							(*stats)->num_warm_pointers,
+							(*stats)->num_clear_pointers,
+							(*stats)->warm_pointers_removed,
+							(*stats)->clear_pointers_removed,
+							(*stats)->pointers_cleared)));
+		}
+		else
+		{
+			int ii;
 
-		*stats = index_bulk_delete(&ivinfo, *stats,
-				lazy_indexvac_phase2, (void *) vacrelstats);
-		ereport(elevel,
-				(errmsg("scanned index \"%s\" to convert WARM pointers, found "
-						"%0.f WARM pointers, %0.f CLEAR pointers, removed "
-						"%0.f WARM pointers, removed %0.f CLEAR pointers, "
-						"cleared %0.f WARM pointers",
-						RelationGetRelationName(indrel),
-						(*stats)->num_warm_pointers,
-						(*stats)->num_clear_pointers,
-						(*stats)->warm_pointers_removed,
-						(*stats)->clear_pointers_removed,
-						(*stats)->pointers_cleared)));
+			/*
+			 * All chains skipped by this index are marked non-convertible.
+			 *
+			 * Start from bottom and move upwards.
+			 */
+			for (ii = 1; ii <= vacrelstats->num_warm_chains; ii++)
+			{
+				LVWarmChain *chain = vacrelstats->warm_chains - ii;
+				if (chain->num_warm_pointers > 0 ||
+					chain->num_clear_pointers > 1)
+				{
+					chain->keep_warm_chain = 1;
+					vacrelstats->num_non_convertible_warm_chains++;
+				}
+			}
+
+		}
 	}
 	else
 	{
@@ -2328,7 +2421,8 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
  * See the comments at the head of this file for rationale.
  */
 static void
-lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
+lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks,
+				 bool dowarmcleanup)
 {
 	long		maxtuples;
 	int			vac_work_mem = IsAutoVacuumWorkerProcess() &&
@@ -2337,11 +2431,16 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
 
 	if (vacrelstats->hasindex)
 	{
+		/*
+		 * If we're not doing WARM cleanup then the entire memory is available
+		 * for tracking dead tuples. Otherwise it gets split between tracking
+		 * dead tuples and tracking WARM chains.
+		 */
 		maxtuples = (vac_work_mem * 1024L) / (sizeof(ItemPointerData) +
-				sizeof(LVWarmChain));
+				dowarmcleanup ? sizeof(LVWarmChain) : 0);
 		maxtuples = Min(maxtuples, INT_MAX);
 		maxtuples = Min(maxtuples, MaxAllocSize / (sizeof(ItemPointerData) +
-					sizeof(LVWarmChain)));
+				dowarmcleanup ? sizeof(LVWarmChain) : 0));
 
 		/* curious coding here to ensure the multiplication can't overflow */
 		if ((BlockNumber) (maxtuples / LAZY_ALLOC_TUPLES) > relblocks)
@@ -2355,21 +2454,29 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
 		maxtuples = MaxHeapTuplesPerPage;
 	}
 
-	vacrelstats->num_dead_tuples = 0;
-	vacrelstats->max_dead_tuples = (int) maxtuples;
-	vacrelstats->dead_tuples = (ItemPointer)
-		palloc(maxtuples * sizeof(ItemPointerData));
-
-	/*
-	 * XXX Cheat for now and allocate the same size array for tracking warm
-	 * chains. maxtuples must have been already adjusted above to ensure we
-	 * don't cross vac_work_mem.
+	/* Allocate work area of the desired size and setup dead_tuples and
+	 * warm_chains to the start and the end of the area respectively. They grow
+	 * in opposite directions as dead tuples and warm chains are added. Note
+	 * that if we are not doing WARM cleanup then the entire area will only be
+	 * used for tracking dead tuples.
 	 */
-	vacrelstats->num_warm_chains = 0;
-	vacrelstats->max_warm_chains = (int) maxtuples;
-	vacrelstats->warm_chains = (LVWarmChain *)
-		palloc0(maxtuples * sizeof(LVWarmChain));
+	vacrelstats->work_area_size = maxtuples * (sizeof(ItemPointerData) +
+				dowarmcleanup ? sizeof(LVWarmChain) : 0);
+	vacrelstats->work_area = (char *) palloc0(vacrelstats->work_area_size);
+	vacrelstats->num_dead_tuples = 0;
+	vacrelstats->dead_tuples = (ItemPointer)vacrelstats->work_area;
+	vacrelstats->maxtuples = maxtuples;
 
+	if (dowarmcleanup)
+	{
+		vacrelstats->num_warm_chains = 0;
+		vacrelstats->warm_chains = (LVWarmChain *)
+			(vacrelstats->work_area + vacrelstats->work_area_size);
+	}
+	else
+	{
+		vacrelstats->warm_chains = NULL;
+	}
 }
 
 /*
@@ -2379,17 +2486,38 @@ static void
 lazy_record_clear_chain(LVRelStats *vacrelstats,
 					   ItemPointer itemptr)
 {
+	char *end_deads, *end_warms;
+	Size free_work_area;
+
+	if (vacrelstats->warm_chains == NULL)
+	{
+		vacrelstats->num_non_convertible_warm_chains++;
+		return;
+	}
+
+	end_deads = (char *) (vacrelstats->dead_tuples +
+					vacrelstats->num_dead_tuples);
+	end_warms = (char *) (vacrelstats->warm_chains -
+					vacrelstats->num_warm_chains);
+	free_work_area = (end_warms - end_deads);
+
+	Assert(free_work_area >= 0);
 	/*
 	 * The array shouldn't overflow under normal behavior, but perhaps it
 	 * could if we are given a really small maintenance_work_mem. In that
 	 * case, just forget the last few tuples (we'll get 'em next time).
 	 */
-	if (vacrelstats->num_warm_chains < vacrelstats->max_warm_chains)
+	if (free_work_area >= sizeof (LVWarmChain))
 	{
-		vacrelstats->warm_chains[vacrelstats->num_warm_chains].chain_tid = *itemptr;
-		vacrelstats->warm_chains[vacrelstats->num_warm_chains].is_postwarm_chain = 0;
+		LVWarmChain *chain;
+
 		vacrelstats->num_warm_chains++;
+	   	chain = vacrelstats->warm_chains - vacrelstats->num_warm_chains;
+		chain->chain_tid = *itemptr;
+		chain->is_postwarm_chain = 0;
 	}
+	else
+		vacrelstats->num_non_convertible_warm_chains++;
 }
 
 /*
@@ -2399,17 +2527,39 @@ static void
 lazy_record_warm_chain(LVRelStats *vacrelstats,
 					   ItemPointer itemptr)
 {
+	char *end_deads, *end_warms;
+	Size free_work_area;
+
+	if (vacrelstats->warm_chains == NULL)
+	{
+		vacrelstats->num_non_convertible_warm_chains++;
+		return;
+	}
+
+	end_deads = (char *) (vacrelstats->dead_tuples +
+					vacrelstats->num_dead_tuples);
+	end_warms = (char *) (vacrelstats->warm_chains -
+					vacrelstats->num_warm_chains);
+	free_work_area = (end_warms - end_deads);
+
+	Assert(free_work_area >= 0);
+
 	/*
 	 * The array shouldn't overflow under normal behavior, but perhaps it
 	 * could if we are given a really small maintenance_work_mem. In that
 	 * case, just forget the last few tuples (we'll get 'em next time).
 	 */
-	if (vacrelstats->num_warm_chains < vacrelstats->max_warm_chains)
+	if (free_work_area >= sizeof (LVWarmChain))
 	{
-		vacrelstats->warm_chains[vacrelstats->num_warm_chains].chain_tid = *itemptr;
-		vacrelstats->warm_chains[vacrelstats->num_warm_chains].is_postwarm_chain = 1;
+		LVWarmChain *chain;
+
 		vacrelstats->num_warm_chains++;
+	   	chain = vacrelstats->warm_chains - vacrelstats->num_warm_chains;
+		chain->chain_tid = *itemptr;
+		chain->is_postwarm_chain = 1;
 	}
+	else
+		vacrelstats->num_non_convertible_warm_chains++;
 }
 
 /*
@@ -2419,12 +2569,20 @@ static void
 lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr)
 {
+	char *end_deads = (char *) (vacrelstats->dead_tuples +
+		 	vacrelstats->num_dead_tuples);
+	char *end_warms = (char *) (vacrelstats->warm_chains -
+			vacrelstats->num_warm_chains);
+	Size freespace = (end_warms - end_deads);
+
+	Assert(freespace >= 0);
+	
 	/*
 	 * The array shouldn't overflow under normal behavior, but perhaps it
 	 * could if we are given a really small maintenance_work_mem. In that
 	 * case, just forget the last few tuples (we'll get 'em next time).
 	 */
-	if (vacrelstats->num_dead_tuples < vacrelstats->max_dead_tuples)
+	if (freespace >= sizeof (ItemPointer))
 	{
 		vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr;
 		vacrelstats->num_dead_tuples++;
@@ -2477,10 +2635,10 @@ lazy_indexvac_phase1(ItemPointer itemptr, bool is_warm, void *state)
 		return IBDCR_DELETE;
 
 	chain = (LVWarmChain *) bsearch((void *) itemptr,
-								(void *) vacrelstats->warm_chains,
-								vacrelstats->num_warm_chains,
-								sizeof(LVWarmChain),
-								vac_cmp_warm_chain);
+				(void *) (vacrelstats->warm_chains - vacrelstats->num_warm_chains),
+				vacrelstats->num_warm_chains,
+				sizeof(LVWarmChain),
+				vac_cmp_warm_chain);
 	if (chain != NULL)
 	{
 		if (is_warm)
@@ -2500,13 +2658,13 @@ static IndexBulkDeleteCallbackResult
 lazy_indexvac_phase2(ItemPointer itemptr, bool is_warm, void *state)
 {
 	LVRelStats		*vacrelstats = (LVRelStats *) state;
-	LVWarmChain	*chain;
+	LVWarmChain		*chain;
 
 	chain = (LVWarmChain *) bsearch((void *) itemptr,
-								(void *) vacrelstats->warm_chains,
-								vacrelstats->num_warm_chains,
-								sizeof(LVWarmChain),
-								vac_cmp_warm_chain);
+				(void *) (vacrelstats->warm_chains - vacrelstats->num_warm_chains),
+				vacrelstats->num_warm_chains,
+				sizeof(LVWarmChain),
+				vac_cmp_warm_chain);
 
 	if (chain != NULL && (chain->keep_warm_chain != 1))
 	{
@@ -2619,6 +2777,7 @@ lazy_indexvac_phase2(ItemPointer itemptr, bool is_warm, void *state)
 		 * index pointers.
 		 */
 		chain->keep_warm_chain = 1;
+		vacrelstats->num_non_convertible_warm_chains++;
 		return IBDCR_KEEP;
 	}
 	return IBDCR_KEEP;
@@ -2627,6 +2786,9 @@ lazy_indexvac_phase2(ItemPointer itemptr, bool is_warm, void *state)
 /*
  * Comparator routines for use with qsort() and bsearch(). Similar to
  * vac_cmp_itemptr, but right hand argument is LVWarmChain struct pointer.
+ *
+ * The warm_chains array is sorted in descending order hence the return values
+ * are flipped.
  */
 static int
 vac_cmp_warm_chain(const void *left, const void *right)
@@ -2640,17 +2802,17 @@ vac_cmp_warm_chain(const void *left, const void *right)
 	rblk = ItemPointerGetBlockNumber(&((LVWarmChain *) right)->chain_tid);
 
 	if (lblk < rblk)
-		return -1;
-	if (lblk > rblk)
 		return 1;
+	if (lblk > rblk)
+		return -1;
 
 	loff = ItemPointerGetOffsetNumber((ItemPointer) left);
 	roff = ItemPointerGetOffsetNumber(&((LVWarmChain *) right)->chain_tid);
 
 	if (loff < roff)
-		return -1;
-	if (loff > roff)
 		return 1;
+	if (loff > roff)
+		return -1;
 
 	return 0;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d53a29..1592220 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -433,7 +433,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	overlay_placing substr_from substr_for
 
 %type <boolean> opt_instead
-%type <boolean> opt_unique opt_concurrently opt_verbose opt_full
+%type <boolean> opt_unique opt_concurrently opt_verbose opt_full opt_warmclean
 %type <boolean> opt_freeze opt_default opt_recheck
 %type <defelt>	opt_binary opt_oids copy_delimiter
 
@@ -684,7 +684,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VIEWS VOLATILE
 
-	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
+	WARMCLEAN WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLNAMESPACES
 	XMLPARSE XMLPI XMLROOT XMLSERIALIZE XMLTABLE
@@ -10059,7 +10059,7 @@ cluster_index_specification:
  *
  *****************************************************************************/
 
-VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
+VacuumStmt: VACUUM opt_full opt_freeze opt_verbose opt_warmclean
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
 					n->options = VACOPT_VACUUM;
@@ -10069,11 +10069,13 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
 						n->options |= VACOPT_FREEZE;
 					if ($4)
 						n->options |= VACOPT_VERBOSE;
+					if ($5)
+						n->options |= VACOPT_WARM_CLEANUP;
 					n->relation = NULL;
 					n->va_cols = NIL;
 					$$ = (Node *)n;
 				}
-			| VACUUM opt_full opt_freeze opt_verbose qualified_name
+			| VACUUM opt_full opt_freeze opt_verbose opt_warmclean qualified_name
 				{
 					VacuumStmt *n = makeNode(VacuumStmt);
 					n->options = VACOPT_VACUUM;
@@ -10083,13 +10085,15 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
 						n->options |= VACOPT_FREEZE;
 					if ($4)
 						n->options |= VACOPT_VERBOSE;
-					n->relation = $5;
+					if ($5)
+						n->options |= VACOPT_WARM_CLEANUP;
+					n->relation = $6;
 					n->va_cols = NIL;
 					$$ = (Node *)n;
 				}
-			| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
+			| VACUUM opt_full opt_freeze opt_verbose opt_warmclean AnalyzeStmt
 				{
-					VacuumStmt *n = (VacuumStmt *) $5;
+					VacuumStmt *n = (VacuumStmt *) $6;
 					n->options |= VACOPT_VACUUM;
 					if ($2)
 						n->options |= VACOPT_FULL;
@@ -10097,6 +10101,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
 						n->options |= VACOPT_FREEZE;
 					if ($4)
 						n->options |= VACOPT_VERBOSE;
+					if ($5)
+						n->options |= VACOPT_WARM_CLEANUP;
 					$$ = (Node *)n;
 				}
 			| VACUUM '(' vacuum_option_list ')'
@@ -10129,6 +10135,7 @@ vacuum_option_elem:
 			| VERBOSE			{ $$ = VACOPT_VERBOSE; }
 			| FREEZE			{ $$ = VACOPT_FREEZE; }
 			| FULL				{ $$ = VACOPT_FULL; }
+			| WARMCLEAN			{ $$ = VACOPT_WARM_CLEANUP; }
 			| IDENT
 				{
 					if (strcmp($1, "disable_page_skipping") == 0)
@@ -10182,6 +10189,10 @@ opt_freeze: FREEZE									{ $$ = TRUE; }
 			| /*EMPTY*/								{ $$ = FALSE; }
 		;
 
+opt_warmclean: WARMCLEAN							{ $$ = TRUE; }
+			| /*EMPTY*/								{ $$ = FALSE; }
+		;
+
 opt_name_list:
 			'(' name_list ')'						{ $$ = $2; }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -14886,6 +14897,7 @@ type_func_name_keyword:
 			| SIMILAR
 			| TABLESAMPLE
 			| VERBOSE
+			| WARMCLEAN
 		;
 
 /* Reserved keyword --- these keywords are usable only as a ColLabel.
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..a157c05 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -117,6 +117,8 @@ int			autovacuum_vac_thresh;
 double		autovacuum_vac_scale;
 int			autovacuum_anl_thresh;
 double		autovacuum_anl_scale;
+double		autovacuum_warmcleanup_scale;
+double		autovacuum_warmcleanup_index_scale;
 int			autovacuum_freeze_max_age;
 int			autovacuum_multixact_freeze_max_age;
 
@@ -338,7 +340,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
 						  Form_pg_class classForm,
 						  PgStat_StatTabEntry *tabentry,
 						  int effective_multixact_freeze_max_age,
-						  bool *dovacuum, bool *doanalyze, bool *wraparound);
+						  bool *dovacuum, bool *doanalyze, bool *wraparound,
+						  bool *dowarmcleanup);
 
 static void autovacuum_do_vac_analyze(autovac_table *tab,
 						  BufferAccessStrategy bstrategy);
@@ -2076,6 +2079,7 @@ do_autovacuum(void)
 		bool		dovacuum;
 		bool		doanalyze;
 		bool		wraparound;
+		bool		dowarmcleanup;
 
 		if (classForm->relkind != RELKIND_RELATION &&
 			classForm->relkind != RELKIND_MATVIEW)
@@ -2115,10 +2119,14 @@ do_autovacuum(void)
 		tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared,
 											 shared, dbentry);
 
-		/* Check if it needs vacuum or analyze */
+		/* 
+		 * Check if it needs vacuum or analyze. For vacuum, also check if it
+		 * needs WARM cleanup.
+		 */
 		relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
 								  effective_multixact_freeze_max_age,
-								  &dovacuum, &doanalyze, &wraparound);
+								  &dovacuum, &doanalyze, &wraparound,
+								  &dowarmcleanup);
 
 		/* Relations that need work are added to table_oids */
 		if (dovacuum || doanalyze)
@@ -2171,6 +2179,7 @@ do_autovacuum(void)
 		bool		dovacuum;
 		bool		doanalyze;
 		bool		wraparound;
+		bool		dowarmcleanup;
 
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
@@ -2201,7 +2210,8 @@ do_autovacuum(void)
 
 		relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
 								  effective_multixact_freeze_max_age,
-								  &dovacuum, &doanalyze, &wraparound);
+								  &dovacuum, &doanalyze, &wraparound,
+								  &dowarmcleanup);
 
 		/* ignore analyze for toast tables */
 		if (dovacuum)
@@ -2792,6 +2802,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 	HeapTuple	classTup;
 	bool		dovacuum;
 	bool		doanalyze;
+	bool		dowarmcleanup;
 	autovac_table *tab = NULL;
 	PgStat_StatTabEntry *tabentry;
 	PgStat_StatDBEntry *shared;
@@ -2833,7 +2844,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 
 	relation_needs_vacanalyze(relid, avopts, classForm, tabentry,
 							  effective_multixact_freeze_max_age,
-							  &dovacuum, &doanalyze, &wraparound);
+							  &dovacuum, &doanalyze, &wraparound,
+							  &dowarmcleanup);
 
 	/* ignore ANALYZE for toast tables */
 	if (classForm->relkind == RELKIND_TOASTVALUE)
@@ -2849,6 +2861,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 		int			vac_cost_limit;
 		int			vac_cost_delay;
 		int			log_min_duration;
+		double		warmcleanup_index_scale;
 
 		/*
 		 * Calculate the vacuum cost parameters and the freeze ages.  If there
@@ -2895,19 +2908,26 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 			? avopts->multixact_freeze_table_age
 			: default_multixact_freeze_table_age;
 
+		warmcleanup_index_scale = (avopts &&
+								   avopts->warmcleanup_index_scale >= 0)
+			? avopts->warmcleanup_index_scale
+			: autovacuum_warmcleanup_index_scale;
+
 		tab = palloc(sizeof(autovac_table));
 		tab->at_relid = relid;
 		tab->at_sharedrel = classForm->relisshared;
 		tab->at_vacoptions = VACOPT_SKIPTOAST |
 			(dovacuum ? VACOPT_VACUUM : 0) |
 			(doanalyze ? VACOPT_ANALYZE : 0) |
-			(!wraparound ? VACOPT_NOWAIT : 0);
+			(!wraparound ? VACOPT_NOWAIT : 0) |
+			(dowarmcleanup ? VACOPT_WARM_CLEANUP : 0);
 		tab->at_params.freeze_min_age = freeze_min_age;
 		tab->at_params.freeze_table_age = freeze_table_age;
 		tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age;
 		tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
 		tab->at_params.is_wraparound = wraparound;
 		tab->at_params.log_min_duration = log_min_duration;
+		tab->at_params.warmcleanup_index_scale = warmcleanup_index_scale;
 		tab->at_vacuum_cost_limit = vac_cost_limit;
 		tab->at_vacuum_cost_delay = vac_cost_delay;
 		tab->at_relname = NULL;
@@ -2974,7 +2994,8 @@ relation_needs_vacanalyze(Oid relid,
  /* output params below */
 						  bool *dovacuum,
 						  bool *doanalyze,
-						  bool *wraparound)
+						  bool *wraparound,
+						  bool *dowarmcleanup)
 {
 	bool		force_vacuum;
 	bool		av_enabled;
@@ -2986,6 +3007,9 @@ relation_needs_vacanalyze(Oid relid,
 	float4		vac_scale_factor,
 				anl_scale_factor;
 
+	/* constant from reloptions or GUC valriable */
+	float4		warmcleanup_scale_factor;
+
 	/* thresholds calculated from above constants */
 	float4		vacthresh,
 				anlthresh;
@@ -2994,6 +3018,9 @@ relation_needs_vacanalyze(Oid relid,
 	float4		vactuples,
 				anltuples;
 
+	/* number of WARM chains in the table */
+	float4		warmchains;
+
 	/* freeze parameters */
 	int			freeze_max_age;
 	int			multixact_freeze_max_age;
@@ -3026,6 +3053,11 @@ relation_needs_vacanalyze(Oid relid,
 		? relopts->analyze_threshold
 		: autovacuum_anl_thresh;
 
+	/* Use table specific value or the GUC value */
+	warmcleanup_scale_factor = (relopts && relopts->warmcleanup_scale_factor >= 0)
+		? relopts->warmcleanup_scale_factor
+		: autovacuum_warmcleanup_scale;
+
 	freeze_max_age = (relopts && relopts->freeze_max_age >= 0)
 		? Min(relopts->freeze_max_age, autovacuum_freeze_max_age)
 		: autovacuum_freeze_max_age;
@@ -3073,6 +3105,7 @@ relation_needs_vacanalyze(Oid relid,
 		reltuples = classForm->reltuples;
 		vactuples = tabentry->n_dead_tuples;
 		anltuples = tabentry->changes_since_analyze;
+		warmchains = tabentry->n_warm_chains;
 
 		vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples;
 		anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples;
@@ -3089,6 +3122,17 @@ relation_needs_vacanalyze(Oid relid,
 		/* Determine if this table needs vacuum or analyze. */
 		*dovacuum = force_vacuum || (vactuples > vacthresh);
 		*doanalyze = (anltuples > anlthresh);
+
+		/*
+		 * If the number of WARM chains in the is more than the configured
+		 * fraction, then we also do a WARM cleanup. This only triggers at the
+		 * table level, but we then look at each index and do cleanup for the
+		 * index only if the WARM pointers in the index are more than
+		 * configured index-level scale factor. lazy_vacuum_index() later deals
+		 * with that.
+		 */
+		if (*dovacuum && (warmcleanup_scale_factor * reltuples < warmchains))
+			*dowarmcleanup = true;
 	}
 	else
 	{
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 52fe4ba..f38ce8a 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -226,9 +226,11 @@ typedef struct TwoPhasePgStatRecord
 	PgStat_Counter tuples_inserted;		/* tuples inserted in xact */
 	PgStat_Counter tuples_updated;		/* tuples updated in xact */
 	PgStat_Counter tuples_deleted;		/* tuples deleted in xact */
+	PgStat_Counter tuples_warm_updated;	/* tuples warm updated in xact */
 	PgStat_Counter inserted_pre_trunc;	/* tuples inserted prior to truncate */
 	PgStat_Counter updated_pre_trunc;	/* tuples updated prior to truncate */
 	PgStat_Counter deleted_pre_trunc;	/* tuples deleted prior to truncate */
+	PgStat_Counter warm_updated_pre_trunc;	/* tuples warm updated prior to truncate */
 	Oid			t_id;			/* table's OID */
 	bool		t_shared;		/* is it a shared catalog? */
 	bool		t_truncated;	/* was the relation truncated? */
@@ -1367,7 +1369,8 @@ pgstat_report_autovac(Oid dboid)
  */
 void
 pgstat_report_vacuum(Oid tableoid, bool shared,
-					 PgStat_Counter livetuples, PgStat_Counter deadtuples)
+					 PgStat_Counter livetuples, PgStat_Counter deadtuples,
+					 PgStat_Counter warmchains)
 {
 	PgStat_MsgVacuum msg;
 
@@ -1381,6 +1384,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	msg.m_vacuumtime = GetCurrentTimestamp();
 	msg.m_live_tuples = livetuples;
 	msg.m_dead_tuples = deadtuples;
+	msg.m_warm_chains = warmchains;
 	pgstat_send(&msg, sizeof(msg));
 }
 
@@ -1396,7 +1400,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 void
 pgstat_report_analyze(Relation rel,
 					  PgStat_Counter livetuples, PgStat_Counter deadtuples,
-					  bool resetcounter)
+					  PgStat_Counter warmchains, bool resetcounter)
 {
 	PgStat_MsgAnalyze msg;
 
@@ -1421,12 +1425,14 @@ pgstat_report_analyze(Relation rel,
 		{
 			livetuples -= trans->tuples_inserted - trans->tuples_deleted;
 			deadtuples -= trans->tuples_updated + trans->tuples_deleted;
+			warmchains -= trans->tuples_warm_updated;
 		}
 		/* count stuff inserted by already-aborted subxacts, too */
 		deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
 		/* Since ANALYZE's counts are estimates, we could have underflowed */
 		livetuples = Max(livetuples, 0);
 		deadtuples = Max(deadtuples, 0);
+		warmchains = Max(warmchains, 0);
 	}
 
 	pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ANALYZE);
@@ -1437,6 +1443,7 @@ pgstat_report_analyze(Relation rel,
 	msg.m_analyzetime = GetCurrentTimestamp();
 	msg.m_live_tuples = livetuples;
 	msg.m_dead_tuples = deadtuples;
+	msg.m_warm_chains = warmchains;
 	pgstat_send(&msg, sizeof(msg));
 }
 
@@ -1907,7 +1914,10 @@ pgstat_count_heap_update(Relation rel, bool hot, bool warm)
 		if (hot)
 			pgstat_info->t_counts.t_tuples_hot_updated++;
 		else if (warm)
+		{
+			pgstat_info->trans->tuples_warm_updated++;
 			pgstat_info->t_counts.t_tuples_warm_updated++;
+		}
 	}
 }
 
@@ -2070,6 +2080,12 @@ AtEOXact_PgStat(bool isCommit)
 				/* update and delete each create a dead tuple */
 				tabstat->t_counts.t_delta_dead_tuples +=
 					trans->tuples_updated + trans->tuples_deleted;
+				/* 
+				 * commit or abort, a WARM update generates a WARM chain which
+				 * needs cleanup.
+				 */
+				tabstat->t_counts.t_delta_warm_chains +=
+					trans->tuples_warm_updated;
 				/* insert, update, delete each count as one change event */
 				tabstat->t_counts.t_changed_tuples +=
 					trans->tuples_inserted + trans->tuples_updated +
@@ -2080,6 +2096,12 @@ AtEOXact_PgStat(bool isCommit)
 				/* inserted tuples are dead, deleted tuples are unaffected */
 				tabstat->t_counts.t_delta_dead_tuples +=
 					trans->tuples_inserted + trans->tuples_updated;
+				/* 
+				 * commit or abort, a WARM update generates a WARM chain which
+				 * needs cleanup.
+				 */
+				tabstat->t_counts.t_delta_warm_chains +=
+					trans->tuples_warm_updated;
 				/* an aborted xact generates no changed_tuple events */
 			}
 			tabstat->trans = NULL;
@@ -2136,12 +2158,16 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 						trans->upper->tuples_inserted = trans->tuples_inserted;
 						trans->upper->tuples_updated = trans->tuples_updated;
 						trans->upper->tuples_deleted = trans->tuples_deleted;
+						trans->upper->tuples_warm_updated =
+							trans->tuples_warm_updated;
 					}
 					else
 					{
 						trans->upper->tuples_inserted += trans->tuples_inserted;
 						trans->upper->tuples_updated += trans->tuples_updated;
 						trans->upper->tuples_deleted += trans->tuples_deleted;
+						trans->upper->tuples_warm_updated +=
+							trans->tuples_warm_updated;
 					}
 					tabstat->trans = trans->upper;
 					pfree(trans);
@@ -2177,9 +2203,13 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 				tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
 				tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
 				tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
+				tabstat->t_counts.t_tuples_warm_updated +=
+					trans->tuples_warm_updated;
 				/* inserted tuples are dead, deleted tuples are unaffected */
 				tabstat->t_counts.t_delta_dead_tuples +=
 					trans->tuples_inserted + trans->tuples_updated;
+				tabstat->t_counts.t_delta_warm_chains +=
+					trans->tuples_warm_updated;
 				tabstat->trans = trans->upper;
 				pfree(trans);
 			}
@@ -2221,9 +2251,11 @@ AtPrepare_PgStat(void)
 			record.tuples_inserted = trans->tuples_inserted;
 			record.tuples_updated = trans->tuples_updated;
 			record.tuples_deleted = trans->tuples_deleted;
+			record.tuples_warm_updated = trans->tuples_warm_updated;
 			record.inserted_pre_trunc = trans->inserted_pre_trunc;
 			record.updated_pre_trunc = trans->updated_pre_trunc;
 			record.deleted_pre_trunc = trans->deleted_pre_trunc;
+			record.warm_updated_pre_trunc = trans->warm_updated_pre_trunc;
 			record.t_id = tabstat->t_id;
 			record.t_shared = tabstat->t_shared;
 			record.t_truncated = trans->truncated;
@@ -2298,11 +2330,14 @@ pgstat_twophase_postcommit(TransactionId xid, uint16 info,
 		/* forget live/dead stats seen by backend thus far */
 		pgstat_info->t_counts.t_delta_live_tuples = 0;
 		pgstat_info->t_counts.t_delta_dead_tuples = 0;
+		pgstat_info->t_counts.t_delta_warm_chains = 0;
 	}
 	pgstat_info->t_counts.t_delta_live_tuples +=
 		rec->tuples_inserted - rec->tuples_deleted;
 	pgstat_info->t_counts.t_delta_dead_tuples +=
 		rec->tuples_updated + rec->tuples_deleted;
+	pgstat_info->t_counts.t_delta_warm_chains +=
+		rec->tuples_warm_updated;
 	pgstat_info->t_counts.t_changed_tuples +=
 		rec->tuples_inserted + rec->tuples_updated +
 		rec->tuples_deleted;
@@ -2330,12 +2365,16 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info,
 		rec->tuples_inserted = rec->inserted_pre_trunc;
 		rec->tuples_updated = rec->updated_pre_trunc;
 		rec->tuples_deleted = rec->deleted_pre_trunc;
+		rec->tuples_warm_updated = rec->warm_updated_pre_trunc;
 	}
 	pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
 	pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
 	pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
+	pgstat_info->t_counts.t_tuples_warm_updated += rec->tuples_warm_updated;
 	pgstat_info->t_counts.t_delta_dead_tuples +=
 		rec->tuples_inserted + rec->tuples_updated;
+	pgstat_info->t_counts.t_delta_warm_chains +=
+		rec->tuples_warm_updated;
 }
 
 
@@ -4526,6 +4565,7 @@ pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry, Oid tableoid, bool create)
 		result->tuples_warm_updated = 0;
 		result->n_live_tuples = 0;
 		result->n_dead_tuples = 0;
+		result->n_warm_chains = 0;
 		result->changes_since_analyze = 0;
 		result->blocks_fetched = 0;
 		result->blocks_hit = 0;
@@ -5636,6 +5676,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 			tabentry->tuples_warm_updated = tabmsg->t_counts.t_tuples_warm_updated;
 			tabentry->n_live_tuples = tabmsg->t_counts.t_delta_live_tuples;
 			tabentry->n_dead_tuples = tabmsg->t_counts.t_delta_dead_tuples;
+			tabentry->n_warm_chains = tabmsg->t_counts.t_delta_warm_chains;
 			tabentry->changes_since_analyze = tabmsg->t_counts.t_changed_tuples;
 			tabentry->blocks_fetched = tabmsg->t_counts.t_blocks_fetched;
 			tabentry->blocks_hit = tabmsg->t_counts.t_blocks_hit;
@@ -5667,9 +5708,11 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 			{
 				tabentry->n_live_tuples = 0;
 				tabentry->n_dead_tuples = 0;
+				tabentry->n_warm_chains = 0;
 			}
 			tabentry->n_live_tuples += tabmsg->t_counts.t_delta_live_tuples;
 			tabentry->n_dead_tuples += tabmsg->t_counts.t_delta_dead_tuples;
+			tabentry->n_warm_chains += tabmsg->t_counts.t_delta_warm_chains;
 			tabentry->changes_since_analyze += tabmsg->t_counts.t_changed_tuples;
 			tabentry->blocks_fetched += tabmsg->t_counts.t_blocks_fetched;
 			tabentry->blocks_hit += tabmsg->t_counts.t_blocks_hit;
@@ -5679,6 +5722,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 		tabentry->n_live_tuples = Max(tabentry->n_live_tuples, 0);
 		/* Likewise for n_dead_tuples */
 		tabentry->n_dead_tuples = Max(tabentry->n_dead_tuples, 0);
+		tabentry->n_warm_chains = Max(tabentry->n_warm_chains, 0);
 
 		/*
 		 * Add per-table stats to the per-database entry, too.
@@ -5904,6 +5948,7 @@ pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len)
 
 	tabentry->n_live_tuples = msg->m_live_tuples;
 	tabentry->n_dead_tuples = msg->m_dead_tuples;
+	tabentry->n_warm_chains = msg->m_warm_chains;
 
 	if (msg->m_autovacuum)
 	{
@@ -5938,6 +5983,7 @@ pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len)
 
 	tabentry->n_live_tuples = msg->m_live_tuples;
 	tabentry->n_dead_tuples = msg->m_dead_tuples;
+	tabentry->n_warm_chains = msg->m_warm_chains;
 
 	/*
 	 * If commanded, reset changes_since_analyze to zero.  This forgets any
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 227a87d..8804908 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -193,6 +193,21 @@ pg_stat_get_dead_tuples(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_warm_chains(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *tabentry;
+
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (tabentry->n_warm_chains);
+
+	PG_RETURN_INT64(result);
+}
+
 
 Datum
 pg_stat_get_mod_since_analyze(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 08b6030..81fec03 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -130,6 +130,7 @@ int			VacuumCostPageMiss = 10;
 int			VacuumCostPageDirty = 20;
 int			VacuumCostLimit = 200;
 int			VacuumCostDelay = 0;
+double		VacuumWarmCleanupScale;
 
 int			VacuumPageHit = 0;
 int			VacuumPageMiss = 0;
@@ -137,3 +138,5 @@ int			VacuumPageDirty = 0;
 
 int			VacuumCostBalance = 0;		/* working state for vacuum */
 bool		VacuumCostActive = false;
+
+double		VacuumWarmCleanupIndexScale = 1;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8b5f064..ecf8028 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3017,6 +3017,36 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
+		{"autovacuum_warmcleanup_scale_factor", PGC_SIGHUP, AUTOVACUUM,
+			gettext_noop("Number of WARM chains prior to cleanup as a fraction of reltuples."),
+			NULL
+		},
+		&autovacuum_warmcleanup_scale,
+		0.1, 0.0, 100.0,
+		NULL, NULL, NULL
+	},
+
+	{
+		{"autovacuum_warmcleanup_index_scale_factor", PGC_SIGHUP, AUTOVACUUM,
+			gettext_noop("Number of WARM pointers prior to cleanup as a fraction of total WARM chains."),
+			NULL
+		},
+		&autovacuum_warmcleanup_index_scale,
+		0.2, 0.0, 100.0,
+		NULL, NULL, NULL
+	},
+
+	{
+		{"vacuum_warmcleanup_index_scale_factor", PGC_USERSET, WARM_CLEANUP,
+			gettext_noop("Number of WARM pointers in the index prior to cleanup as a fraction of total WARM chains."),
+			NULL
+		},
+		&VacuumWarmCleanupIndexScale,
+		0.2, 0.0, 100.0,
+		NULL, NULL, NULL
+	},
+
+	{
 		{"checkpoint_completion_target", PGC_SIGHUP, WAL_CHECKPOINTS,
 			gettext_noop("Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval."),
 			NULL
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 3f1a142..61a4e23 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2795,6 +2795,8 @@ DATA(insert OID = 2878 (  pg_stat_get_live_tuples	PGNSP PGUID 12 1 0 0 0 f f f f
 DESCR("statistics: number of live tuples");
 DATA(insert OID = 2879 (  pg_stat_get_dead_tuples	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_dead_tuples _null_ _null_ _null_ ));
 DESCR("statistics: number of dead tuples");
+DATA(insert OID = 3403 (  pg_stat_get_warm_chains	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_warm_chains _null_ _null_ _null_ ));
+DESCR("statistics: number of warm chains");
 DATA(insert OID = 3177 (  pg_stat_get_mod_since_analyze PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_mod_since_analyze _null_ _null_ _null_ ));
 DESCR("statistics: number of tuples changed since last analyze");
 DATA(insert OID = 1934 (  pg_stat_get_blocks_fetched	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_blocks_fetched _null_ _null_ _null_ ));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 541c2fa..9914143 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -145,6 +145,8 @@ typedef struct VacuumParams
 	int			log_min_duration;		/* minimum execution threshold in ms
 										 * at which  verbose logs are
 										 * activated, -1 to use default */
+	double		warmcleanup_index_scale; /* Fraction of WARM pointers to cause
+										  * index WARM cleanup */
 } VacuumParams;
 
 /* GUC parameters */
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 6ca44f7..2993b1a 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -134,7 +134,8 @@ typedef void (*ExplainDirectModify_function) (ForeignScanState *node,
 typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 											   HeapTuple *rows, int targrows,
 												  double *totalrows,
-												  double *totaldeadrows);
+												  double *totaldeadrows,
+												  double *totalwarmchains);
 
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 4c607b2..901960a 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -255,6 +255,7 @@ extern int	VacuumPageDirty;
 extern int	VacuumCostBalance;
 extern bool VacuumCostActive;
 
+extern double VacuumWarmCleanupIndexScale;
 
 /* in tcop/postgres.c */
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b2afd50..f5fc001 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3039,7 +3039,8 @@ typedef enum VacuumOption
 	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
 	VACOPT_NOWAIT = 1 << 5,		/* don't wait to get lock (autovacuum only) */
 	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
-	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7		/* don't skip any pages */
+	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7,		/* don't skip any pages */
+	VACOPT_WARM_CLEANUP = 1 << 8	/* do WARM cleanup */
 } VacuumOption;
 
 typedef struct VacuumStmt
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index cd21a78..7d9818b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -433,6 +433,7 @@ PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
+PG_KEYWORD("warmclean", WARMCLEAN, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("when", WHEN, RESERVED_KEYWORD)
 PG_KEYWORD("where", WHERE, RESERVED_KEYWORD)
 PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 99bdc8b..883cbd4 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -110,6 +110,7 @@ typedef struct PgStat_TableCounts
 
 	PgStat_Counter t_delta_live_tuples;
 	PgStat_Counter t_delta_dead_tuples;
+	PgStat_Counter t_delta_warm_chains;
 	PgStat_Counter t_changed_tuples;
 
 	PgStat_Counter t_blocks_fetched;
@@ -167,11 +168,13 @@ typedef struct PgStat_TableXactStatus
 {
 	PgStat_Counter tuples_inserted;		/* tuples inserted in (sub)xact */
 	PgStat_Counter tuples_updated;		/* tuples updated in (sub)xact */
+	PgStat_Counter tuples_warm_updated;	/* tuples warm-updated in (sub)xact */
 	PgStat_Counter tuples_deleted;		/* tuples deleted in (sub)xact */
 	bool		truncated;		/* relation truncated in this (sub)xact */
 	PgStat_Counter inserted_pre_trunc;	/* tuples inserted prior to truncate */
 	PgStat_Counter updated_pre_trunc;	/* tuples updated prior to truncate */
 	PgStat_Counter deleted_pre_trunc;	/* tuples deleted prior to truncate */
+	PgStat_Counter warm_updated_pre_trunc;	/* tuples warm updated prior to truncate */
 	int			nest_level;		/* subtransaction nest level */
 	/* links to other structs for same relation: */
 	struct PgStat_TableXactStatus *upper;		/* next higher subxact if any */
@@ -370,6 +373,7 @@ typedef struct PgStat_MsgVacuum
 	TimestampTz m_vacuumtime;
 	PgStat_Counter m_live_tuples;
 	PgStat_Counter m_dead_tuples;
+	PgStat_Counter m_warm_chains;
 } PgStat_MsgVacuum;
 
 
@@ -388,6 +392,7 @@ typedef struct PgStat_MsgAnalyze
 	TimestampTz m_analyzetime;
 	PgStat_Counter m_live_tuples;
 	PgStat_Counter m_dead_tuples;
+	PgStat_Counter m_warm_chains;
 } PgStat_MsgAnalyze;
 
 
@@ -630,6 +635,7 @@ typedef struct PgStat_StatTabEntry
 
 	PgStat_Counter n_live_tuples;
 	PgStat_Counter n_dead_tuples;
+	PgStat_Counter n_warm_chains;
 	PgStat_Counter changes_since_analyze;
 
 	PgStat_Counter blocks_fetched;
@@ -1156,10 +1162,11 @@ extern void pgstat_reset_single_counter(Oid objectid, PgStat_Single_Reset_Type t
 
 extern void pgstat_report_autovac(Oid dboid);
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
-					 PgStat_Counter livetuples, PgStat_Counter deadtuples);
+					 PgStat_Counter livetuples, PgStat_Counter deadtuples,
+					 PgStat_Counter warmchains);
 extern void pgstat_report_analyze(Relation rel,
 					  PgStat_Counter livetuples, PgStat_Counter deadtuples,
-					  bool resetcounter);
+					  PgStat_Counter warmchains, bool resetcounter);
 
 extern void pgstat_report_recovery_conflict(int reason);
 extern void pgstat_report_deadlock(void);
diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h
index d383fd3..19fb0a2 100644
--- a/src/include/postmaster/autovacuum.h
+++ b/src/include/postmaster/autovacuum.h
@@ -39,6 +39,8 @@ extern int	autovacuum_freeze_max_age;
 extern int	autovacuum_multixact_freeze_max_age;
 extern int	autovacuum_vac_cost_delay;
 extern int	autovacuum_vac_cost_limit;
+extern double autovacuum_warmcleanup_scale;
+extern double autovacuum_warmcleanup_index_scale;
 
 /* autovacuum launcher PID, only valid when worker is shutting down */
 extern int	AutovacuumLauncherPid;
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 2da9115..cd4532b 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -68,6 +68,7 @@ enum config_group
 	WAL_SETTINGS,
 	WAL_CHECKPOINTS,
 	WAL_ARCHIVING,
+	WARM_CLEANUP,
 	REPLICATION,
 	REPLICATION_SENDING,
 	REPLICATION_MASTER,
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 2b86054..f0dd350 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -278,6 +278,8 @@ typedef struct AutoVacOpts
 	int			log_min_duration;
 	float8		vacuum_scale_factor;
 	float8		analyze_scale_factor;
+	float8		warmcleanup_scale_factor;
+	float8		warmcleanup_index_scale;
 } AutoVacOpts;
 
 typedef struct StdRdOptions
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f7dc4a4..d34aa68 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1759,6 +1759,7 @@ pg_stat_all_tables| SELECT c.oid AS relid,
     pg_stat_get_tuples_warm_updated(c.oid) AS n_tup_warm_upd,
     pg_stat_get_live_tuples(c.oid) AS n_live_tup,
     pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_warm_chains(c.oid) AS n_warm_chains,
     pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
     pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
     pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
@@ -1907,6 +1908,7 @@ pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.n_tup_warm_upd,
     pg_stat_all_tables.n_live_tup,
     pg_stat_all_tables.n_dead_tup,
+    pg_stat_all_tables.n_warm_chains,
     pg_stat_all_tables.n_mod_since_analyze,
     pg_stat_all_tables.last_vacuum,
     pg_stat_all_tables.last_autovacuum,
@@ -1951,6 +1953,7 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.n_tup_warm_upd,
     pg_stat_all_tables.n_live_tup,
     pg_stat_all_tables.n_dead_tup,
+    pg_stat_all_tables.n_warm_chains,
     pg_stat_all_tables.n_mod_since_analyze,
     pg_stat_all_tables.last_vacuum,
     pg_stat_all_tables.last_autovacuum,
diff --git a/src/test/regress/expected/warm.out b/src/test/regress/expected/warm.out
index 1f07272..34cdbe5 100644
--- a/src/test/regress/expected/warm.out
+++ b/src/test/regress/expected/warm.out
@@ -745,6 +745,65 @@ SELECT a, b FROM test_toast_warm WHERE b = 104.20;
 (1 row)
 
 DROP TABLE test_toast_warm;
+-- Test VACUUM
+CREATE TABLE test_vacuum_warm (a int unique, b text, c int, d int, e int);
+CREATE INDEX test_vacuum_warm_index1 ON test_vacuum_warm(b);
+CREATE INDEX test_vacuum_warm_index2 ON test_vacuum_warm(c);
+CREATE INDEX test_vacuum_warm_index3 ON test_vacuum_warm(d);
+INSERT INTO test_vacuum_warm VALUES (1, 'a', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (2, 'b', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (3, 'c', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (4, 'd', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (5, 'e', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (6, 'f', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (7, 'g', 100, 200);
+UPDATE test_vacuum_warm SET b = 'u', c = 300 WHERE a = 1;
+UPDATE test_vacuum_warm SET b = 'v', c = 300 WHERE a = 2;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 3;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 4;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 5;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 6;
+-- a plain vacuum cannot clear WARM chains.
+SET enable_seqscan = false;
+SET enable_bitmapscan = false;
+SET seq_page_cost = 10000;
+VACUUM test_vacuum_warm;
+-- We expect non-zero heap-fetches here
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT b FROM test_vacuum_warm WHERE b = 'u';
+                                        QUERY PLAN                                         
+-------------------------------------------------------------------------------------------
+ Index Only Scan using test_vacuum_warm_index1 on test_vacuum_warm (actual rows=1 loops=1)
+   Index Cond: (b = 'u'::text)
+   Heap Fetches: 1
+(3 rows)
+
+-- Now set vacuum_warmcleanup_index_scale_factor such that only
+-- test_vacuum_warm_index2 can be cleaned up.
+SET vacuum_warmcleanup_index_scale_factor=0.5;
+VACUUM WARMCLEAN test_vacuum_warm;
+-- We expect non-zero heap-fetches here
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT b FROM test_vacuum_warm WHERE b = 'u';
+                                        QUERY PLAN                                         
+-------------------------------------------------------------------------------------------
+ Index Only Scan using test_vacuum_warm_index1 on test_vacuum_warm (actual rows=1 loops=1)
+   Index Cond: (b = 'u'::text)
+   Heap Fetches: 1
+(3 rows)
+
+-- All WARM chains cleaned up, so index-only scan should be used now without
+-- any heap fetches
+SET vacuum_warmcleanup_index_scale_factor=0;
+VACUUM WARMCLEAN test_vacuum_warm;
+-- We expect zero heap-fetches now
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT b FROM test_vacuum_warm WHERE b = 'u';
+                                        QUERY PLAN                                         
+-------------------------------------------------------------------------------------------
+ Index Only Scan using test_vacuum_warm_index1 on test_vacuum_warm (actual rows=1 loops=1)
+   Index Cond: (b = 'u'::text)
+   Heap Fetches: 0
+(3 rows)
+
+DROP TABLE test_vacuum_warm;
 -- Toasted heap attributes
 CREATE TABLE toasttest(descr text , cnt int DEFAULT 0, f1 text, f2 text);
 CREATE INDEX testindx1 ON toasttest(descr);
diff --git a/src/test/regress/sql/warm.sql b/src/test/regress/sql/warm.sql
index fc80c0f..ae9db9a 100644
--- a/src/test/regress/sql/warm.sql
+++ b/src/test/regress/sql/warm.sql
@@ -285,6 +285,53 @@ SELECT a, b FROM test_toast_warm WHERE b = 104.20;
 
 DROP TABLE test_toast_warm;
 
+-- Test VACUUM
+
+CREATE TABLE test_vacuum_warm (a int unique, b text, c int, d int, e int);
+CREATE INDEX test_vacuum_warm_index1 ON test_vacuum_warm(b);
+CREATE INDEX test_vacuum_warm_index2 ON test_vacuum_warm(c);
+CREATE INDEX test_vacuum_warm_index3 ON test_vacuum_warm(d);
+
+INSERT INTO test_vacuum_warm VALUES (1, 'a', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (2, 'b', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (3, 'c', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (4, 'd', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (5, 'e', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (6, 'f', 100, 200);
+INSERT INTO test_vacuum_warm VALUES (7, 'g', 100, 200);
+
+UPDATE test_vacuum_warm SET b = 'u', c = 300 WHERE a = 1;
+UPDATE test_vacuum_warm SET b = 'v', c = 300 WHERE a = 2;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 3;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 4;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 5;
+UPDATE test_vacuum_warm SET c = 300 WHERE a = 6;
+
+-- a plain vacuum cannot clear WARM chains.
+SET enable_seqscan = false;
+SET enable_bitmapscan = false;
+SET seq_page_cost = 10000;
+VACUUM test_vacuum_warm;
+-- We expect non-zero heap-fetches here
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT b FROM test_vacuum_warm WHERE b = 'u';
+
+-- Now set vacuum_warmcleanup_index_scale_factor such that only
+-- test_vacuum_warm_index2 can be cleaned up.
+SET vacuum_warmcleanup_index_scale_factor=0.5;
+VACUUM WARMCLEAN test_vacuum_warm;
+-- We expect non-zero heap-fetches here
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT b FROM test_vacuum_warm WHERE b = 'u';
+
+
+-- All WARM chains cleaned up, so index-only scan should be used now without
+-- any heap fetches
+SET vacuum_warmcleanup_index_scale_factor=0;
+VACUUM WARMCLEAN test_vacuum_warm;
+-- We expect zero heap-fetches now
+EXPLAIN (analyze, costs off, timing off, summary off) SELECT b FROM test_vacuum_warm WHERE b = 'u';
+
+DROP TABLE test_vacuum_warm;
+
 -- Toasted heap attributes
 CREATE TABLE toasttest(descr text , cnt int DEFAULT 0, f1 text, f2 text);
 CREATE INDEX testindx1 ON toasttest(descr);
-- 
2.9.3 (Apple Git-75)



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:32       ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-05 19:36         ` Robert Haas <[email protected]>
  2017-04-06 01:37           ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 1 reply; 151+ messages in thread

From: Robert Haas @ 2017-04-05 19:36 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Wed, Apr 5, 2017 at 2:32 PM, Pavan Deolasee <[email protected]> wrote:
>> The only other idea that I have for a really clean solution here is to
>> support this only for index types that are amcanreturn, and actually
>> compare the value stored in the index tuple with the one stored in the
>> heap tuple, ensuring that new index tuples are inserted whenever they
>> don't match and then using the exact same test to determine the
>> applicability of a given index pointer to a given heap tuple.
>
> Just so that I understand, are you suggesting that while inserting WARM
> index pointers, we check if the new index tuple will look exactly the same
> as the old index tuple and not insert a duplicate pointer at all?

Yes.

> I considered that, but it will require us to do an index lookup during WARM
> index insert and for non-unique keys, that may or may not be exactly cheap.

I don't think it requires that.  You should be able to figure out
based on the tuple being updated and the corresponding new tuple
whether this will bet true or not.

> Or we need something like what Claudio wrote to sort all index entries by
> heap TIDs. If we do that, then the recheck can be done just based on the
> index and heap flags (because we can then turn the old index pointer into a
> CLEAR pointer. Index pointer is set to COMMON during initial insert).

Yeah, I think that patch is going to be needed for some of the storage
work I'm interesting in doing, too, so I am tentatively in favor of
it, but I wasn't proposing using it here.

> The other way is to pass old tuple values along with the new tuple values to
> amwarminsert, build index tuples and then do a comparison. For duplicate
> index tuples, skip WARM inserts.

This is more what I was thinking.  But maybe one of the other ideas
you wrote here is better; not sure.

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


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:32       ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 19:36         ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
@ 2017-04-06 01:37           ` Pavan Deolasee <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-06 01:37 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Jaime Casanova <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Thu, Apr 6, 2017 at 1:06 AM, Robert Haas <[email protected]> wrote:

> On Wed, Apr 5, 2017 at 2:32 PM, Pavan Deolasee <[email protected]>
> wrote:
>
> > The other way is to pass old tuple values along with the new tuple
> values to
> > amwarminsert, build index tuples and then do a comparison. For duplicate
> > index tuples, skip WARM inserts.
>
> This is more what I was thinking.  But maybe one of the other ideas
> you wrote here is better; not sure.
>
>
Ok. I think I suggested this as one of the ideas upthread, to support hash
indexes for example. This might be a good safety-net, but AFAIC what we
have today should work since we pretty much construct index tuples in a
consistent way before doing a comparison.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:32       ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
@ 2017-04-14 15:51         ` Jaime Casanova <[email protected]>
  2017-04-18 08:25           ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  1 sibling, 1 reply; 151+ messages in thread

From: Jaime Casanova @ 2017-04-14 15:51 UTC (permalink / raw)
  To: Pavan Deolasee <[email protected]>; +Cc: Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On 5 April 2017 at 13:32, Pavan Deolasee <[email protected]> wrote:
>
> Ok. I've extensively updated the README to match the current state of
> affairs. Updated patch set attached.

Hi Pavan,

I run a test on current warm patchset, i used pgbench with a scale of
20 and a fillfactor of 90 and then start the pgbench run with 6
clients in parallel i also run sqlsmith on it.

And i got a core dump after sometime of those things running.

The assertion that fails is:

"""
LOG:  statement: UPDATE pgbench_tellers SET tbalance = tbalance + 3519
WHERE tid = 34;
TRAP: FailedAssertion("!(((bool) (((const void*)(&tup->t_ctid) !=
((void *)0)) && (((&tup->t_ctid)->ip_posid & ((((uint16) 1) << 13) -
1)) != 0))))", File: "../../../../src/include/access/htup_details.h",
Line: 659)
"""

-- 
Jaime Casanova                      www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#0  0x00007f42d832e067 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007f42d832f448 in __GI_abort () at abort.c:89
#2  0x0000000000842961 in ExceptionalCondition (conditionName=<optimized out>, errorType=<optimized out>, fileName=<optimized out>, lineNumber=<optimized out>)
    at assert.c:54
#3  0x00000000004c48e6 in HeapTupleHeaderGetRootOffset (tup=<optimized out>) at ../../../../src/include/access/htup_details.h:659
#4  heap_update (relation=0x7f4294dc8168, otid=0x2660ff0, newtup=0x6, cid=2, crosscheck=0x7f42d91bc700, wait=-102 '\232', hufd=0x7ffd14fcebb0, 
    lockmode=0x7ffd14fceb9c, modified_attrsp=0x7ffd14fceba8, warm_update=0x7ffd14fceb9b "") at heapam.c:4672
#5  0x000000000062e95d in ExecUpdate (tupleid=0x7ffd14fcec90, oldtuple=0x2324, slot=0x26606f8, planSlot=0xffffffffffffffff, epqstate=0x2660ff0, estate=0x25db9e0, 
    canSetTag=1 '\001') at nodeModifyTable.c:1012
#6  0x000000000062f009 in ExecModifyTable (node=0x25dbc48) at nodeModifyTable.c:1609
#7  0x0000000000616198 in ExecProcNode (node=node@entry=0x25dbc48) at execProcnode.c:424
#8  0x00000000006116a6 in ExecutePlan (execute_once=<optimized out>, dest=0x262e498, direction=<optimized out>, numberTuples=0, sendTuples=<optimized out>, 
    operation=CMD_UPDATE, use_parallel_mode=<optimized out>, planstate=0x25dbc48, estate=0x25db9e0) at execMain.c:1651
#9  standard_ExecutorRun (queryDesc=0x2666100, direction=<optimized out>, count=0, execute_once=<optimized out>) at execMain.c:360
#10 0x0000000000746822 in ProcessQuery (plan=<optimized out>, sourceText=0x25ac4e0 "UPDATE pgbench_tellers SET tbalance = tbalance + 3494 WHERE tid = 76;", 
    params=0x0, queryEnv=0x0, dest=0x262e498, completionTag=0x7ffd14fcf010 "") at pquery.c:162
#11 0x0000000000746a93 in PortalRunMulti (portal=portal@entry=0x25462d0, isTopLevel=isTopLevel@entry=1 '\001', setHoldSnapshot=setHoldSnapshot@entry=0 '\000', 
    dest=dest@entry=0x262e498, altdest=altdest@entry=0x262e498, completionTag=completionTag@entry=0x7ffd14fcf010 "") at pquery.c:1287
#12 0x00000000007476a2 in PortalRun (portal=0x25462d0, count=9223372036854775807, isTopLevel=<optimized out>, run_once=<optimized out>, dest=0x262e498, 
    altdest=0x262e498, completionTag=0x7ffd14fcf010 "") at pquery.c:800
#13 0x000000000074361b in exec_simple_query (query_string=0x2324 <error: Cannot access memory at address 0x2324>) at postgres.c:1105
#14 0x0000000000745254 in PostgresMain (argc=1, argv=0x25ac4e0, dbname=0x2555888 "pgbench", username=0x2528260 "jcasanov") at postgres.c:4075
#15 0x00000000004780f3 in BackendRun (port=0x254dd70) at postmaster.c:4317
#16 BackendStartup (port=0x254dd70) at postmaster.c:3989
#17 ServerLoop () at postmaster.c:1729
#18 0x00000000006d33d0 in PostmasterMain (argc=argc@entry=3, argv=argv@entry=0x25260c0) at postmaster.c:1337
#19 0x0000000000478f4d in main (argc=3, argv=0x25260c0) at main.c:228



-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [text/plain] warm_bt_20170413.txt (2.8K, ../../CAJGNTePTBOn-w4KjVC3K043AbvoavdPLFkCuc3yp9hTEcFY8mQ@mail.gmail.com/2-warm_bt_20170413.txt)
  download | inline:
#0  0x00007f42d832e067 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007f42d832f448 in __GI_abort () at abort.c:89
#2  0x0000000000842961 in ExceptionalCondition (conditionName=<optimized out>, errorType=<optimized out>, fileName=<optimized out>, lineNumber=<optimized out>)
    at assert.c:54
#3  0x00000000004c48e6 in HeapTupleHeaderGetRootOffset (tup=<optimized out>) at ../../../../src/include/access/htup_details.h:659
#4  heap_update (relation=0x7f4294dc8168, otid=0x2660ff0, newtup=0x6, cid=2, crosscheck=0x7f42d91bc700, wait=-102 '\232', hufd=0x7ffd14fcebb0, 
    lockmode=0x7ffd14fceb9c, modified_attrsp=0x7ffd14fceba8, warm_update=0x7ffd14fceb9b "") at heapam.c:4672
#5  0x000000000062e95d in ExecUpdate (tupleid=0x7ffd14fcec90, oldtuple=0x2324, slot=0x26606f8, planSlot=0xffffffffffffffff, epqstate=0x2660ff0, estate=0x25db9e0, 
    canSetTag=1 '\001') at nodeModifyTable.c:1012
#6  0x000000000062f009 in ExecModifyTable (node=0x25dbc48) at nodeModifyTable.c:1609
#7  0x0000000000616198 in ExecProcNode (node=node@entry=0x25dbc48) at execProcnode.c:424
#8  0x00000000006116a6 in ExecutePlan (execute_once=<optimized out>, dest=0x262e498, direction=<optimized out>, numberTuples=0, sendTuples=<optimized out>, 
    operation=CMD_UPDATE, use_parallel_mode=<optimized out>, planstate=0x25dbc48, estate=0x25db9e0) at execMain.c:1651
#9  standard_ExecutorRun (queryDesc=0x2666100, direction=<optimized out>, count=0, execute_once=<optimized out>) at execMain.c:360
#10 0x0000000000746822 in ProcessQuery (plan=<optimized out>, sourceText=0x25ac4e0 "UPDATE pgbench_tellers SET tbalance = tbalance + 3494 WHERE tid = 76;", 
    params=0x0, queryEnv=0x0, dest=0x262e498, completionTag=0x7ffd14fcf010 "") at pquery.c:162
#11 0x0000000000746a93 in PortalRunMulti (portal=portal@entry=0x25462d0, isTopLevel=isTopLevel@entry=1 '\001', setHoldSnapshot=setHoldSnapshot@entry=0 '\000', 
    dest=dest@entry=0x262e498, altdest=altdest@entry=0x262e498, completionTag=completionTag@entry=0x7ffd14fcf010 "") at pquery.c:1287
#12 0x00000000007476a2 in PortalRun (portal=0x25462d0, count=9223372036854775807, isTopLevel=<optimized out>, run_once=<optimized out>, dest=0x262e498, 
    altdest=0x262e498, completionTag=0x7ffd14fcf010 "") at pquery.c:800
#13 0x000000000074361b in exec_simple_query (query_string=0x2324 <error: Cannot access memory at address 0x2324>) at postgres.c:1105
#14 0x0000000000745254 in PostgresMain (argc=1, argv=0x25ac4e0, dbname=0x2555888 "pgbench", username=0x2528260 "jcasanov") at postgres.c:4075
#15 0x00000000004780f3 in BackendRun (port=0x254dd70) at postmaster.c:4317
#16 BackendStartup (port=0x254dd70) at postmaster.c:3989
#17 ServerLoop () at postmaster.c:1729
#18 0x00000000006d33d0 in PostmasterMain (argc=argc@entry=3, argv=argv@entry=0x25260c0) at postmaster.c:1337
#19 0x0000000000478f4d in main (argc=3, argv=0x25260c0) at main.c:228


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Patch: Write Amplification Reduction Method (WARM)
  2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 03:12 ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 03:43   ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-05 13:36     ` Re: Patch: Write Amplification Reduction Method (WARM) Robert Haas <[email protected]>
  2017-04-05 18:32       ` Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
  2017-04-14 15:51         ` Re: Patch: Write Amplification Reduction Method (WARM) Jaime Casanova <[email protected]>
@ 2017-04-18 08:25           ` Pavan Deolasee <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Pavan Deolasee @ 2017-04-18 08:25 UTC (permalink / raw)
  To: Jaime Casanova <[email protected]>; +Cc: Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alvaro Herrera <[email protected]>; Bruce Momjian <[email protected]>; Haribabu Kommi <[email protected]>; Tomas Vondra <[email protected]>; pgsql-hackers

On Fri, Apr 14, 2017 at 9:21 PM, Jaime Casanova <jaime.casanova@2ndquadrant.
com> wrote:

>
>
> Hi Pavan,
>
> I run a test on current warm patchset, i used pgbench with a scale of
> 20 and a fillfactor of 90 and then start the pgbench run with 6
> clients in parallel i also run sqlsmith on it.
>
> And i got a core dump after sometime of those things running.
>
> The assertion that fails is:
>
> """
> LOG:  statement: UPDATE pgbench_tellers SET tbalance = tbalance + 3519
> WHERE tid = 34;
> TRAP: FailedAssertion("!(((bool) (((const void*)(&tup->t_ctid) !=
> ((void *)0)) && (((&tup->t_ctid)->ip_posid & ((((uint16) 1) << 13) -
> 1)) != 0))))", File: "../../../../src/include/access/htup_details.h",
> Line: 659)
> """
>

Hi Jaime,

Thanks for doing the tests and reporting the problem. Per our chat, the
assertion failure occurs only after a crash recovery. I traced i down to
the point where we were failing to set the root line pointer correctly
during crash recovery. In fact, we were setting it, but after the local
changes are copied to the on-disk image, thus failing to make to the
storage.

Can you please test with the attached patch and confirm it works? I was
able to reproduce the exact same assertion on my end and the patch seems to
fix it. But an additional check won't harm.

I'll include the fix in the next set of patches.

Thanks,
Pavan

-- 
 Pavan Deolasee                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [application/octet-stream] warm_crash_recovery_fix.patch (1.5K, ../../CABOikdNdvMYvwiXaZjy6RJzMA+ZZ-Yh+yLvbbKA-8WgoKUt=mA@mail.gmail.com/3-warm_crash_recovery_fix.patch)
  download | inline diff:
diff --git b/src/backend/access/heap/heapam.c a/src/backend/access/heap/heapam.c
index d309dd3..3dd6910 100644
--- b/src/backend/access/heap/heapam.c
+++ a/src/backend/access/heap/heapam.c
@@ -9490,16 +9490,16 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 		if (warm_update)
 			HeapTupleHeaderSetWarmUpdated(htup);
 
-		offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
-		if (offnum == InvalidOffsetNumber)
-			elog(PANIC, "failed to add tuple");
-
 		/*
 		 * Make sure the tuple is marked as the latest and root offset
 		 * information is restored.
 		 */
 		HeapTupleHeaderSetHeapLatest(htup, xlrec->root_offnum);
 
+		offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
+		if (offnum == InvalidOffsetNumber)
+			elog(PANIC, "failed to add tuple");
+
 		if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED)
 			PageClearAllVisible(page);
 
diff --git b/src/include/access/htup_details.h a/src/include/access/htup_details.h
index ff22113..87b77eb 100644
--- b/src/include/access/htup_details.h
+++ a/src/include/access/htup_details.h
@@ -557,6 +557,7 @@ HeapTupleHeaderSetHeapLatest(HeapTupleHeader tup, Offset offnum)
 	Assert(OffsetNumberIsValid(offnum));
 
 	tup->t_infomask2 |= HEAP_LATEST_TUPLE;
+	Assert(OffsetNumberIsValid(offnum));
 	ItemPointerSetOffsetNumber(&tup->t_ctid, offnum);
 }
 
@@ -630,7 +631,7 @@ static inline void
 HeapTupleHeaderSetNextTid(HeapTupleHeader tup, ItemPointer tid)
 {
 	ItemPointerCopy(tid, &(tup->t_ctid));
-
+	Assert(ItemPointerIsValid(tid));
 	HeapTupleHeaderClearHeapLatest(tup);
 }
 


^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/12] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--45Z9DzgjV8m4Oswq
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/12] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--45Z9DzgjV8m4Oswq
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 5/6] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 14154d1ce0..4b11e40544 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -12,7 +12,7 @@ use Test::More;
 use Config;
 
 my $node = PostgreSQL::Test::Cluster->new('primary');
-$node->init(allows_streaming => 1);
+$node->init(allows_streaming => 0);
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.1


--pAwQNkOnpTn9IO2O
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0006-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v8 2/9] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index a26e99500b..2e7e3db639 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -14,7 +14,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--P+33d92oIH25kiaB
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v8-0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v9 2/9] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index a26e99500b..2e7e3db639 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -14,7 +14,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--neYutvxvOLaeuPCA
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v9-0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--XsQoSWH+UP9D9v3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v7 2/9] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index a26e99500b..2e7e3db639 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -14,7 +14,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--JYK4vJDZwFMowpUq
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v7-0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--0qVF/w3MHQqLSynd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--f0KYrhQ4vYSV2aJu
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.27.0


----Next_Part(Mon_Mar__8_17_32_42_2021_572)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0002-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal
@ 2021-03-08 06:32 Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Kyotaro Horiguchi @ 2021-03-08 06:32 UTC (permalink / raw)

The test doesn't need that feature and pg_current_xact_id() is better
exercised by turning off the feature.

Copied from: https://www.postgresql.org/message-id/20210308.173242.463790587797836129.horikyota.ntt%40gmail.com
---
 src/test/recovery/t/011_crash_recovery.pl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/recovery/t/011_crash_recovery.pl b/src/test/recovery/t/011_crash_recovery.pl
index 10cd98f70a..690655dda2 100644
--- a/src/test/recovery/t/011_crash_recovery.pl
+++ b/src/test/recovery/t/011_crash_recovery.pl
@@ -11,7 +11,7 @@ use Config;
 plan tests => 3;
 
 my $node = get_new_node('primary');
-$node->init(allows_streaming => 1);
+$node->init();
 $node->start;
 
 my ($stdin, $stdout, $stderr) = ('', '', '');
-- 
2.17.0


--jozmn01XJZjDjM3N
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0003-Make-sure-published-XIDs-are-persistent.patch"



^ permalink  raw  reply  [nested|flat] 151+ messages in thread

* Re: Making background psql nicer to use in tap tests
@ 2023-01-30 20:06 Tom Lane <[email protected]>
  0 siblings, 0 replies; 151+ messages in thread

From: Tom Lane @ 2023-01-30 20:06 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: pgsql-hackers; Melanie Plageman <[email protected]>

Andres Freund <[email protected]> writes:
> It's annoyingly hard to wait for the result of a query in a generic way with
> background_psql(), and more generally for psql. background_psql() uses -XAtq,
> which means that we'll not get "status" output (like "BEGIN" or "(1 row)"),
> and that queries not returning anything are completely invisible.

Yeah, the empty-query-result problem was giving me fits recently.
+1 for wrapping this into something more convenient to use.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 151+ messages in thread


end of thread, other threads:[~2023-01-30 20:06 UTC | newest]

Thread overview: 151+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-04-05 02:21 Re: Patch: Write Amplification Reduction Method (WARM) Pavan Deolasee <[email protected]>
2017-04-05 03:12 ` Robert Haas <[email protected]>
2017-04-05 03:43   ` Pavan Deolasee <[email protected]>
2017-04-05 13:36     ` Robert Haas <[email protected]>
2017-04-05 18:27       ` Andres Freund <[email protected]>
2017-04-05 18:50         ` Peter Geoghegan <[email protected]>
2017-04-07 04:28           ` Pavan Deolasee <[email protected]>
2017-04-08 18:06         ` Pavan Deolasee <[email protected]>
2017-04-10 23:19           ` Bruce Momjian <[email protected]>
2017-04-10 23:34           ` Andres Freund <[email protected]>
2017-04-12 00:50             ` Bruce Momjian <[email protected]>
2017-04-11 13:40           ` Robert Haas <[email protected]>
2017-04-11 17:20             ` Pavan Deolasee <[email protected]>
2017-04-12 03:53               ` Amit Kapila <[email protected]>
2017-04-12 04:23                 ` Pavan Deolasee <[email protected]>
2017-04-12 17:12               ` Robert Haas <[email protected]>
2017-04-12 20:34                 ` Peter Geoghegan <[email protected]>
2017-04-13 05:14                   ` Pavan Deolasee <[email protected]>
2017-04-13 05:42                 ` Pavan Deolasee <[email protected]>
2017-04-05 18:32       ` Pavan Deolasee <[email protected]>
2017-04-05 19:36         ` Robert Haas <[email protected]>
2017-04-06 01:37           ` Pavan Deolasee <[email protected]>
2017-04-14 15:51         ` Jaime Casanova <[email protected]>
2017-04-18 08:25           ` Pavan Deolasee <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/12] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/12] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 5/6] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v8 2/9] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v9 2/9] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v7 2/9] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 1/8] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH v1 1/2] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2021-03-08 06:32 [PATCH 02/10] Run 011_crash_recovery.pl with wal_level=minimal Kyotaro Horiguchi <[email protected]>
2023-01-30 20:06 Re: Making background psql nicer to use in tap tests Tom Lane <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox