Virtual tuple slots versus TOAST: big problem - Mailing list pgsql-hackers

From Tom Lane
Subject Virtual tuple slots versus TOAST: big problem
Date
Msg-id 20874.1132420925@sss.pgh.pa.us
Whole thread Raw
Responses Re: Virtual tuple slots versus TOAST: big problem
List pgsql-hackers
I looked into this 8.1 bug reported by Alexey Beschiokov:
http://archives.postgresql.org/pgsql-bugs/2005-11/msg00192.php
The executive summary is: it looks like a kluge solution isn't hard,
but solving it in a more reasonable fashion is going to require some
significant API changes inside the backend :-(

The problem is that ExecInsert() does ExecMaterializeSlot() to get a
physical tuple from the virtual tuple slot it's initially handed.
After this step, the TupleTableSlot contains that same physical tuple.
ExecInsert then passes the bare tuple to some subroutines and the Slot
to other subroutines.  In the particular case shown by Alexey, the
sequence is:ExecConstraints is called with the slotheap_insert (and thence tuptoaster.c) is called with the bare
tupleExecInsertIndexTuplesis called with the slot
 

The problem is that ExecConstraints accesses some fields of the slot,
causing partial extraction of tuple fields and setup of the tts_values[]
array within the slot.  After that, the tuptoaster proceeds to smash
down the tuple to a size it likes, which it does by scribbling on the
HeapTuple structure it's handed.  That makes the TupleTableSlot
structure inconsistent --- it has tts_values fields pointing at memory
that's no longer part of the tuple actually stored in the slot.  When
ExecInsertIndexTuples then tries to extract more fields from the slot,
a crash is not unlikely.

(It's annoying that we didn't find this during beta, but a failure
requires ExecInsertIndexTuples to try to access fields to the right of
the last one fetched by ExecConstraints, and it only matters if the
tuple actually got toasted in between, so it is a bit of a corner case.)

ExecUpdate has the same bug.  I don't think there are any other places,
because ExecMaterializeSlot isn't used elsewhere ATM.  The problem did
not exist before 8.1 because TupleTableSlots didn't contain extra info
beyond the bare tuple, so tuptoaster could hack that without rendering
the Slot inconsistent.

I think that a kluge fix is possible by setting tts_nvalid to zero after
invoking heap_insert or heap_update, so that ExecInsertIndexTuples will
be forced to recompute tts_values instead of reusing the previous data.
This is probably the right thing to do in the 8.1 branch, but it's
incredibly ugly and we need to fix it better going forward.

"A better fix" seems to require passing the TupleTableSlot, not just the
bare tuple, down to the toaster --- else there is no way for the toaster
to update the data structure that it's accidentally invalidating.  This
seems like it might be a good idea anyway on performance grounds: we
could save one cycle of heap_deform_tuple and heap_formtuple in the case
where toasting is needed, if the toaster is invoked on the tuple while
it's still in virtual-slot format.  The problem is that given the
current structure, that means changing the APIs of heap_insert and
heap_update, or else making near-duplicate versions that take a
TupleTableSlot instead of a bare tuple.  Neither of these things seem
real attractive.  If we wanted to avoid forming a physical tuple until
the last moment we'd also need to change the APIs associated with
triggers, ie make them work on Slots not tuples.  This'd be even more
invasive.  It would likely be cleaner and more efficient in the long
run, but there's a lot of code to touch, and breaking user-defined
triggers doesn't seem palatable at all.

Any thoughts on the best way to proceed?
        regards, tom lane


pgsql-hackers by date:

Previous
From: Martijn van Oosterhout
Date:
Subject: Returning multiple result sets
Next
From: Tom Lane
Date:
Subject: Re: Returning multiple result sets