Thread: [HACKERS] Faster methods for getting SPI results

[HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
I've been looking at the performance of SPI calls within plpython. 
There's a roughly 1.5x difference from equivalent python code just in 
pulling data out of the SPI tuplestore. Some of that is due to an 
inefficiency in how plpython is creating result dictionaries, but fixing 
that is ultimately a dead-end: if you're dealing with a lot of results 
in python, you want a tuple of arrays, not an array of tuples.

While we could just brute-force a tuple of arrays by plowing through the 
SPI tuplestore (this is what pl/r does), there's still a lot of extra 
work involved in doing that. AFAICT there's at least 2 copies that 
happen between the executor producing a tuple and it making it into the 
tuplestore, plus the tuplestore is going to consume a potentially very 
large amount of memory for a very short period of time, before all the 
data gets duplicated (again) into python objects.

It would be a lot more efficient if we could just grab datums from the 
executor and make a single copy into plpython (or R), letting the PL 
deal with all the memory management overhead.

I briefly looked at using SPI cursors to do just that, but that looks 
even worse: every fetch is executed in a subtransaction, and every fetch 
creates an entire tuplestore even if it's just going to return a single 
value. (But hey, we never claimed cursors were fast...)

Is there any way to avoid all of this? I'm guessing one issue might be 
that we don't want to call an external interpreter while potentially 
holding page pins, but even then couldn't we just copy a single tuple at 
a time and save a huge amount of palloc overhead?
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
On 12/20/16 10:14 PM, Jim Nasby wrote:
> It would be a lot more efficient if we could just grab datums from the
> executor and make a single copy into plpython (or R), letting the PL
> deal with all the memory management overhead.
>
> I briefly looked at using SPI cursors to do just that, but that looks
> even worse: every fetch is executed in a subtransaction, and every fetch
> creates an entire tuplestore even if it's just going to return a single
> value. (But hey, we never claimed cursors were fast...)
>
> Is there any way to avoid all of this? I'm guessing one issue might be
> that we don't want to call an external interpreter while potentially
> holding page pins, but even then couldn't we just copy a single tuple at
> a time and save a huge amount of palloc overhead?

AFAICT that's exactly how DestRemote works: it grabs a raw slot from the 
executor, makes sure it's fully expanded, and sends it on it's way via 
pq_send*(). So presumably the same could be done for SPI, by creating a 
new CommandDest (ISTM none of the existing ones would do what we want).

I'm not sure what the API for this should look like. One possibility is 
to have SPI_execute and friends accept a flag that indicates not to 
build a tupletable. I don't think a query needs to be read-only to allow 
for no tuplestore, so overloading read_only seems like a bad idea.

Another option is to treat this as a "lightweight cursor" that only 
allows forward fetches. One nice thing about that option is it leaves 
open the possibility of using a small tuplestore for each "fetch", 
without all the overhead that a full blown cursor has. This assumes 
there are some use cases where you want to operate on relatively small 
sets of tuples at a time, but you don't need to materialize the whole 
thing in one shot.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
On 12/21/16 8:21 AM, Jim Nasby wrote:
> On 12/20/16 10:14 PM, Jim Nasby wrote:
>> It would be a lot more efficient if we could just grab datums from the
>> executor and make a single copy into plpython (or R), letting the PL
>> deal with all the memory management overhead.
>>
>> I briefly looked at using SPI cursors to do just that, but that looks
>> even worse: every fetch is executed in a subtransaction, and every fetch
>> creates an entire tuplestore even if it's just going to return a single
>> value. (But hey, we never claimed cursors were fast...)
>>
>> Is there any way to avoid all of this? I'm guessing one issue might be
>> that we don't want to call an external interpreter while potentially
>> holding page pins, but even then couldn't we just copy a single tuple at
>> a time and save a huge amount of palloc overhead?
>
> AFAICT that's exactly how DestRemote works: it grabs a raw slot from the
> executor, makes sure it's fully expanded, and sends it on it's way via
> pq_send*(). So presumably the same could be done for SPI, by creating a
> new CommandDest (ISTM none of the existing ones would do what we want).
>
> I'm not sure what the API for this should look like. One possibility is
> to have SPI_execute and friends accept a flag that indicates not to
> build a tupletable. I don't think a query needs to be read-only to allow
> for no tuplestore, so overloading read_only seems like a bad idea.
>
> Another option is to treat this as a "lightweight cursor" that only
> allows forward fetches. One nice thing about that option is it leaves
> open the possibility of using a small tuplestore for each "fetch",
> without all the overhead that a full blown cursor has. This assumes
> there are some use cases where you want to operate on relatively small
> sets of tuples at a time, but you don't need to materialize the whole
> thing in one shot.

I've looked at this some more, and ITSM that the only way to do this 
without some major surgery is to create a new type of Destination 
specifically for SPI that allows for the execution of an arbitrary C 
function for each tuple to be sent. AFAICT this should be fairly safe, 
since DestRemote can potentially block while sending a tuple and also 
runs output functions (which presumably could themselves generate errors).

_SPI_execute_plan() would need to accept an arbitrary DestReceiver 
struct, and use that (if specified) instead of creating it's own.

Once that's done, my plan is to allow plpy to use this functionality 
with a receiver function that adds tuple fields to corresponding python 
lists. This should result in significantly less overhead than going 
through a tuplestore when dealing with a large number of rows.

Before I go code this up, I'd like to know if there's some fatal flaw in 
this, or if there's an easier way to hack this up just to test my 
performance theory.

Suggestions?
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results

From
Craig Ringer
Date:
On 28 December 2016 at 09:58, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

> I've looked at this some more, and ITSM that the only way to do this without
> some major surgery is to create a new type of Destination specifically for
> SPI that allows for the execution of an arbitrary C function for each tuple
> to be sent.

That sounds a lot more sensible than the prior proposals. Callback driven.

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



Re: [HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
On 12/27/16 9:10 PM, Craig Ringer wrote:
> On 28 December 2016 at 09:58, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
>
>> I've looked at this some more, and ITSM that the only way to do this without
>> some major surgery is to create a new type of Destination specifically for
>> SPI that allows for the execution of an arbitrary C function for each tuple
>> to be sent.
>
> That sounds a lot more sensible than the prior proposals. Callback driven.

Are there other places this would be useful? I'm reluctant to write all 
of this just to discover it doesn't help performance at all, but if it's 
useful on it's own I can just submit it as a stand-alone patch.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results

From
Craig Ringer
Date:
On 28 December 2016 at 12:32, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
> On 12/27/16 9:10 PM, Craig Ringer wrote:
>>
>> On 28 December 2016 at 09:58, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
>>
>>> I've looked at this some more, and ITSM that the only way to do this
>>> without
>>> some major surgery is to create a new type of Destination specifically
>>> for
>>> SPI that allows for the execution of an arbitrary C function for each
>>> tuple
>>> to be sent.
>>
>>
>> That sounds a lot more sensible than the prior proposals. Callback driven.
>
>
> Are there other places this would be useful? I'm reluctant to write all of
> this just to discover it doesn't help performance at all, but if it's useful
> on it's own I can just submit it as a stand-alone patch.

I don't have a use for it personally. In BDR and pglogical anything
that does work with nontrivial numbers of tuples uses lower level
scans anyway.

I expect anything that uses the SPI to run arbitrary user queries
could have a use for something like this though. Any PL, for one.

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



Re: [HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
On 12/27/16 9:10 PM, Craig Ringer wrote:
> On 28 December 2016 at 09:58, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
>
>> I've looked at this some more, and ITSM that the only way to do this without
>> some major surgery is to create a new type of Destination specifically for
>> SPI that allows for the execution of an arbitrary C function for each tuple
>> to be sent.
>
> That sounds a lot more sensible than the prior proposals. Callback driven.

Here's what I've got right now. I haven't bothered with 
SPI_execute_callback() yet, and there's some missing sanity checks.

I'm not sure if the changes to CreateDestReceiver() are warranted or 
necessary, though it would at least give you sane defaults. My 
incomplete code that would make use of this currently does
CallbackState    callback;
memcpy(callback.pub, CreateDestReceiver(DestSPICallback), 
sizeof(DestReceiver));callback.pub.receiveSlot = PLy_CSreceive;callback.pub.rStartup = PLy_CSStartup;
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
On 12/28/16 3:14 AM, Craig Ringer wrote:
> On 28 December 2016 at 12:32, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
>> On 12/27/16 9:10 PM, Craig Ringer wrote:
>>>
>>> On 28 December 2016 at 09:58, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
>>>
>>>> I've looked at this some more, and ITSM that the only way to do this
>>>> without
>>>> some major surgery is to create a new type of Destination specifically
>>>> for
>>>> SPI that allows for the execution of an arbitrary C function for each
>>>> tuple
>>>> to be sent.
>>>
>>>
>>> That sounds a lot more sensible than the prior proposals. Callback driven.
>>
>>
>> Are there other places this would be useful? I'm reluctant to write all of
>> this just to discover it doesn't help performance at all, but if it's useful
>> on it's own I can just submit it as a stand-alone patch.
>
> I don't have a use for it personally. In BDR and pglogical anything
> that does work with nontrivial numbers of tuples uses lower level
> scans anyway.
>
> I expect anything that uses the SPI to run arbitrary user queries
> could have a use for something like this though. Any PL, for one.

Just a quick update on this: I've gotten this working well enough in 
plpython to do some performance testing. This patch does change python 
results from being a list of dicts to a dict of lists, but I suspect the 
vast majority of the speed improvement is from not creating a tuplestore.

The attached sample (from OS X /usr/bin/sample) is interesting. The 
highlight is:

> !       3398 SPI_execute_callback  (in postgres) + 163  [0x110125793]
> !         3394 _SPI_execute_plan  (in postgres) + 1262  [0x1101253fe]
> !         : 2043 standard_ExecutorRun  (in postgres) + 288  [0x1100f9a40]
> !         : | 1990 ExecProcNode  (in postgres) + 250  [0x1100fd62a]

The top line is the entry into SPI from plpython. The bottom line is 
generate_series into a tuplestore and then reading from that tuplestore. 
Almost all the time being spent in standard_ExecutorRun is in 
PLy_CSreceive, which is appending values to a set of python lists as 
it's getting tuples.

The other noteworthy item in the sample is this:

> 535 list_dealloc  (in Python) + 116,103,...  [0x11982b1b4,0x11982b1a7,...]

that's how long it's taking python to free the 3 lists (each with 
9999999 python int objects).

In short (and at best*), this makes plpython just as fast at processing 
results as SELECT count(SELECT s, s, s FROM generate_series()).

The * on that is there's something odd going on where plpython starts 
out really fast at this, then gets 100% slower. I've reached out to some 
python folks about that. Even so, the overall results from a quick test 
on my laptop are (IMHO) impressive:

             Old Code        New Code    Improvement
Pure SQL    2 sec          2 sec
plpython    12.7-14 sec    4-10 sec     ~1.3-3x
plpython - SQL 10.7-12 sec 2-8 sec      ~1.3-6x

Pure SQL is how long an equivalent query takes to run with just SQL.
plpython - SQL is simply the raw python times minus the pure SQL time.

I suspect other PL languages that have fairly fast object alloc and 
dealloc would see a similar benefit.

BTW, the patch currently breaks on nested calls to plpython, but I don't 
think that should change the performance much.

The test function:
> CREATE OR REPLACE FUNCTION test_series(
>     iter int
> ) RETURNS int LANGUAGE plpythonu AS $body$
> d = plpy.execute('SELECT s AS some_table_id, s AS some_field_name, s AS some_other_field_name FROM
generate_series(1,{})s'.format(iter) )
 
> return len(d['some_table_id'])
> $body$;
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)

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

Attachment

Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 1/5/17 9:50 PM, Jim Nasby wrote:
> The * on that is there's something odd going on where plpython starts
> out really fast at this, then gets 100% slower. I've reached out to some
> python folks about that. Even so, the overall results from a quick test
> on my laptop are (IMHO) impressive:
>
>             Old Code        New Code    Improvement
> Pure SQL    2 sec          2 sec
> plpython    12.7-14 sec    4-10 sec     ~1.3-3x
> plpython - SQL 10.7-12 sec 2-8 sec      ~1.3-6x
>
> Pure SQL is how long an equivalent query takes to run with just SQL.
> plpython - SQL is simply the raw python times minus the pure SQL time.

I finally got all the kinks worked out and did some testing with python 
3. Performance for my test [1] improved ~460% when returning a dict of 
lists (as opposed to the current list of dicts). Based on previous 
testing, I expect that using this method to return a list of dicts will 
be about 8% slower. The inconsistency in results on 2.7 has to do with 
how python 2 handles ints.

Someone who's familiar with pl/perl should take a look at this and see 
if it would apply there. I've attached the SPI portion of this patch.

I think the last step here is to figure out how to support switching 
between the current behavior and the "columnar" behavior of a dict of 
lists. I believe the best way to do that is to add two optional 
arguments to the execution functions: container=[] and members={}, and 
then copy those to produce the output objects. That means you can get 
the new behavior by doing something like:

plpy.execute('...', container={}, members=[])

Or, more interesting, you could do:

plpy.execute('...', container=Pandas.DataFrame, members=Pandas.Series)

since that's what a lot of people are going to want anyway.

In the future we could also add a GUC to change the default behavior.

Any concerns with that approach?

1:
> d = plpy.execute('SELECT s AS some_table_id, s AS some_field_name, s AS some_other_field_name FROM
generate_series(1,{})s'.format(iter) )
 
> return len(d['some_table_id'])
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)

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

Attachment

Re: [HACKERS] Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 24 January 2017 at 11:23, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

> I finally got all the kinks worked out and did some testing with python 3.
> Performance for my test [1] improved ~460% when returning a dict of lists
> (as opposed to the current list of dicts). Based on previous testing, I
> expect that using this method to return a list of dicts will be about 8%
> slower. The inconsistency in results on 2.7 has to do with how python 2
> handles ints.

Impressive results.

> I think the last step here is to figure out how to support switching between
> the current behavior and the "columnar" behavior of a dict of lists.

That sounds like it'd be much better approached as a separate, later patch.

If I understand you correctly, you propose to return the resultset

a   b
1  10
2  20

which is currently returned as

[ {"a":1, "b":10}, {"a":2, "b":20} ]

instead as

{ "a": [1, 2], "b": [10, 20] }

?

If so I see that as a lot more of a niche thing. I can see why it'd be
useful and would help performance, but it seems much more disruptive.
It requires users to discover it exists, actively adopt a different
style of ingesting data, etc. For a 10%-ish gain in a PL.

I strongly suggest making this design effort a separate thread, and
focusing on the SPI improvements that give "free" no-user-action
performance boosts here.


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



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 1/23/17 10:36 PM, Craig Ringer wrote:
> which is currently returned as
>
> [ {"a":1, "b":10}, {"a":2, "b":20} ]
>
> instead as
>
> { "a": [1, 2], "b": [10, 20] }

Correct.

> If so I see that as a lot more of a niche thing. I can see why it'd be
> useful and would help performance, but it seems much more disruptive.
> It requires users to discover it exists, actively adopt a different
> style of ingesting data, etc. For a 10%-ish gain in a PL.

In data science, what we're doing now is actually the niche. All real 
analytics happens with something like a Pandas DataFrame, which is 
organized as a dict of lists.

This isn't just idle nomenclature either: organizing results in what 
amounts to a column store provides a significant speed improvement for 
most analytics, because you're working on an array of contiguous memory 
(at least, when you're using more advanced types like DataFrames and 
Series).

> I strongly suggest making this design effort a separate thread, and
> focusing on the SPI improvements that give "free" no-user-action
> performance boosts here.

Fair enough. I posted the SPI portion of that yesterday. That should be 
useful for pl/R and possibly pl/perl. pl/tcl could make use of it, but 
it would end up executing arbitrary tcl code in the middle of portal 
execution, which doesn't strike me as a great idea. Unfortunately, I 
don't think plpgsql could make much use of this for similar reasons.

I'll post a plpython patch that doesn't add the output format control.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 1/23/17 9:23 PM, Jim Nasby wrote:
> I think the last step here is to figure out how to support switching
> between the current behavior and the "columnar" behavior of a dict of lists.

I've thought more about this... instead of trying to switch from the 
current situation of 1 choice of how results are return to 2 choices, I 
think it'd be better to just expose the API that the new Destination 
type provides to SPI. Specifically, execute a python function during 
Portal startup, and a different function for receiving tuples. There'd 
be an optional 3rd function for Portal shutdown.

The startup function would be handed details of the resultset it was 
about to receive, as a list that contained python tuples with the 
results of SPI_fname, _gettype, _gettypeid. This function would return a 
callback version number and a python object that would be kept in the 
DestReceiver.

The receiver function would get the object created by the startup 
function, as well as a python tuple of the TupleTableSlot that had gone 
through type conversion. It would need to add the value to the object 
from the startup function. It would return true or false, just like a 
Portal receiver function does.

The shutdown function would receive the object that's been passed 
around. It would be able to do any post-processing. Whatever it returned 
is what would be handed back to python as the results of the query.

The version number returned by the startup function allows for future 
improvements to this facility. One idea there is allowing the startup 
function to control how Datums get mapped into python objects.

In order to support all of this without breaking backwards compatibility 
or forking a new API, plpy.execute would accept a kwdict, to avoid 
conflicting with the arbitrary number of arguments that can currently be 
accepted. We'd look in the kwdict for a key called "portal_functions" 
pointing at a 2 or 3 element tuple of the startup, receive and shutdown 
functions. plpy would pre-define a tuple that provides the current 
behavior, and that's what would be used by default. In the future, we 
might add a way to control the default.

Comments?
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 1/24/17 10:43 PM, Jim Nasby wrote:
>
>> I strongly suggest making this design effort a separate thread, and
>> focusing on the SPI improvements that give "free" no-user-action
>> performance boosts here.
>
> Fair enough. I posted the SPI portion of that yesterday. That should be
> useful for pl/R and possibly pl/perl. pl/tcl could make use of it, but
> it would end up executing arbitrary tcl code in the middle of portal
> execution, which doesn't strike me as a great idea. Unfortunately, I
> don't think plpgsql could make much use of this for similar reasons.
>
> I'll post a plpython patch that doesn't add the output format control.

I've attached the results of that. Unfortunately the speed improvement 
is only 27% at this point (with 9999999 tuples). Presumably that's 
because it's constructing a brand new dictionary from scratch for each 
tuple.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)

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

Attachment

Re: [HACKERS] Faster methods for getting SPI results

From
Peter Eisentraut
Date:
On 12/20/16 23:14, Jim Nasby wrote:
> I've been looking at the performance of SPI calls within plpython. 
> There's a roughly 1.5x difference from equivalent python code just in 
> pulling data out of the SPI tuplestore. Some of that is due to an 
> inefficiency in how plpython is creating result dictionaries, but fixing 
> that is ultimately a dead-end: if you're dealing with a lot of results 
> in python, you want a tuple of arrays, not an array of tuples.

There is nothing that requires us to materialize the results into an
actual list of actual rows.  We could wrap the SPI_tuptable into a
Python object and implement __getitem__ or __iter__ to emulate sequence
or mapping access.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: [HACKERS] Faster methods for getting SPI results

From
Jon Nelson
Date:


On Thu, Mar 2, 2017 at 10:03 AM, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 12/20/16 23:14, Jim Nasby wrote:
> I've been looking at the performance of SPI calls within plpython.
> There's a roughly 1.5x difference from equivalent python code just in
> pulling data out of the SPI tuplestore. Some of that is due to an
> inefficiency in how plpython is creating result dictionaries, but fixing
> that is ultimately a dead-end: if you're dealing with a lot of results
> in python, you want a tuple of arrays, not an array of tuples.

There is nothing that requires us to materialize the results into an
actual list of actual rows.  We could wrap the SPI_tuptable into a
Python object and implement __getitem__ or __iter__ to emulate sequence
or mapping access.

Python objects have a small (but non-zero) overhead in terms of both memory and speed. A built-in dictionary is probably one of the least-expensive (memory/cpu) choices, although how the dictionary is constructed also impacts performance.  Another choice is a tuple.

Avoiding Py_BuildValue(...) in exchange for a bit more complexity (via PyTuple_New(..) and PyTuple_SetItem(...)) is also a nice performance win in my experience.

--
Jon

Re: [HACKERS] Faster methods for getting SPI results

From
Jim Nasby
Date:
On 3/2/17 8:03 AM, Peter Eisentraut wrote:
> On 12/20/16 23:14, Jim Nasby wrote:
>> I've been looking at the performance of SPI calls within plpython.
>> There's a roughly 1.5x difference from equivalent python code just in
>> pulling data out of the SPI tuplestore. Some of that is due to an
>> inefficiency in how plpython is creating result dictionaries, but fixing
>> that is ultimately a dead-end: if you're dealing with a lot of results
>> in python, you want a tuple of arrays, not an array of tuples.
>
> There is nothing that requires us to materialize the results into an
> actual list of actual rows.  We could wrap the SPI_tuptable into a
> Python object and implement __getitem__ or __iter__ to emulate sequence
> or mapping access.

Would it be possible to have that just pull tuples directly from the 
executor? The overhead of populating the tuplestore just to drain it 
again can become quite significant, and AFAICT it's completely unnecessary.

Unfortunately, I think adding support for that would be even more 
invasive, which is why I haven't attempted it. On the flip side, I 
believe that kind of an interface would be usable by plpgsql, whereas 
the DestReceiver approach is not (AFAICT).
-- 
Jim Nasby, Chief Data Architect, OpenSCG



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 2/28/17 9:42 PM, Jim Nasby wrote:
>>
>> I'll post a plpython patch that doesn't add the output format control.
>
> I've attached the results of that. Unfortunately the speed improvement
> is only 27% at this point (with 9999999 tuples). Presumably that's
> because it's constructing a brand new dictionary from scratch for each
> tuple.

I found a couple bugs. New patches attached.
-- 
Jim Nasby, Chief Data Architect, OpenSCG

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

Attachment

Re: [HACKERS] Faster methods for getting SPI results

From
Peter Eisentraut
Date:
On 3/5/17 16:07, Jim Nasby wrote:
>> There is nothing that requires us to materialize the results into an
>> actual list of actual rows.  We could wrap the SPI_tuptable into a
>> Python object and implement __getitem__ or __iter__ to emulate sequence
>> or mapping access.
> Would it be possible to have that just pull tuples directly from the 
> executor? The overhead of populating the tuplestore just to drain it 
> again can become quite significant, and AFAICT it's completely unnecessary.

I think there are many options, depending on what you want.  If you want
to materialize the result, then you have to materialize it somewhere,
and then make a Python object around that.  Or you could make an
iterator interface that just reads a tuple at a time.  Or maybe there
are other options.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 6 March 2017 at 05:09, Jim Nasby <jim.nasby@openscg.com> wrote:
> On 2/28/17 9:42 PM, Jim Nasby wrote:
>>>
>>>
>>> I'll post a plpython patch that doesn't add the output format control.
>>
>>
>> I've attached the results of that. Unfortunately the speed improvement
>> is only 27% at this point (with 9999999 tuples). Presumably that's
>> because it's constructing a brand new dictionary from scratch for each
>> tuple.

Taking a look at this now.

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



Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 5 April 2017 at 08:00, Craig Ringer <craig@2ndquadrant.com> wrote:

> Taking a look at this now.

Rebased to current master with conflicts and whitespace errors fixed.
Review pending.

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

Attachment

Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 5 April 2017 at 08:23, Craig Ringer <craig@2ndquadrant.com> wrote:
> On 5 April 2017 at 08:00, Craig Ringer <craig@2ndquadrant.com> wrote:
>
>> Taking a look at this now.
>
> Rebased to current master with conflicts and whitespace errors fixed.
> Review pending.

This patch fails to update the documentation at all.

https://www.postgresql.org/docs/devel/static/spi.html



The patch crashes in initdb with --enable-cassert builds:

performing post-bootstrap initialization ... TRAP:
FailedAssertion("!(myState->pub.mydest == DestSQLFunction)", File:
"functions.c", Line: 800)
sh: line 1: 30777 Aborted                 (core dumped)
"/home/craig/projects/2Q/postgres/tmp_install/home/craig/pg/10/bin/postgres"
--single -F -O -j -c search_path=pg_catalog -c exit_on_error=true
template1 > /dev/null
child process exited with exit code 134

Backtrace attached.




Details on patch 1:

missing newline

+}
+int
+SPI_execute_callback(const char *src, bool read_only, long tcount,


+/* Execute a previously prepared plan with a callback Destination */


caps "Destination"



+                // XXX throw error if callback is set

^^



+static DestReceiver spi_callbackDR = {
+    donothingReceive, donothingStartup, donothingCleanup, donothingCleanup,
+    DestSPICallback
+};


Is the callback destreceiver supposed to be a blackhole? Why? Its
name, spi_callbackDR and DestSPICallback, doesn't seem to indicate
that it drops its input.

Presumably that's a default destination you're then supposed to modify
with your own callbacks? There aren't any comments to indicate what's
going on here.



Comments on patch 2:

If this is the "bare minimum" patch, what is pending? Does it impose
any downsides or limits?

+/* Get switch execution contexts */
+extern PLyExecutionContext
*PLy_switch_execution_context(PLyExecutionContext *new);

Comment makes no sense to me. This seems something like memory context
switch, where you supply the new and return the old. But the comment
doesn't explain it at all.

+void PLy_CSStartup(DestReceiver *self, int operation, TupleDesc typeinfo);
+void PLy_CSDestroy(DestReceiver *self);

These are declared in the plpy_spi.c. Why aren't these static?

+    volatile MemoryContext oldcontext;
+    volatile ResourceOwner oldowner;
     int         rv;
-    volatile MemoryContext oldcontext;
-    volatile ResourceOwner oldowner;

Unnecessary code movement.

In PLy_Callback_New, I think your use of a sub-context is sensible. Is
it necessary to palloc0 though?

+static CallbackState *
+PLy_Callback_Free(CallbackState *callback)

The code here looks like it could be part of spi.c's callback support,
rather than plpy_spi specific, since you provide a destroy callback in
the SPI callback struct.

+    /* We need to store this because the TupleDesc the receive
function gets has no names. */
+    myState->desc = typeinfo;

Is it safe to just store the pointer to the TupleDesc here? What's the lifetime?

+     * will clean it up when the time is right. XXX This might result in a leak
+     * if an error happens and the result doesn't get dereferenced.
+     */
+    MemoryContextSwitchTo(TopMemoryContext);
+    result->tupdesc = CreateTupleDescCopy(typeinfo);

especially given this XXX comment...


Patch needs bug fix, docs updates, fixes for issues marked in
comments. But overall approach looks sensible enough.

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

Attachment

Re: Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/4/17 7:44 PM, Craig Ringer wrote:
> The patch crashes in initdb with --enable-cassert builds:

Thanks for the review! I'll get to the rest of it in a bit, but I'm 
unable to reproduce the initdb failure. I looked at the assert line and 
I don't see anything obvious either. :/

Can you send your full configure call? uname -a? Mine is:

./configure --with-includes=/opt/local/include 
--with-libraries=/opt/local/lib --enable-debug --with-libxml --with-tcl 
--with-perl --with-python --enable-depend --enable-dtrace 
--enable-tap-tests --prefix=/Users/decibel/pgsql/HEAD/i/i 
--with-pgport=$PGC_PORT -C --enable-cassert --enable-debug CFLAGS='-ggdb 
-O0 -fno-omit-frame-pointer'

Darwin decina.local 15.6.0 Darwin Kernel Version 15.6.0: Mon Jan  9 
23:07:29 PST 2017; root:xnu-3248.60.11.2.1~1/RELEASE_X86_64 x86_64
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com



Re: Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/4/17 7:44 PM, Craig Ringer wrote:
> On 5 April 2017 at 08:23, Craig Ringer <craig@2ndquadrant.com> wrote:
>> On 5 April 2017 at 08:00, Craig Ringer <craig@2ndquadrant.com> wrote:
> This patch fails to update the documentation at all.
>
> https://www.postgresql.org/docs/devel/static/spi.html

I'll fix that soon.

> missing newline

Fixed.

> +/* Execute a previously prepared plan with a callback Destination */
>
>
> caps "Destination"

Hmm, I capitalized it since DestReceiver is capitalized. I guess it's 
best to just drop Destination.

> +                // XXX throw error if callback is set

Fixed (opted to just use an Assert).

> +static DestReceiver spi_callbackDR = {
> +    donothingReceive, donothingStartup, donothingCleanup, donothingCleanup,
> +    DestSPICallback
> +};
> Presumably that's a default destination you're then supposed to modify
> with your own callbacks? There aren't any comments to indicate what's
> going on here.

Correct. Added the following comment:

> /*
>  * This is strictly a starting point for creating a callback. It should not
>  * actually be used.
>  */



> Comments on patch 2:
>
> If this is the "bare minimum" patch, what is pending? Does it impose
> any downsides or limits?

No limits. I'm not aware of any downsides.

It's "bare minimum" because a follow-on step is to allow different 
methods of returning results. In particular, my testing indicates that 
patch 1 + returning a dict of lists (as opposed to the current list of 
dicts) results in a 460% improvement vs ~30% with patch 2.

> +/* Get switch execution contexts */
> +extern PLyExecutionContext
> *PLy_switch_execution_context(PLyExecutionContext *new);
>
> Comment makes no sense to me. This seems something like memory context
> switch, where you supply the new and return the old. But the comment
> doesn't explain it at all.

Comment changed to
/* Switch execution context (similar to MemoryContextSwitchTo */

> +void PLy_CSStartup(DestReceiver *self, int operation, TupleDesc typeinfo);
> +void PLy_CSDestroy(DestReceiver *self);
>
> These are declared in the plpy_spi.c. Why aren't these static?

Derp. Fixed.

> +    volatile MemoryContext oldcontext;
> +    volatile ResourceOwner oldowner;
>      int         rv;
> -    volatile MemoryContext oldcontext;
> -    volatile ResourceOwner oldowner;
>
> Unnecessary code movement.

IMHO it reads better that way. I've left it for now so $COMMITTER can 
decide, but if it really bugs you let me know and I'll swap it.

> In PLy_Callback_New, I think your use of a sub-context is sensible. Is
> it necessary to palloc0 though?

Hrm, maybe not... but it seems like cheap insurance considering the 
amount of other stuff involved in just starting a new SPI call. And 
honestly, I'd rather not mess with it at this point. :) I have added an 
XXX comment though.

> +static CallbackState *
> +PLy_Callback_Free(CallbackState *callback)
>
> The code here looks like it could be part of spi.c's callback support,
> rather than plpy_spi specific, since you provide a destroy callback in
> the SPI callback struct.

I added this for use in PG_CATCH() blocks, because it's not clear to me 
that the portal gets cleaned up in those cases. It's certainly possible 
that it's pointless.

FWIW, I'm pretty sure I copied that pattern from somewhere else... 
probably plpgsql or pltcl.

> +    /* We need to store this because the TupleDesc the receive
> function gets has no names. */
> +    myState->desc = typeinfo;
>
> Is it safe to just store the pointer to the TupleDesc here? What's the lifetime?

Only Portal lifetime.

> +     * will clean it up when the time is right. XXX This might result in a leak
> +     * if an error happens and the result doesn't get dereferenced.
> +     */
> +    MemoryContextSwitchTo(TopMemoryContext);
> +    result->tupdesc = CreateTupleDescCopy(typeinfo);
>
> especially given this XXX comment...

I've changed the comment to the following. Hopefully this clarifies things:

>     /*
>      * Save tuple descriptor for later use by result set metadata
>      * functions.  Save it in TopMemoryContext so that it survives outside of
>      * an SPI context.  We trust that PLy_result_dealloc() will clean it up
>      * when the time is right. The difference between result and everything
>      * else is that result needs to survive after the portal is destroyed,
>      * because result is what's handed back to the plpython function. While
>      * it's tempting to use something other than TopMemoryContext, that won't
>      * work: the user could potentially put result into the global dictionary,
>      * which means it could survive as long as the session does.  This might
>      * result in a leak if an error happens and the result doesn't get
>      * dereferenced, but if that happens it means the python GC has failed us,
>      * at which point we probably have bigger problems.
>      *
>      * This still isn't perfect though; if something the result tupledesc
>      * references has it's OID changed then the tupledesc will be invalid. I'm
>      * not sure it's worth worrying about that though.
>      */

Updated patches attached, but I still need to update the docs.
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com

Attachment

Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 6 April 2017 at 09:40, Jim Nasby <jim.nasby@openscg.com> wrote:
> On 4/4/17 7:44 PM, Craig Ringer wrote:
>>
>> The patch crashes in initdb with --enable-cassert builds:
>
>
> Thanks for the review! I'll get to the rest of it in a bit, but I'm unable
> to reproduce the initdb failure. I looked at the assert line and I don't see
> anything obvious either. :/
>
> Can you send your full configure call? uname -a? Mine is:
>
> ./configure --with-includes=/opt/local/include
> --with-libraries=/opt/local/lib --enable-debug --with-libxml --with-tcl
> --with-perl --with-python --enable-depend --enable-dtrace --enable-tap-tests
> --prefix=/Users/decibel/pgsql/HEAD/i/i --with-pgport=$PGC_PORT -C
> --enable-cassert --enable-debug CFLAGS='-ggdb -O0 -fno-omit-frame-pointer'


./configure --prefix=/home/craig/pg/10 --enable-cassert --enable-debug
--enable-tap-tests --with-python
make -s clean
make -s -j4
make check

results in the crash here.

if I add
   CFLAGS=""

to the arguments (which suppresses the default "-O2"), or pass
   CFLAGS="-O0"

then the crash goes away.



$ python --version
Python 2.7.11

$ lsb_release -a
LSB Version:
:core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
Distributor ID: Fedora
Description: Fedora release 23 (Twenty Three)
Release: 23
Codename: TwentyThree

$ gcc --version
gcc (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)


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



Re: Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/5/17 7:44 PM, Jim Nasby wrote:
> Updated patches attached, but I still need to update the docs.

Attached is a complete series of patches that includes the docs patch.

Right now, the docs don't include a concrete example, because adding one 
would be a pretty large if it demonstrated real usage, which presumably 
means Yet Another Contrib Module strictly for the purpose of 
demonstrating something. Rather than doing that, ISTM it'd be better to 
point the user at what plpythonu is doing.

Another option would be to have a very simple example that only uses 
*receiveSlot, but that seems rather pointless to me.
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com

Attachment

Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 6 April 2017 at 11:46, Craig Ringer <craig@2ndquadrant.com> wrote:
> On 6 April 2017 at 09:40, Jim Nasby <jim.nasby@openscg.com> wrote:
>> On 4/4/17 7:44 PM, Craig Ringer wrote:
>>>
>>> The patch crashes in initdb with --enable-cassert builds:
>>
>>
>> Thanks for the review! I'll get to the rest of it in a bit, but I'm unable
>> to reproduce the initdb failure. I looked at the assert line and I don't see
>> anything obvious either. :/
>>
>> Can you send your full configure call? uname -a? Mine is:
>>
>> ./configure --with-includes=/opt/local/include
>> --with-libraries=/opt/local/lib --enable-debug --with-libxml --with-tcl
>> --with-perl --with-python --enable-depend --enable-dtrace --enable-tap-tests
>> --prefix=/Users/decibel/pgsql/HEAD/i/i --with-pgport=$PGC_PORT -C
>> --enable-cassert --enable-debug CFLAGS='-ggdb -O0 -fno-omit-frame-pointer'
>
>
> ./configure --prefix=/home/craig/pg/10 --enable-cassert --enable-debug
> --enable-tap-tests --with-python
> make -s clean
> make -s -j4
> make check
>
> results in the crash here.

... which I can't reproduce now. Even though I cleared ccache and "git
reset -fdx" before I ran the above and got the crash.

Assume it's a local system peculiarity. If I can reproduce again I'll
dig into it.

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



Re: Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/5/17 9:08 PM, Craig Ringer wrote:
> ... which I can't reproduce now. Even though I cleared ccache and "git
> reset -fdx" before I ran the above and got the crash.

Glad to hear that, since I can't repro at all. :)

> Assume it's a local system peculiarity. If I can reproduce again I'll
> dig into it.

Sounds good. Thanks!
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com



Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 6 April 2017 at 11:50, Jim Nasby <jim.nasby@openscg.com> wrote:

> Attached is a complete series of patches that includes the docs patch.

+   <function>SPI_execute_callback</function> is the same as
+   <function>SPI_execute</function>, except that instead of returning results
+   via <structname>SPITupleTable</structname>, the user-supplied
<parameter>callback</parameter>
+   is used. Unlike
+   <function>SPI_execute</function>,
+   <function>SPI_execute_callback</function>
+   will run the callback for every SQL command passed in to
<parameter>command</parameter>.

This doesn't explain why the user should care or prefer this approach.
Maybe after "Unlike":

"<function>SPI_execute_callback</> does not need to accumulate all the
query results into memory before the caller can process them. Instead
of building a <literal>SPI_tuptable</> containing all the results, the
supplied callback is invoked for each row processed by SPI. The row
data is passed to the callback then discarded when the callback
returns. This reduces copying and allows the application to process
results sooner."

The docs do not discuss memory lifetimes. What memory context is each
call invoked in and when is it reset? Something like:

"<literal>rStartup</>, <literal>receiveSlot</> and
<literal>rShutdown</> are all called in a memory context that is reset
for each <function>SPI_execute_callback</>."

Also, what rules apply in terms of what you can/cannot do from within
a callback? Presumably it's unsafe to perform additional SPI calls,
perform transactions, call into the executor, change the current
snapshot, etc, but I would consider that reasonably obvious. Are there
any specific things to avoid?

Under what circumstances is  the query execution context [...] freed"
such that the destroy callback is called?

It is not clear to me when reading the document that the user of
SPI_execute_callback is expected to define their own
DestReceiver-compatible struct, and that the private fields that "may
appear beyond this point" are those defined by the application. How
does the app know that its DestReceiver is compatible with the result
of CreateDestReceiver(DestSPICallback)? Should it copy the fields?
etc. Your PLPython example incorporates DestReceiver by-value in its
own state struct; maybe your docs program listing should illustrate
that instead, show how to initialize the destreceiver member, and
separately list the callbacks in destreceiver in an <itemisedlist> ?


It definitely needs to mention the relevant parts of plpython to look
at for an example of usage.

> Right now, the docs don't include a concrete example, because adding one
> would be a pretty large if it demonstrated real usage, which presumably
> means Yet Another Contrib Module strictly for the purpose of demonstrating
> something. Rather than doing that, ISTM it'd be better to point the user at
> what plpythonu is doing.

Seems fine to me.

Notes on the docs aside, I am pretty happy with this and think it's
reasonable to proceed with it for Pg 10.

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



Re: Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 6 April 2017 at 15:38, Craig Ringer <craig@2ndquadrant.com> wrote:

> Notes on the docs aside, I am pretty happy with this and think it's
> reasonable to proceed with it for Pg 10.

Actually, I'm a bit hesitant about returning a static struct that you
expect callers to copy and modify.  But it seems to be an issue with
CreateDestReceiver's interface where it mixes returning pointers to
static structs with returning pointers to palloc'd memory, not a new
issue added by this patch.

I think you're better off bypassing CreateDestReceiver here, and doing
what CreateTransientRelDestReceiver(), etc do directly instead. That
way there's no need for the hoop jumping of returning a pointer to a
static struct to memcpy() into another struct in:

+    memcpy(&(callback->pub), CreateDestReceiver(DestSPICallback),
sizeof(DestReceiver));

Just document that the spi callback struct must have DestReciever as
its first member and must be palloc0'd.

But otherwise, pending docs changes, I think it's ready for committer.

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



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Peter Eisentraut
Date:
On 4/6/17 03:50, Craig Ringer wrote:
> But otherwise, pending docs changes, I think it's ready for committer.

My opinion is still that this is ultimately the wrong approach.  The
right fix for performance issues in PL/Python is to change PL/Python not
to materialize the list of tuples.  Now with this change we would be
moving from two result materializations to one, but I think we are
keeping the wrong one.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/6/17 9:04 AM, Peter Eisentraut wrote:
> On 4/6/17 03:50, Craig Ringer wrote:
>> But otherwise, pending docs changes, I think it's ready for committer.
>
> My opinion is still that this is ultimately the wrong approach.  The
> right fix for performance issues in PL/Python is to change PL/Python not
> to materialize the list of tuples.  Now with this change we would be
> moving from two result materializations to one, but I think we are
> keeping the wrong one.

That's an option for future improvement, but I see no way to accomplish 
that without completely breaking plpy.

I think the best way to handle this would be to allow plpython functions 
to define their own callback function, which would be handed a python 
tuple that was translated from the SPI result tuple. How best to do that 
without breaking plpy will require some thought though.

In the meantime, I don't think a 27% performance gain is anything to 
sneeze at, and the SPI changes would be directly usable by pl/r and pl/tcl.
-- 
Jim C. Nasby, Data Architect                       jim@nasby.net
512.569.9461 (cell)                         http://jim.nasby.net



Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
> On 4/6/17 03:50, Craig Ringer wrote:
>> But otherwise, pending docs changes, I think it's ready for committer.

> My opinion is still that this is ultimately the wrong approach.  The
> right fix for performance issues in PL/Python is to change PL/Python not
> to materialize the list of tuples.  Now with this change we would be
> moving from two result materializations to one, but I think we are
> keeping the wrong one.

I just looked at these patches for the first time, and TBH I'm a bit
dubious too.  I don't particularly have an opinion about the 0003 patch,
but I think that's mostly what Peter is on about.  I do have opinions
about 0001 (and 0002, which ought to be merged; we do not commit features
separately from documentation around here).

I can certainly get on board with the idea of letting a SPI caller provide
a DestReceiver instead of accumulating the query results into a
SPITupleTable, but the way it was done here seems quite bizarre.  I think
for instance you've mishandled non-canSetTag queries; those should have
any results discarded, full stop.  External callers will only be
interested in the result of the canSetTag subquery.

I don't much like DestSPICallback either.  We may well need some better
way for extension code to create a custom DestReceiver that does something
out of the ordinary with query result tuples.  But if so, it should not be
tied to SPI, not even just by name.

I think that 0001/0002 need to be refactored as (A) a change to make
DestReceiver creation more flexible, and then (B) a change to SPI to allow
a caller to pass in the receiver to use.

After poking around a bit, it seems like we've allowed the original
notion of CommandDest to get way out of hand.  The only places where
CreateDestReceiver gets called with a non-constant argument (that is,
where the caller doesn't know perfectly well which kind of DestReceiver
it wants) are two calls in postgres.c, and both of those are ultimately
passing whereToSendOutput, which has got only a really limited set of
possible values.  I am thinking that we should cut down enum CommandDest
to be just
   DestNone,                    /* results are discarded */   DestDebug,                   /* results go to debugging
output*/   DestRemote,                  /* results sent to frontend process */   DestOther                    /*
somethingelse */
 

and change CreateDestReceiver so it throws an error for DestOther; the
way to create any type of receiver other than these few is to call the
underlying creation function directly, rather than going through
CreateDestReceiver.

Having done that, the means for creating a custom receiver is just to
set up an appropriate struct that embeds struct _DestReceiver and
always has mydest = DestOther (or maybe we should just lose the mydest
field altogether).  We could document that a bit better, but it's really
not complicated.

There's enough cruft that's accumulated in this area that this would
probably not be an entirely trivial patch, but I think it would be
a good way to make things cleaner.
        regards, tom lane



Re: [HACKERS] Faster methods for getting SPI results (460% improvement)

From
Craig Ringer
Date:
On 7 April 2017 at 00:54, Tom Lane <tgl@sss.pgh.pa.us> wrote:

> I can certainly get on board with the idea of letting a SPI caller provide
> a DestReceiver instead of accumulating the query results into a
> SPITupleTable, but the way it was done here seems quite bizarre.  I think
> for instance you've mishandled non-canSetTag queries; those should have
> any results discarded, full stop.  External callers will only be
> interested in the result of the canSetTag subquery.

That's something I didn't even know was a thing to look for. Thanks
for spotting it.

For other readers who may also be confused, this refers to internally
generated queries that should not be visible to the caller. We use
these in things like CREATE SCHEMA, ALTER TABLE, in FKs, etc.

I wasn't aware that such queries could ever return a result set, though.

> I don't much like DestSPICallback either.  We may well need some better
> way for extension code to create a custom DestReceiver that does something
> out of the ordinary with query result tuples.  But if so, it should not be
> tied to SPI, not even just by name.
>
> I think that 0001/0002 need to be refactored as (A) a change to make
> DestReceiver creation more flexible, and then (B) a change to SPI to allow
> a caller to pass in the receiver to use.

That's exactly what I tried to avoid suggesting upthread, since it'd
be quite much more invasive than the current patch, though definitely
a desirable cleanup.

Personally I think this patch should be allowed to bypass
CreateDestReceiver and create its own struct. I don't really see that
it should be required to clean up the whole API first.

It's on the pointy end for Pg10, and I thought we'd be fine to include
this in pg10 then aim to clean up DestReceiver in early pg11, or even
as a post-feature-freeze refactoring fixup in pg10. Should the
callback approach be blocked because the API it has to use is a bit
ugly?

> After poking around a bit, it seems like we've allowed the original
> notion of CommandDest to get way out of hand.  The only places where
> CreateDestReceiver gets called with a non-constant argument (that is,
> where the caller doesn't know perfectly well which kind of DestReceiver
> it wants) are two calls in postgres.c, and both of those are ultimately
> passing whereToSendOutput, which has got only a really limited set of
> possible values.  I am thinking that we should cut down enum CommandDest
> to be just
>
>     DestNone,                    /* results are discarded */
>     DestDebug,                   /* results go to debugging output */
>     DestRemote,                  /* results sent to frontend process */
>     DestOther                    /* something else */
>
> and change CreateDestReceiver so it throws an error for DestOther; the
> way to create any type of receiver other than these few is to call the
> underlying creation function directly, rather than going through
> CreateDestReceiver.
>
> Having done that, the means for creating a custom receiver is just to
> set up an appropriate struct that embeds struct _DestReceiver and
> always has mydest = DestOther (or maybe we should just lose the mydest
> field altogether).  We could document that a bit better, but it's really
> not complicated.

I strongly agree that this is the way DestReceiver creation should be
refactored.

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



Craig Ringer <craig@2ndquadrant.com> writes:
> On 7 April 2017 at 00:54, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> ... External callers will only be
>> interested in the result of the canSetTag subquery.

> I wasn't aware that such queries could ever return a result set, though.

Possibly not, but the point is that they should be invisible to the
caller.  As the patch is set up, I think they'd look like empty result
sets instead, because the passed-in DestReceiver is used for them.
What we want is to use DestNone for non-canSetTag queries, and either
DestSPI or the caller's receiver for the canSetTag query.

> Personally I think this patch should be allowed to bypass
> CreateDestReceiver and create its own struct. I don't really see that
> it should be required to clean up the whole API first.

Well, if you bypass CreateDestReceiver then the question is what you're
putting in mydest and whether anything will get confused by that.  The
core problem with the existing API is that there's no provision for
adding new kinds of DestReceivers without a corresponding addition to
the CommandDest enum.  I think we really need some non-kluge solution
to that before moving forward.

> It's on the pointy end for Pg10, and I thought we'd be fine to include
> this in pg10 then aim to clean up DestReceiver in early pg11, or even
> as a post-feature-freeze refactoring fixup in pg10. Should the
> callback approach be blocked because the API it has to use is a bit
> ugly?

Given Peter's objections, I don't think this is getting into v10 anyway,
so we might as well take a bit more time and do it right.

Also, I'm entirely -1 on "post-feature-freeze refactoring fixups".
We're going to have more than enough to do trying to stabilize the
existing committed code, I fear (cf Robert's pessimistic summary of
the open-items list, a couple days ago).  We don't need to be
planning on doing new design post-freeze, whether it's painted as
mere refactoring or not.
        regards, tom lane



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/6/17 8:13 PM, Tom Lane wrote:
>> It's on the pointy end for Pg10, and I thought we'd be fine to include
>> this in pg10 then aim to clean up DestReceiver in early pg11, or even
>> as a post-feature-freeze refactoring fixup in pg10. Should the
>> callback approach be blocked because the API it has to use is a bit
>> ugly?
> Given Peter's objections, I don't think this is getting into v10 anyway,
> so we might as well take a bit more time and do it right.

Well, Peter's objection is that we're not going far enough in plpython, 
but there's absolutely no way to do more without breaking plpy, which 
seems a non-starter. We should certainly be able to expand the existing 
API to provide even more benefit, but I see no reason to leave the 
performance gain this patch provides on the floor just because there's 
more to be had with a different API.

> Also, I'm entirely -1 on "post-feature-freeze refactoring fixups".
> We're going to have more than enough to do trying to stabilize the
> existing committed code, I fear (cf Robert's pessimistic summary of
> the open-items list, a couple days ago).  We don't need to be
> planning on doing new design post-freeze, whether it's painted as
> mere refactoring or not.

Agreed, and I agree that the current patch is a bit of a hack when it 
comes to DestReceiver (or really, DestReceiver has become an ugly wart 
over the years, as you pointed out).

I'll plan to pick this up again once the dust settles on this commitfest.
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Andres Freund
Date:
On 2017-04-06 09:14:43 -0700, Jim Nasby wrote:
> On 4/6/17 9:04 AM, Peter Eisentraut wrote:
> > On 4/6/17 03:50, Craig Ringer wrote:
> > > But otherwise, pending docs changes, I think it's ready for committer.
> > 
> > My opinion is still that this is ultimately the wrong approach.  The
> > right fix for performance issues in PL/Python is to change PL/Python not
> > to materialize the list of tuples.  Now with this change we would be
> > moving from two result materializations to one, but I think we are
> > keeping the wrong one.
> 
> That's an option for future improvement, but I see no way to accomplish that
> without completely breaking plpy.

Why?  We could very well return a somewhat "smarter" object. Returning
rows row-by-row if accessed via iterator, materializes when accessed via
row offset.

- Andres



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/6/17 9:04 PM, Andres Freund wrote:
> On 2017-04-06 09:14:43 -0700, Jim Nasby wrote:
>> On 4/6/17 9:04 AM, Peter Eisentraut wrote:
>>> On 4/6/17 03:50, Craig Ringer wrote:
>>>> But otherwise, pending docs changes, I think it's ready for committer.
>>>
>>> My opinion is still that this is ultimately the wrong approach.  The
>>> right fix for performance issues in PL/Python is to change PL/Python not
>>> to materialize the list of tuples.  Now with this change we would be
>>> moving from two result materializations to one, but I think we are
>>> keeping the wrong one.
>>
>> That's an option for future improvement, but I see no way to accomplish that
>> without completely breaking plpy.
>
> Why?  We could very well return a somewhat "smarter" object. Returning
> rows row-by-row if accessed via iterator, materializes when accessed via
> row offset.

I completely agree with that. What I don't understand is the objection 
to speeding up the old access method. Or are you thinking we'd just 
abandon the old method?
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Andres Freund
Date:
On 2017-04-06 21:06:59 -0700, Jim Nasby wrote:
> On 4/6/17 9:04 PM, Andres Freund wrote:
> > On 2017-04-06 09:14:43 -0700, Jim Nasby wrote:
> > > On 4/6/17 9:04 AM, Peter Eisentraut wrote:
> > > > On 4/6/17 03:50, Craig Ringer wrote:
> > > > > But otherwise, pending docs changes, I think it's ready for committer.
> > > > 
> > > > My opinion is still that this is ultimately the wrong approach.  The
> > > > right fix for performance issues in PL/Python is to change PL/Python not
> > > > to materialize the list of tuples.  Now with this change we would be
> > > > moving from two result materializations to one, but I think we are
> > > > keeping the wrong one.
> > > 
> > > That's an option for future improvement, but I see no way to accomplish that
> > > without completely breaking plpy.
> > 
> > Why?  We could very well return a somewhat "smarter" object. Returning
> > rows row-by-row if accessed via iterator, materializes when accessed via
> > row offset.
> 
> I completely agree with that. What I don't understand is the objection to
> speeding up the old access method. Or are you thinking we'd just abandon the
> old method?

What I'm saying is that we can do that transparently, with the current
API.  And there's no need to materialize anything in plpython, we can
transparently use the SPI materialized version.

Greetings,

Andres Freund



Re: [HACKERS] Faster methods for getting SPI results (460% improvement)

From
Jim Nasby
Date:
On Apr 6, 2017, at 9:10 PM, Andres Freund <andres@anarazel.de> wrote:
>
>>> Why?  We could very well return a somewhat "smarter" object. Returning
>>> rows row-by-row if accessed via iterator, materializes when accessed via
>>> row offset.
>>
>> I completely agree with that. What I don't understand is the objection to
>> speeding up the old access method. Or are you thinking we'd just abandon the
>> old method?
>
> What I'm saying is that we can do that transparently, with the current
> API.  And there's no need to materialize anything in plpython, we can
> transparently use the SPI materialized version.

Oh, just switching from a list to an iterator. Ok, I finally get it.


Jim Nasby <jim.nasby@openscg.com> writes:
> On 4/6/17 8:13 PM, Tom Lane wrote:
>> Given Peter's objections, I don't think this is getting into v10 anyway,
>> so we might as well take a bit more time and do it right.

> Well, Peter's objection is that we're not going far enough in plpython, 
> but there's absolutely no way to do more without breaking plpy, which 
> seems a non-starter. We should certainly be able to expand the existing 
> API to provide even more benefit, but I see no reason to leave the 
> performance gain this patch provides on the floor just because there's 
> more to be had with a different API.

Personally I'm way more excited about what a SPI feature like this
could do for plpgsql than about what it can do for plpython.  If the
latter is what floats your boat, that's fine; but I want a feature
that we can build on for other uses, not a hack that we know we need
to redesign next month.
        regards, tom lane



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Andres Freund
Date:
On 2017-04-07 00:11:59 -0400, Tom Lane wrote:
> Jim Nasby <jim.nasby@openscg.com> writes:
> > On 4/6/17 8:13 PM, Tom Lane wrote:
> >> Given Peter's objections, I don't think this is getting into v10 anyway,
> >> so we might as well take a bit more time and do it right.
> 
> > Well, Peter's objection is that we're not going far enough in plpython, 
> > but there's absolutely no way to do more without breaking plpy, which 
> > seems a non-starter. We should certainly be able to expand the existing 
> > API to provide even more benefit, but I see no reason to leave the 
> > performance gain this patch provides on the floor just because there's 
> > more to be had with a different API.
> 
> Personally I'm way more excited about what a SPI feature like this
> could do for plpgsql than about what it can do for plpython.  If the
> latter is what floats your boat, that's fine; but I want a feature
> that we can build on for other uses, not a hack that we know we need
> to redesign next month.

Dislike of the proposed implementation, alternative proposals, and the
refutation of the "absolutely no way to do more without breaking plpy"
argument leads to me to conclude that this should be returned with
feedback.

- Andres



Re: [HACKERS] Faster methods for getting SPI results (460%improvement)

From
Jim Nasby
Date:
On 4/6/17 9:21 PM, Andres Freund wrote:
>> Personally I'm way more excited about what a SPI feature like this
>> could do for plpgsql than about what it can do for plpython.  If the
>> latter is what floats your boat, that's fine; but I want a feature
>> that we can build on for other uses, not a hack that we know we need
>> to redesign next month.

Yeah, I thought about plpgsql and I can't see any way to make that work 
through an SPI callback (perhaps just due to my ignorance on things C). 
I suspect what plpgsql actually wants is a way to tell SPI to start the 
executor up, a function that pulls individual tuples out of the 
executor, and then a function to shut the executor down.

> Dislike of the proposed implementation, alternative proposals, and the
> refutation of the "absolutely no way to do more without breaking plpy"
> argument leads to me to conclude that this should be returned with
> feedback.

Agreed.
-- 
Jim Nasby, Chief Data Architect, Austin TX
OpenSCG                 http://OpenSCG.com



Re: [HACKERS] Faster methods for getting SPI results

From
Chapman Flack
Date:
On 12/20/16 23:14, Jim Nasby wrote:
> I'm guessing one issue might be that
> we don't want to call an external interpreter while potentially holding page
> pins, but even then couldn't we just copy a single tuple at a time and save
> a huge amount of palloc overhead?

On 04/06/17 03:38, Craig Ringer wrote:
> Also, what rules apply in terms of what you can/cannot do from within
> a callback? Presumably it's unsafe to perform additional SPI calls,
> perform transactions, call into the executor, change the current
> snapshot, etc, but I would consider that reasonably obvious. Are there
> any specific things to avoid?


Confessing, right up front, that I'm not very familiar with the executor
or DestReceiver code, but thinking of issues that might be expected with
PLs, I wonder if there could be a design where the per-tuple callback
could sometimes return a status HAVE_SLOW_STUFF_TO_DO.

If it does, the executor could release some pins or locks, stack some
state, whatever allows it to (as far as practicable) relax restrictions
on what the callback would be allowed to do, then reinvoke the callback,
not with another tuple, but with OK_GO_DO_YOUR_SLOW_STUFF.

On return from that call, the executor could reacquire its stacked
state/locks/pins and resume generating tuples.

That way, a callback could, say, return normally 9 out of 10 times, just
quickly buffering up 10 tuples, and every 10th time return SLOW_STUFF_TO_DO
and get a chance to jump into the PL interpreter and deal with those 10 ...
(a) minimizing the restrictions on what the PL routine may do, and (b)
allowing any costs of state-stacking/lock-releasing-reacquiring, and control
transfer to the interpreter, to be amortized over some number of tuples.
How many tuples that should be might be an empirical question for any given
PL, but with a protocol like this, the callback has an easy way to control
it.

Or would that be overcomplicated?

-Chap



Re: [HACKERS] Faster methods for getting SPI results

From
Tom Lane
Date:
So the conclusion at the end of the last commitfest was that this patch
should be marked Returned With Feedback, and no new work appears to have
been done on it since then.  Why is it in this fest at all?  There
certainly doesn't seem to be any reason to review it again.
        regards, tom lane


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

Re: [HACKERS] Faster methods for getting SPI results

From
Chapman Flack
Date:
On 09/12/2017 03:41 PM, Tom Lane wrote:
> So the conclusion at the end of the last commitfest was that this patch
> should be marked Returned With Feedback, and no new work appears to have
> been done on it since then.  Why is it in this fest at all?  There
> certainly doesn't seem to be any reason to review it again.

I'm not sure how to read the history of the CF entry. Could it
have rolled over to 2017-09 by default if its status was simply
never changed to Returned with Feedback as intended in the last
one? The history doesn't seem to show anything since 2017-03-19.

I would still advocate for a fast-callback/slow-callback distinction,
as in
https://www.postgresql.org/message-id/59813946.40508%40anastigmatix.net
if that does not seem overcomplicated to more experienced hands.

-Chap


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

Re: [HACKERS] Faster methods for getting SPI results

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> On 09/12/2017 03:41 PM, Tom Lane wrote:
>> So the conclusion at the end of the last commitfest was that this patch
>> should be marked Returned With Feedback, and no new work appears to have
>> been done on it since then.  Why is it in this fest at all?  There
>> certainly doesn't seem to be any reason to review it again.

> I'm not sure how to read the history of the CF entry. Could it
> have rolled over to 2017-09 by default if its status was simply
> never changed to Returned with Feedback as intended in the last
> one? The history doesn't seem to show anything since 2017-03-19.

Maybe, or whoever was closing out the last CF didn't notice Andres'
recommendation to mark it RWF.

> I would still advocate for a fast-callback/slow-callback distinction,
> as in
> https://www.postgresql.org/message-id/59813946.40508%40anastigmatix.net
> if that does not seem overcomplicated to more experienced hands.

I did not see any reason given in the thread why we should need that.
If you want to accumulate tuples ten at a time before you do something
with them, you can do that now, by calling ExecutorRun with count=10.
(plpgsql does something much like that IIRC.)  The only reason not to
just use count=1 is that ExecutorRun and ExecutePlan have accumulated
assorted startup/shutdown cruft on the assumption that their runtime
didn't particularly matter.  It still doesn't look that awful, but
it might be noticeable.
        regards, tom lane


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

Re: [HACKERS] Faster methods for getting SPI results

From
Chapman Flack
Date:
On 09/12/17 17:00, Tom Lane wrote:

> I did not see any reason given in the thread why we should need that.
> If you want to accumulate tuples ten at a time before you do something
> with them, you can do that now, by calling ExecutorRun with count=10.

Ah, that sounds easy enough. I'll withdraw the more-complicated suggestion.

-Chap


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

Re: [HACKERS] Faster methods for getting SPI results

From
Daniel Gustafsson
Date:
> On 12 Sep 2017, at 23:00, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Chapman Flack <chap@anastigmatix.net> writes:
>> On 09/12/2017 03:41 PM, Tom Lane wrote:
>>> So the conclusion at the end of the last commitfest was that this patch
>>> should be marked Returned With Feedback, and no new work appears to have
>>> been done on it since then.  Why is it in this fest at all?  There
>>> certainly doesn't seem to be any reason to review it again.
>
>> I'm not sure how to read the history of the CF entry. Could it
>> have rolled over to 2017-09 by default if its status was simply
>> never changed to Returned with Feedback as intended in the last
>> one? The history doesn't seem to show anything since 2017-03-19.
>
> Maybe, or whoever was closing out the last CF didn't notice Andres'
> recommendation to mark it RWF.

It doesn’t seem to have been moved to this CF but was actually created here in
the first place.  Reading this thread it seems like there is clear concensus on
the status though so changing to RWF.

cheers ./daniel

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