Thread: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

Mark Callaghan reported a regression in 15, in the post linked here (with
comments in the twitter thread, hence linked here)
https://twitter.com/MarkCallaghanDB/status/1537475430227161098

A differential flame graph shows increased time spent doing memory
allocations, below ExecInitExpr():
https://mdcallag.github.io/reports/22_06_06_ibench.20m.pg.all/l.i0.0.143.15b1.n.svg

Quite the useful comparison.


That lead to me to look at the size of ExprEvalStep - and indeed, it has
*exploded* in 15. 10-13: 64 bytes, 14: 320 bytes.

The comment above the union for data fields says:
    /*
     * Inline data for the operation.  Inline data is faster to access, but
     * also bloats the size of all instructions.  The union should be kept to
     * no more than 40 bytes on 64-bit systems (so that the entire struct is
     * no more than 64 bytes, a single cacheline on common systems).
     */

However, jsonexpr/EEOP_JSONEXPR is 296 bytes, and
hashedscalararrayop/EEOP_HASHED_SCALARARRAYOP is 64 bytes, even though the
limit is 40 bytes.

The EEOP_JSONEXPR stuff was added during 15 development in:

commit 1a36bc9dba8eae90963a586d37b6457b32b2fed4
Author: Andrew Dunstan <andrew@dunslane.net>
Date:   2022-03-03 13:11:14 -0500

    SQL/JSON query functions


the EEOP_HASHED_SCALARARRAYOP stuff was added during 14 development in:

commit 50e17ad281b8d1c1b410c9833955bc80fbad4078
Author: David Rowley <drowley@postgresql.org>
Date:   2021-04-08 23:51:22 +1200

    Speedup ScalarArrayOpExpr evaluation


Unfortunately ExprEvalStep is public, so I don't think we can fix the 14
regression. If somebody installed updated server packages while the server is
running, we could end up loading extensions referencing ExprEvalStep (at least
plpgsql and LLVMJIT).

It's not great to have an ABI break at this point of the 15 cycle, but I don't
think we have a choice. Exploding the size of ExprEvalStep by ~4x is bad -
both for memory overhead (expressions often have dozens to hundreds of steps)
and expression evaluation performance.


The Hashed SAO case can perhaps be squeezed sufficiently to fit inline, but
clearly that's not going to happen for the json case. So we should just move
that out of line.


Maybe it's worth sticking a StaticAssert() for the struct size somewhere. I'm
a bit wary about that being too noisy, there are some machines with odd
alignment requirements. Perhaps worth restricting the assertion to x86-64 +
armv8 or such?


It very well might be that this isn't the full explanation of the regression
Mark observed. E.g. the difference in DecodeDateTime() looks more likely to be
caused by 591e088dd5b - but we need to fix the above issue, whether it's the
cause of the regression Mark observed or not.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> However, jsonexpr/EEOP_JSONEXPR is 296 bytes, and
> hashedscalararrayop/EEOP_HASHED_SCALARARRAYOP is 64 bytes, even though the
> limit is 40 bytes.

Oops.

> Maybe it's worth sticking a StaticAssert() for the struct size
> somewhere.

Indeed.  I thought we had one already.

> I'm a bit wary about that being too noisy, there are some machines with
> odd alignment requirements. Perhaps worth restricting the assertion to
> x86-64 + armv8 or such?

I'd put it in first and only reconsider if it shows unfixable problems.

            regards, tom lane



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-16 16:31:30 -0700, Andres Freund wrote:
> The EEOP_JSONEXPR stuff was added during 15 development in:
>
> commit 1a36bc9dba8eae90963a586d37b6457b32b2fed4
> Author: Andrew Dunstan <andrew@dunslane.net>
> Date:   2022-03-03 13:11:14 -0500
>
>     SQL/JSON query functions

I'm quite confused about part of the struct definition of this:

            struct JsonCoercionsState
            {
                struct JsonCoercionState
                {
                    JsonCoercion *coercion; /* coercion expression */
                    ExprState  *estate; /* coercion expression state */
                }            null,
                            string,
                numeric    ,
                            boolean,
                            date,
                            time,
                            timetz,
                            timestamp,
                            timestamptz,
                            composite;
            }            coercions;    /* states for coercion from SQL/JSON item
                                     * types directly to the output type */

Why on earth do we have coercion state for all these different types? That
really adds up:

                struct {
                        JsonExpr * jsexpr;               /*    24     8 */
                        struct {
                                FmgrInfo func;           /*    32    48 */
                                /* --- cacheline 1 boundary (64 bytes) was 16 bytes ago --- */
                                Oid typioparam;          /*    80     4 */
                        } input;                         /*    32    56 */

                        /* XXX last struct has 4 bytes of padding */

                        NullableDatum * formatted_expr;  /*    88     8 */
                        NullableDatum * res_expr;        /*    96     8 */
                        NullableDatum * coercion_expr;   /*   104     8 */
                        NullableDatum * pathspec;        /*   112     8 */
                        ExprState * result_expr;         /*   120     8 */
                        /* --- cacheline 2 boundary (128 bytes) --- */
                        ExprState * default_on_empty;    /*   128     8 */
                        ExprState * default_on_error;    /*   136     8 */
                        List *     args;                 /*   144     8 */
                        void *     cache;                /*   152     8 */
                        struct JsonCoercionsState coercions; /*   160   160 */
                } jsonexpr;                              /*    24   296 */

And why is FmgrInfo stored inline in the struct? Everything else just stores
pointers to FmgrInfo.


Now that I look at this: It's a *bad* idea to have subsidiary ExprState inside
an ExprState. Nearly always the correct thing to do is to build those
expressions. There's plenty memory and evaluation overhead in jumping to a
different expression. And I see no reason for doing it that way here?

This stuff doesn't look ready.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
David Rowley
Date:
On Fri, 17 Jun 2022 at 11:31, Andres Freund <andres@anarazel.de> wrote:
> hashedscalararrayop/EEOP_HASHED_SCALARARRAYOP is 64 bytes, even though the
> limit is 40 bytes.

> commit 50e17ad281b8d1c1b410c9833955bc80fbad4078
> Author: David Rowley <drowley@postgresql.org>
> Date:   2021-04-08 23:51:22 +1200
>
>     Speedup ScalarArrayOpExpr evaluation

I've put together the attached patch which removes 4 fields from the
hashedscalararrayop portion of the struct which, once the JSON part is
fixed, will put sizeof(ExprEvalStep) back down to 64 bytes again.

The attached patch causes some extra pointer dereferencing to perform
a hashed saop step, so I tested the performance on f4fb45d15 (prior to
the JSON patch that pushed the sizeof(ExprEvalStep) up further. I
found:

setup:
create table a (a int);
insert into a select x from generate_series(1000000,2000000) x;

bench.sql
select * from a where a in(1,2,3,4,5,6,7,8,9,10);

f4fb45d15 + reduce_sizeof_hashedsaop_ExprEvalStep.patch
drowley@amd3990x:~$ pgbench -n -f bench.sql -T 60 -M prepared postgres
tps = 44.841851 (without initial connection time)
tps = 44.986472 (without initial connection time)
tps = 44.944315 (without initial connection time)

f4fb45d15
drowley@amd3990x:~$ pgbench -n -f bench.sql -T 60 -M prepared postgres
tps = 44.446127 (without initial connection time)
tps = 44.614134 (without initial connection time)
tps = 44.895011 (without initial connection time)

(Patched is ~0.61% faster here)

So, there appears to be no performance regression due to the extra
indirection. There's maybe even some gains due to the smaller step
size.

David

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Peter Geoghegan
Date:
On Thu, Jun 16, 2022 at 7:15 PM David Rowley <dgrowleyml@gmail.com> wrote:
> So, there appears to be no performance regression due to the extra
> indirection. There's maybe even some gains due to the smaller step
> size.

Have you tried this with the insert benchmark [1]?

I've run it myself in the past (when working on B-Tree deduplication).
It's quite straightforward to set up and run.

[1] http://smalldatum.blogspot.com/2017/06/the-insert-benchmark.html
-- 
Peter Geoghegan



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
David Rowley
Date:
On Fri, 17 Jun 2022 at 15:33, Peter Geoghegan <pg@bowt.ie> wrote:
> Have you tried this with the insert benchmark [1]?

I was mostly focusing on the performance of the hashed saop feature
after having removed the additional fields that pushed ExprEvalStep
over 64 bytes in 14.

I agree it would be good to do further benchmarking to see if there's
anything else that's snuck into 15 that's slowed that benchmark down,
but we can likely work on that after we get the ExprEvalStep size back
to 64 bytes again.

David



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-17 16:53:31 +1200, David Rowley wrote:
> On Fri, 17 Jun 2022 at 15:33, Peter Geoghegan <pg@bowt.ie> wrote:
> > Have you tried this with the insert benchmark [1]?
> 
> I was mostly focusing on the performance of the hashed saop feature
> after having removed the additional fields that pushed ExprEvalStep
> over 64 bytes in 14.
> 
> I agree it would be good to do further benchmarking to see if there's
> anything else that's snuck into 15 that's slowed that benchmark down,
> but we can likely work on that after we get the ExprEvalStep size back
> to 64 bytes again.

I did reproduce a regression between 14 and 15, using both pgbench -Mprepared
-S (scale 1) and TPC-H Q01 (scale 5). Between 7-10% - not good, particularly
that that's not been found so far. Fixing the json size issue gets that down
to ~2%. Not sure what that's caused by yet.

Greetings,

Andres Freund

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-16 22:22:28 -0700, Andres Freund wrote:
> On 2022-06-17 16:53:31 +1200, David Rowley wrote:
> > On Fri, 17 Jun 2022 at 15:33, Peter Geoghegan <pg@bowt.ie> wrote:
> > > Have you tried this with the insert benchmark [1]?
> >
> > I was mostly focusing on the performance of the hashed saop feature
> > after having removed the additional fields that pushed ExprEvalStep
> > over 64 bytes in 14.
> >
> > I agree it would be good to do further benchmarking to see if there's
> > anything else that's snuck into 15 that's slowed that benchmark down,
> > but we can likely work on that after we get the ExprEvalStep size back
> > to 64 bytes again.
>
> I did reproduce a regression between 14 and 15, using both pgbench -Mprepared
> -S (scale 1) and TPC-H Q01 (scale 5). Between 7-10% - not good, particularly
> that that's not been found so far. Fixing the json size issue gets that down
> to ~2%. Not sure what that's caused by yet.

The remaining difference looks like it's largely caused by the
enable_timeout_after(IDLE_STATS_UPDATE_TIMEOUT, ...) introduced as part of the
pgstats patch. It's only really visible when I pin a single connection pgbench
to the same CPU core as the server (which gives a ~16% boost here).

It's not the timeout itself - that we amortize nicely (via 09cf1d522). It's
that enable_timeout_after() does a GetCurrentTimestamp().

Not sure yet what the best way to fix that is.

We could just leave the timer active and add some gating condition indicating
idleness to the IdleStatsUpdateTimeoutPending body in ProcessInterrupts()?

Or we could add a timeout.c API that specifies the timeout?
pgstat_report_stat() uses GetCurrentTransactionStopTimestamp(), it seems like
it'd make sense to use the same for arming the timeout?

- Andres



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Kyotaro Horiguchi
Date:
At Thu, 16 Jun 2022 23:24:56 -0700, Andres Freund <andres@anarazel.de> wrote in 
> The remaining difference looks like it's largely caused by the
> enable_timeout_after(IDLE_STATS_UPDATE_TIMEOUT, ...) introduced as part of the
> pgstats patch. It's only really visible when I pin a single connection pgbench
> to the same CPU core as the server (which gives a ~16% boost here).
> 
> It's not the timeout itself - that we amortize nicely (via 09cf1d522). It's
> that enable_timeout_after() does a GetCurrentTimestamp().
> 
> Not sure yet what the best way to fix that is.
> 
> We could just leave the timer active and add some gating condition indicating
> idleness to the IdleStatsUpdateTimeoutPending body in ProcessInterrupts()?
> 
> Or we could add a timeout.c API that specifies the timeout?

I sometimes wanted this, But I don't see a simple way to sort multiple
relative timeouts in absolute time order.  Maybe we can skip
GetCurrentTimestamp only when inserting the first timeout, but I don't
think it benefits this case.

> pgstat_report_stat() uses GetCurrentTransactionStopTimestamp(), it seems like
> it'd make sense to use the same for arming the timeout?

This seems like the feasible best fix for this specific issue.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Kyotaro Horiguchi
Date:
At Fri, 17 Jun 2022 15:54:13 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in 
> > Or we could add a timeout.c API that specifies the timeout?
> 
> I sometimes wanted this, But I don't see a simple way to sort multiple
> relative timeouts in absolute time order.  Maybe we can skip
> GetCurrentTimestamp only when inserting the first timeout, but I don't
> think it benefits this case.

Or we can use a free-run interval timer and individual down-counter
for each timtouts.  I think we need at-most 0.1s resolution and error
of long-run timer doesn't harm?

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Kyotaro Horiguchi
Date:
At Fri, 17 Jun 2022 15:59:26 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in 
> At Fri, 17 Jun 2022 15:54:13 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in 
> > > Or we could add a timeout.c API that specifies the timeout?
> > 
> > I sometimes wanted this, But I don't see a simple way to sort multiple
> > relative timeouts in absolute time order.  Maybe we can skip
> > GetCurrentTimestamp only when inserting the first timeout, but I don't
> > think it benefits this case.
> 
> Or we can use a free-run interval timer and individual down-counter
> for each timtouts.  I think we need at-most 0.1s resolution and error
> of long-run timer doesn't harm?

Yeah, stupid. We don't want awake process with such a high frequency..

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
James Coleman
Date:
On Thu, Jun 16, 2022 at 10:15 PM David Rowley <dgrowleyml@gmail.com> wrote:
>
> On Fri, 17 Jun 2022 at 11:31, Andres Freund <andres@anarazel.de> wrote:
> > hashedscalararrayop/EEOP_HASHED_SCALARARRAYOP is 64 bytes, even though the
> > limit is 40 bytes.
>
> > commit 50e17ad281b8d1c1b410c9833955bc80fbad4078
> > Author: David Rowley <drowley@postgresql.org>
> > Date:   2021-04-08 23:51:22 +1200
> >
> >     Speedup ScalarArrayOpExpr evaluation
>
> I've put together the attached patch which removes 4 fields from the
> hashedscalararrayop portion of the struct which, once the JSON part is
> fixed, will put sizeof(ExprEvalStep) back down to 64 bytes again.
>
> The attached patch causes some extra pointer dereferencing to perform
> a hashed saop step, so I tested the performance on f4fb45d15 (prior to
> the JSON patch that pushed the sizeof(ExprEvalStep) up further. I
> found:
>
> setup:
> create table a (a int);
> insert into a select x from generate_series(1000000,2000000) x;
>
> bench.sql
> select * from a where a in(1,2,3,4,5,6,7,8,9,10);
>
> f4fb45d15 + reduce_sizeof_hashedsaop_ExprEvalStep.patch
> drowley@amd3990x:~$ pgbench -n -f bench.sql -T 60 -M prepared postgres
> tps = 44.841851 (without initial connection time)
> tps = 44.986472 (without initial connection time)
> tps = 44.944315 (without initial connection time)
>
> f4fb45d15
> drowley@amd3990x:~$ pgbench -n -f bench.sql -T 60 -M prepared postgres
> tps = 44.446127 (without initial connection time)
> tps = 44.614134 (without initial connection time)
> tps = 44.895011 (without initial connection time)
>
> (Patched is ~0.61% faster here)
>
> So, there appears to be no performance regression due to the extra
> indirection. There's maybe even some gains due to the smaller step
> size.

I didn't see that comment when working on this (it's quite a long
unioned struct; I concur on adding an assert to catch it).

This patch looks very reasonable to me though.

James Coleman



Andres Freund <andres@anarazel.de> writes:
> The remaining difference looks like it's largely caused by the
> enable_timeout_after(IDLE_STATS_UPDATE_TIMEOUT, ...) introduced as part of the
> pgstats patch. It's only really visible when I pin a single connection pgbench
> to the same CPU core as the server (which gives a ~16% boost here).

> It's not the timeout itself - that we amortize nicely (via 09cf1d522). It's
> that enable_timeout_after() does a GetCurrentTimestamp().

> Not sure yet what the best way to fix that is.

Maybe not queue a new timeout if the old one is still active?

BTW, it looks like that patch also falsified this comment
(postgres.c:4478):

         * At most one of these timeouts will be active, so there's no need to
         * worry about combining the timeout.c calls into one.

Maybe fixing that end of things would be a simpler way of buying back
the delta.

> Or we could add a timeout.c API that specifies the timeout?

Don't think that will help: it'd be morally equivalent to
enable_timeout_at(), which also has to do GetCurrentTimestamp().

            regards, tom lane



I wrote:
> Andres Freund <andres@anarazel.de> writes:
>> Or we could add a timeout.c API that specifies the timeout?

> Don't think that will help: it'd be morally equivalent to
> enable_timeout_at(), which also has to do GetCurrentTimestamp().

BTW, if we were willing to drop get_timeout_start_time(), it might
be possible to avoid doing GetCurrentTimestamp() in enable_timeout_at,
in the common case where the specified timestamp is beyond signal_due_at
so that no setitimer call is needed.  But getting the race conditions
right could be tricky.  On the whole this doesn't sound like something
to tackle post-beta.

            regards, tom lane



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-17 14:14:54 +1200, David Rowley wrote:
> I've put together the attached patch which removes 4 fields from the
> hashedscalararrayop portion of the struct which, once the JSON part is
> fixed, will put sizeof(ExprEvalStep) back down to 64 bytes again.

> The attached patch causes some extra pointer dereferencing to perform
> a hashed saop step, so I tested the performance on f4fb45d15 (prior to
> the JSON patch that pushed the sizeof(ExprEvalStep) up further. I
> found:

What do you think about the approach prototyped in my patch to move the hash
FunctionCallInfo into the element_tab? With a tiny bit more work that should
reduce the amount of dereferincing over the state today, while also keeping
below the limit?

> setup:
> create table a (a int);
> insert into a select x from generate_series(1000000,2000000) x;
> 
> bench.sql
> select * from a where a in(1,2,3,4,5,6,7,8,9,10);
> 
> f4fb45d15 + reduce_sizeof_hashedsaop_ExprEvalStep.patch
> drowley@amd3990x:~$ pgbench -n -f bench.sql -T 60 -M prepared postgres
> tps = 44.841851 (without initial connection time)
> tps = 44.986472 (without initial connection time)
> tps = 44.944315 (without initial connection time)
> 
> f4fb45d15
> drowley@amd3990x:~$ pgbench -n -f bench.sql -T 60 -M prepared postgres
> tps = 44.446127 (without initial connection time)
> tps = 44.614134 (without initial connection time)
> tps = 44.895011 (without initial connection time)
> 
> (Patched is ~0.61% faster here)
> 
> So, there appears to be no performance regression due to the extra
> indirection. There's maybe even some gains due to the smaller step
> size.

"smaller step size"?

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-17 10:33:08 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > The remaining difference looks like it's largely caused by the
> > enable_timeout_after(IDLE_STATS_UPDATE_TIMEOUT, ...) introduced as part of the
> > pgstats patch. It's only really visible when I pin a single connection pgbench
> > to the same CPU core as the server (which gives a ~16% boost here).
> 
> > It's not the timeout itself - that we amortize nicely (via 09cf1d522). It's
> > that enable_timeout_after() does a GetCurrentTimestamp().
> 
> > Not sure yet what the best way to fix that is.
> 
> Maybe not queue a new timeout if the old one is still active?

Right now we disable the timer after ReadCommand(). We can of course change
that. At first I thought we might need more bookkeeping to do so, to avoid
ProcessInterrupts() triggering pgstat_report_stat() when the timer fires
later, but we probably can jury-rig something with DoingCommandRead &&
IsTransactionOrTransactionBlock() or such.

I guess one advantage of something like this could be that we could possibly
move the arming of the timeout to pgstat.c. But that looks like it might be
more complicated than really worth it.


> BTW, it looks like that patch also falsified this comment
> (postgres.c:4478):
> 
>          * At most one of these timeouts will be active, so there's no need to
>          * worry about combining the timeout.c calls into one.

Hm, yea. I guess we can just disable them at once.


> Maybe fixing that end of things would be a simpler way of buying back
> the delta.

I don't think that'll do the trick - in the case I'm looking at none of the
other timers are active...


> > Or we could add a timeout.c API that specifies the timeout?
> 
> Don't think that will help: it'd be morally equivalent to
> enable_timeout_at(), which also has to do GetCurrentTimestamp().

I should have been more precise - what I meant was a timeout.c API that allows
the caller to pass in "now", which in this case we'd get from
GetCurrentTransactionStopTimestamp(), which would avoid the additional
timestamp computation.

Greetings,

Andres Freund



Andres Freund <andres@anarazel.de> writes:
> I should have been more precise - what I meant was a timeout.c API that allows
> the caller to pass in "now", which in this case we'd get from
> GetCurrentTransactionStopTimestamp(), which would avoid the additional
> timestamp computation.

I don't care for that one bit: it makes the accuracy of all timeouts
dependent on how careful that caller is to provide an up-to-date "now".
In the example at hand, there is WAY too much code between
SetCurrentTransactionStopTimestamp() and the timer arming to make me
think the results will be acceptable.

            regards, tom lane



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-17 13:43:49 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > I should have been more precise - what I meant was a timeout.c API that allows
> > the caller to pass in "now", which in this case we'd get from
> > GetCurrentTransactionStopTimestamp(), which would avoid the additional
> > timestamp computation.
> 
> I don't care for that one bit: it makes the accuracy of all timeouts
> dependent on how careful that caller is to provide an up-to-date "now".

I don't think it'd necessarily have to influence the accuracy of all timeouts
- but I've not looked at timeout.c much before. From what I understand we use
'now' for two things: First, to set ->start_time in enable_timeout() and
second to schedule the alarm in schedule_alarm(). An inaccurate start_time
won't cause problems for other timers afaics and it looks to me that it
wouldn't be too hard to only require an accurate 'now' if the new timeout is
nearest_timeout and now + nearest_timeout < signal_due_at?

It's probably to complicated to tinker with now tho.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-17 10:30:55 -0700, Andres Freund wrote:
> On 2022-06-17 10:33:08 -0400, Tom Lane wrote:
> > Andres Freund <andres@anarazel.de> writes:
> > > The remaining difference looks like it's largely caused by the
> > > enable_timeout_after(IDLE_STATS_UPDATE_TIMEOUT, ...) introduced as part of the
> > > pgstats patch. It's only really visible when I pin a single connection pgbench
> > > to the same CPU core as the server (which gives a ~16% boost here).
> > 
> > > It's not the timeout itself - that we amortize nicely (via 09cf1d522). It's
> > > that enable_timeout_after() does a GetCurrentTimestamp().
> > 
> > > Not sure yet what the best way to fix that is.
> > 
> > Maybe not queue a new timeout if the old one is still active?
> 
> Right now we disable the timer after ReadCommand(). We can of course change
> that. At first I thought we might need more bookkeeping to do so, to avoid
> ProcessInterrupts() triggering pgstat_report_stat() when the timer fires
> later, but we probably can jury-rig something with DoingCommandRead &&
> IsTransactionOrTransactionBlock() or such.

Here's a patch for that.

One thing I noticed is that disable_timeout() calls do
schedule_alarm(GetCurrentTimestamp()) if there's any other active timeout,
even if the to-be-disabled timer is already disabled. Of course callers of
disable_timeout() can guard against that using get_timeout_active(), but that
spreads repetitive code around...

I opted to add a fastpath for that, instead of using
get_timeout_active(). Afaics that's safe to do without disarming the signal
handler, but I'd welcome a look from somebody that knows this code.


> I guess one advantage of something like this could be that we could possibly
> move the arming of the timeout to pgstat.c. But that looks like it might be
> more complicated than really worth it.

I didn't do that yet, but am curious whether others think this would be
preferrable.


> > BTW, it looks like that patch also falsified this comment
> > (postgres.c:4478):
> > 
> >          * At most one of these timeouts will be active, so there's no need to
> >          * worry about combining the timeout.c calls into one.
> 
> Hm, yea. I guess we can just disable them at once.

With the proposed change we don't need to change the separate timeout.c to
one, or update the comment, as it should now look the same as 14.


I also attached my heavily-WIP patches for the ExprEvalStep issues, I
accidentally had only included a small part of the contents of the json fix.

Greetings,

Andres Freund

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-06-17 Fr 16:06, Andres Freund wrote:
>
>
> I also attached my heavily-WIP patches for the ExprEvalStep issues, 


Many thanks


> I
> accidentally had only included a small part of the contents of the json fix.
>

Yeah, that confused me mightily last week :-)

I and a couple of colleagues have looked it over. As far as it goes the
json fix looks kosher to me. I'll play with it some more.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-21 17:11:33 -0400, Andrew Dunstan wrote:
> I and a couple of colleagues have looked it over. As far as it goes the
> json fix looks kosher to me. I'll play with it some more.

Cool.

Any chance you could look at fixing the "structure" of the generated
expression "program". The recursive ExecEvalExpr() calls are really not ok...

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-06-21 Tu 17:25, Andres Freund wrote:
> Hi,
>
> On 2022-06-21 17:11:33 -0400, Andrew Dunstan wrote:
>> I and a couple of colleagues have looked it over. As far as it goes the
>> json fix looks kosher to me. I'll play with it some more.
> Cool.
>
> Any chance you could look at fixing the "structure" of the generated
> expression "program". The recursive ExecEvalExpr() calls are really not ok...
>

Yes, but I don't guarantee to have a fix in time for Beta2.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Michael Paquier
Date:
On Tue, Jun 21, 2022 at 05:41:07PM -0400, Andrew Dunstan wrote:
> On 2022-06-21 Tu 17:25, Andres Freund wrote:
>> On 2022-06-21 17:11:33 -0400, Andrew Dunstan wrote:
>>> I and a couple of colleagues have looked it over. As far as it goes the
>>> json fix looks kosher to me. I'll play with it some more.
>>
>> Cool.
>>
>> Any chance you could look at fixing the "structure" of the generated
>> expression "program". The recursive ExecEvalExpr() calls are really not ok...

By how much does the size of ExprEvalStep go down once you don't
inline the JSON structures as of 0004 in [1]?  And what of 0003?  The
JSON portions seem like the largest portion of the cake, though both
are must-fixes.

> Yes, but I don't guarantee to have a fix in time for Beta2.

IMHO, it would be nice to get something done for beta2.  Now the
thread is rather fresh and I guess that more performance study is
required even for 0004, so..  Waiting for beta3 would a better move at
this stage.  Is somebody confident enough in the patches proposed?
0004 looks rather sane, seen from here, at least.

[1]: https://www.postgresql.org/message-id/20220617200605.3moq7dtxua5cxemv@alap3.anarazel.de
--
Michael

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-23 16:38:12 +0900, Michael Paquier wrote:
> On Tue, Jun 21, 2022 at 05:41:07PM -0400, Andrew Dunstan wrote:
> > On 2022-06-21 Tu 17:25, Andres Freund wrote:
> >> On 2022-06-21 17:11:33 -0400, Andrew Dunstan wrote:
> >>> I and a couple of colleagues have looked it over. As far as it goes the
> >>> json fix looks kosher to me. I'll play with it some more.
> >>
> >> Cool.
> >>
> >> Any chance you could look at fixing the "structure" of the generated
> >> expression "program". The recursive ExecEvalExpr() calls are really not ok...
>
> By how much does the size of ExprEvalStep go down once you don't
> inline the JSON structures as of 0004 in [1]?  And what of 0003?

0004 gets us back to 64 bytes, if 0003 is applied first. 0003 alone doesn't
yield a size reduction, because obviously 0004 is the bigger problem. Applying
just 0004 you end up with 88 bytes.


> The JSON portions seem like the largest portion of the cake, though both are
> must-fixes.

Yep.


> > Yes, but I don't guarantee to have a fix in time for Beta2.
>
> IMHO, it would be nice to get something done for beta2.  Now the
> thread is rather fresh and I guess that more performance study is
> required even for 0004, so..

I don't think there's a whole lot of performance study needed for 0004 - the
current state is obviously wrong.

I think Andrew's beta 2 comment was more about my other architectural
complains around the json expression eval stuff.


> Waiting for beta3 would a better move at this stage.  Is somebody confident
> enough in the patches proposed?

0001 is the one that needs to most careful analysis, I think. 0002 I'd be fine
with pushing after reviewing it again. For 0003 David's approach might be
better or worse, it doesn't matter much I think. 0004 is ok I think, perhaps
with the exception of quibbling over some naming decisions?


Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-06-23 Th 21:51, Andres Freund wrote:
> Hi,
>
> On 2022-06-23 16:38:12 +0900, Michael Paquier wrote:
>> On Tue, Jun 21, 2022 at 05:41:07PM -0400, Andrew Dunstan wrote:
>>> On 2022-06-21 Tu 17:25, Andres Freund wrote:
>>>> On 2022-06-21 17:11:33 -0400, Andrew Dunstan wrote:
>>>>> I and a couple of colleagues have looked it over. As far as it goes the
>>>>> json fix looks kosher to me. I'll play with it some more.
>>>> Cool.
>>>>
>>>> Any chance you could look at fixing the "structure" of the generated
>>>> expression "program". The recursive ExecEvalExpr() calls are really not ok...
>> By how much does the size of ExprEvalStep go down once you don't
>> inline the JSON structures as of 0004 in [1]?  And what of 0003?
> 0004 gets us back to 64 bytes, if 0003 is applied first. 0003 alone doesn't
> yield a size reduction, because obviously 0004 is the bigger problem. Applying
> just 0004 you end up with 88 bytes.
>
>
>> The JSON portions seem like the largest portion of the cake, though both are
>> must-fixes.
> Yep.
>
>
>>> Yes, but I don't guarantee to have a fix in time for Beta2.
>> IMHO, it would be nice to get something done for beta2.  Now the
>> thread is rather fresh and I guess that more performance study is
>> required even for 0004, so..
> I don't think there's a whole lot of performance study needed for 0004 - the
> current state is obviously wrong.
>
> I think Andrew's beta 2 comment was more about my other architectural
> complains around the json expression eval stuff.


Right. That's being worked on but it's not going to be a mechanical fix.


>
>
>> Waiting for beta3 would a better move at this stage.  Is somebody confident
>> enough in the patches proposed?
> 0001 is the one that needs to most careful analysis, I think. 0002 I'd be fine
> with pushing after reviewing it again. For 0003 David's approach might be
> better or worse, it doesn't matter much I think. 0004 is ok I think, perhaps
> with the exception of quibbling over some naming decisions?
>
>

The attached very small patch applies on top of your 0002 and deals with
the FmgrInfo complaint.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
David Rowley
Date:
On Sat, 18 Jun 2022 at 05:21, Andres Freund <andres@anarazel.de> wrote:
>
> On 2022-06-17 14:14:54 +1200, David Rowley wrote:
> > So, there appears to be no performance regression due to the extra
> > indirection. There's maybe even some gains due to the smaller step
> > size.
>
> "smaller step size"?

I mean smaller sizeof(ExprEvalStep).

David



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
David Rowley
Date:
On Sat, 18 Jun 2022 at 08:06, Andres Freund <andres@anarazel.de> wrote:
> I also attached my heavily-WIP patches for the ExprEvalStep issues, I
> accidentally had only included a small part of the contents of the json fix.

I've now looked at the 0003 patch.  I like the idea you have about
moving some of the additional fields into ScalarArrayOpExprHashTable.
I think the patch can even go a little further and move the hash_finfo
into there too. This means we don't need to dereference the "op" in
saop_element_hash().

To make this work, I did need to tag the ScalarArrayOpExpr into the
ExprEvalStep.  That's required now since some of the initialization of
the hash function fields is delayed until
ExecEvalHashedScalarArrayOp().  We need to know the
ScalarArrayOpExpr's hashfuncid and inputcollid.

Your v2 patch did shift off some of this initialization work to
ExecEvalHashedScalarArrayOp().  The attached v3 takes that a bit
further.  This saves a bit more work for ScalarArrayOpExprs that are
evaluated 0 times.

Another small thing which I considered doing was to put the
hash_fcinfo_data field as the final field in
ScalarArrayOpExprHashTable so that we could allocate the memory for
the hash_fcinfo_data in the same allocation as the
ScalarArrayOpExprHashTable.  This would reduce the pointer
dereferencing done in saop_element_hash() a bit further.  I just
didn't notice anywhere else where we do that for FunctionCallInfo, so
I resisted doing this.

(There was also a small bug in your patch where you mistakenly cast to
an OpExpr instead of ScalarArrayOpExpr when you were fetching the
inputcollid)

David

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-24 10:29:06 -0400, Andrew Dunstan wrote:
> On 2022-06-23 Th 21:51, Andres Freund wrote:
> > On 2022-06-23 16:38:12 +0900, Michael Paquier wrote:
> >> On Tue, Jun 21, 2022 at 05:41:07PM -0400, Andrew Dunstan wrote:
> >>> Yes, but I don't guarantee to have a fix in time for Beta2.
> >> IMHO, it would be nice to get something done for beta2.  Now the
> >> thread is rather fresh and I guess that more performance study is
> >> required even for 0004, so..
> > I don't think there's a whole lot of performance study needed for 0004 - the
> > current state is obviously wrong.
> >
> > I think Andrew's beta 2 comment was more about my other architectural
> > complains around the json expression eval stuff.
> 
> 
> Right. That's being worked on but it's not going to be a mechanical fix.

Any updates here?

I'd mentioned the significant space use due to all JsonCoercionsState for all
the types. Another related aspect is that this code is just weird - the same
struct name (JsonCoercionsState), nested in each other?

    struct JsonCoercionsState
    {
        struct JsonCoercionState
        {
            JsonCoercion *coercion; /* coercion expression */
            ExprState  *estate; /* coercion expression state */
        }           null,
                    string,
        numeric    ,
                    boolean,
                    date,
                    time,
                    timetz,
                    timestamp,
                    timestamptz,
                    composite;
    }           coercions;      /* states for coercion from SQL/JSON item
                                 * types directly to the output type */

Also note the weird numeric indentation that pgindent does...


> The attached very small patch applies on top of your 0002 and deals with
> the FmgrInfo complaint.

Now that the FmgrInfo is part of a separately allocated struct, that doesn't
seem necessary anymore.

- Andres



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-07-05 Tu 14:36, Andres Freund wrote:
> Hi,
>
> On 2022-06-24 10:29:06 -0400, Andrew Dunstan wrote:
>> On 2022-06-23 Th 21:51, Andres Freund wrote:
>>> On 2022-06-23 16:38:12 +0900, Michael Paquier wrote:
>>>> On Tue, Jun 21, 2022 at 05:41:07PM -0400, Andrew Dunstan wrote:
>>>>> Yes, but I don't guarantee to have a fix in time for Beta2.
>>>> IMHO, it would be nice to get something done for beta2.  Now the
>>>> thread is rather fresh and I guess that more performance study is
>>>> required even for 0004, so..
>>> I don't think there's a whole lot of performance study needed for 0004 - the
>>> current state is obviously wrong.
>>>
>>> I think Andrew's beta 2 comment was more about my other architectural
>>> complains around the json expression eval stuff.
>>
>> Right. That's being worked on but it's not going to be a mechanical fix.
> Any updates here?


Not yet. A colleague and I are working on it. I'll post a status this
week if we can't post a fix.


>
> I'd mentioned the significant space use due to all JsonCoercionsState for all
> the types. Another related aspect is that this code is just weird - the same
> struct name (JsonCoercionsState), nested in each other?
>
>     struct JsonCoercionsState
>     {
>         struct JsonCoercionState
>         {
>             JsonCoercion *coercion; /* coercion expression */
>             ExprState  *estate; /* coercion expression state */
>         }           null,
>                     string,
>         numeric    ,
>                     boolean,
>                     date,
>                     time,
>                     timetz,
>                     timestamp,
>                     timestamptz,
>                     composite;
>     }           coercions;      /* states for coercion from SQL/JSON item
>                                  * types directly to the output type */
>
> Also note the weird numeric indentation that pgindent does...


Yeah, we'll try to fix that.


>
>
>> The attached very small patch applies on top of your 0002 and deals with
>> the FmgrInfo complaint.
> Now that the FmgrInfo is part of a separately allocated struct, that doesn't
> seem necessary anymore.


Right, but you complained that we should do it the same way as it's done
elsewhere, so I thought I'd do that anyway.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-23 18:51:45 -0700, Andres Freund wrote:
> > Waiting for beta3 would a better move at this stage.  Is somebody confident
> > enough in the patches proposed?
> 
> 0001 is the one that needs to most careful analysis, I think. 0002 I'd be fine
> with pushing after reviewing it again. For 0003 David's approach might be
> better or worse, it doesn't matter much I think. 0004 is ok I think, perhaps
> with the exception of quibbling over some naming decisions?

I don't quite feel comfortable with 0001, without review by others. So my
current plan is to drop it and use get_timeout_active() "manually". We can
improve this in HEAD to remove the redundancy.

I've pushed what was 0004, will push what was 0002 with the above change in a
short while unless somebody protests PDQ. Then will look at David's edition of
my 0003.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-06-29 11:40:45 +1200, David Rowley wrote:
> On Sat, 18 Jun 2022 at 08:06, Andres Freund <andres@anarazel.de> wrote:
> > I also attached my heavily-WIP patches for the ExprEvalStep issues, I
> > accidentally had only included a small part of the contents of the json fix.
> 
> I've now looked at the 0003 patch.  I like the idea you have about
> moving some of the additional fields into ScalarArrayOpExprHashTable.
> I think the patch can even go a little further and move the hash_finfo
> into there too. This means we don't need to dereference the "op" in
> saop_element_hash().

Makes sense.


> To make this work, I did need to tag the ScalarArrayOpExpr into the
> ExprEvalStep.  That's required now since some of the initialization of
> the hash function fields is delayed until
> ExecEvalHashedScalarArrayOp().  We need to know the
> ScalarArrayOpExpr's hashfuncid and inputcollid.

Makes sense.


> Another small thing which I considered doing was to put the
> hash_fcinfo_data field as the final field in
> ScalarArrayOpExprHashTable so that we could allocate the memory for
> the hash_fcinfo_data in the same allocation as the
> ScalarArrayOpExprHashTable.  This would reduce the pointer
> dereferencing done in saop_element_hash() a bit further.  I just
> didn't notice anywhere else where we do that for FunctionCallInfo, so
> I resisted doing this.

I think that'd make sense - it does add a bit of size calculation magic, but
it shouldn't be a problem. I'm fairly sure we do this in other parts of the
code.


> (There was also a small bug in your patch where you mistakenly cast to
> an OpExpr instead of ScalarArrayOpExpr when you were fetching the
> inputcollid)

Ooops.


Are you good pushing this? I'm fine with you doing so wether you adapt it
further or not.


Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
David Rowley
Date:
Thanks for looking at this.

On Wed, 6 Jul 2022 at 12:32, Andres Freund <andres@anarazel.de> wrote:
>
> On 2022-06-29 11:40:45 +1200, David Rowley wrote:
> > Another small thing which I considered doing was to put the
> > hash_fcinfo_data field as the final field in
> > ScalarArrayOpExprHashTable so that we could allocate the memory for
> > the hash_fcinfo_data in the same allocation as the
> > ScalarArrayOpExprHashTable.  This would reduce the pointer
> > dereferencing done in saop_element_hash() a bit further.  I just
> > didn't notice anywhere else where we do that for FunctionCallInfo, so
> > I resisted doing this.
>
> I think that'd make sense - it does add a bit of size calculation magic, but
> it shouldn't be a problem. I'm fairly sure we do this in other parts of the
> code.

I've now adjusted that.  I also changed the hash_finfo field to make
it so the FmgrInfo is inline rather than a pointer. This saves an
additional dereference in saop_element_hash() and also saves a
palloc().

I had to adjust the palloc for the ScalarArrayOpExprHashTable struct
into a palloc0 due to the FmgrInfo being inlined.  I considered just
zeroing out the hash_finfo portion but thought it wasn't worth the
extra code.

> Are you good pushing this? I'm fine with you doing so wether you adapt it
> further or not.

Pushed.

David



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-07-05 Tu 15:04, Andrew Dunstan wrote:
> On 2022-07-05 Tu 14:36, Andres Freund wrote:
>>
>>>> I think Andrew's beta 2 comment was more about my other architectural
>>>> complains around the json expression eval stuff.
>>> Right. That's being worked on but it's not going to be a mechanical fix.
>> Any updates here?
>
> Not yet. A colleague and I are working on it. I'll post a status this
> week if we can't post a fix.


We're still working on it. We've made substantial progress but there are
some tests failing that we need to fix.


>> I'd mentioned the significant space use due to all JsonCoercionsState for all
>> the types. Another related aspect is that this code is just weird - the same
>> struct name (JsonCoercionsState), nested in each other?
>>
>>     struct JsonCoercionsState
>>     {
>>         struct JsonCoercionState
>>         {
>>             JsonCoercion *coercion; /* coercion expression */
>>             ExprState  *estate; /* coercion expression state */
>>         }           null,
>>                     string,
>>         numeric    ,
>>                     boolean,
>>                     date,
>>                     time,
>>                     timetz,
>>                     timestamp,
>>                     timestamptz,
>>                     composite;
>>     }           coercions;      /* states for coercion from SQL/JSON item
>>                                  * types directly to the output type */
>>
>> Also note the weird numeric indentation that pgindent does...
>
> Yeah, we'll try to fix that.


Actually, it's not the same name: JsonCoercionsState vs
JsonCoercionState. But I agree that it's a subtle enough difference that
we should use something more obvious. Maybe JsonCoercionStates instead
of JsonCoercionsState? The plural at the end would be harder to miss.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-07-08 17:05:49 -0400, Andrew Dunstan wrote:
> Actually, it's not the same name: JsonCoercionsState vs
> JsonCoercionState. But I agree that it's a subtle enough difference that
> we should use something more obvious. Maybe JsonCoercionStates instead
> of JsonCoercionsState? The plural at the end would be harder to miss.

Given that it's a one-off use struct, why name it? Then we don't have to
figure out a name we never use.

I also still would like to understand why we need pre-allocated space for all
these types. How could multiple datums be coerced in an interleaved manner?
And if that's possible, why can't multiple datums of the same type be coerced
at the same time?

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-07-08 17:05:49 -0400, Andrew Dunstan wrote:
> On 2022-07-05 Tu 15:04, Andrew Dunstan wrote:
> > On 2022-07-05 Tu 14:36, Andres Freund wrote:
> >>
> >>>> I think Andrew's beta 2 comment was more about my other architectural
> >>>> complains around the json expression eval stuff.
> >>> Right. That's being worked on but it's not going to be a mechanical fix.
> >> Any updates here?
> >
> > Not yet. A colleague and I are working on it. I'll post a status this
> > week if we can't post a fix.

> We're still working on it. We've made substantial progress but there are
> some tests failing that we need to fix.

I think we need to resolve this soon - or consider the alternatives. A lot of
the new json stuff doesn't seem fully baked, so I'm starting to wonder if we
have to consider pushing it a release further down.

Perhaps you could post your current state? I might be able to help resolving
some of the problems.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-07-15 Fr 17:07, Andres Freund wrote:
> Hi,
>
> On 2022-07-08 17:05:49 -0400, Andrew Dunstan wrote:
>> On 2022-07-05 Tu 15:04, Andrew Dunstan wrote:
>>> On 2022-07-05 Tu 14:36, Andres Freund wrote:
>>>>>> I think Andrew's beta 2 comment was more about my other architectural
>>>>>> complains around the json expression eval stuff.
>>>>> Right. That's being worked on but it's not going to be a mechanical fix.
>>>> Any updates here?
>>> Not yet. A colleague and I are working on it. I'll post a status this
>>> week if we can't post a fix.
>> We're still working on it. We've made substantial progress but there are
>> some tests failing that we need to fix.
> I think we need to resolve this soon - or consider the alternatives. A lot of
> the new json stuff doesn't seem fully baked, so I'm starting to wonder if we
> have to consider pushing it a release further down.
>
> Perhaps you could post your current state? I might be able to help resolving
> some of the problems.


Ok. Here is the state of things. This has proved to be rather more
intractable than I expected. Almost all the legwork here has been done
by Amit Langote, for which he deserves both my thanks and considerable
credit, but I take responsibility for it.

I just discovered today that this scheme is failing under
"force_parallel_mode = regress". I have as yet no idea if that can be
fixed simply or not. Apart from that I think the main outstanding issue
is to fill in the gaps in llvm_compile_expr().

If you have help you can offer that would be very welcome.

I'd still very much like to get this done, but if the decision is we've
run out of time I'll be sad but understand.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
Hi,

On Tue, Jul 19, 2022 at 4:09 AM Andrew Dunstan <andrew@dunslane.net> wrote:
> On 2022-07-15 Fr 17:07, Andres Freund wrote:
> > Perhaps you could post your current state? I might be able to help resolving
> > some of the problems.
>
> Ok. Here is the state of things. This has proved to be rather more
> intractable than I expected. Almost all the legwork here has been done
> by Amit Langote, for which he deserves both my thanks and considerable
> credit, but I take responsibility for it.
>
> I just discovered today that this scheme is failing under
> "force_parallel_mode = regress". I have as yet no idea if that can be
> fixed simply or not.

The errors Andrew mentions here had to do with a bug of the new
coercion evaluation logic.  The old code in ExecEvalJsonExpr() would
skip coercion evaluation and thus also the sub-transaction associated
with it for some JsonExprs that the new code would not and that didn't
sit well with the invariant that a parallel worker shouldn't try to
start a sub-transaction.

That bug has been fixed in the attached updated version.

> Apart from that I think the main outstanding issue
> is to fill in the gaps in llvm_compile_expr().

About that, I was wondering if the blocks in llvm_compile_expr() need
to be hand-coded to match what's added in ExecInterpExpr() or if I've
missed some tool that can be used instead?

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-07-19 20:40:11 +0900, Amit Langote wrote:
> About that, I was wondering if the blocks in llvm_compile_expr() need
> to be hand-coded to match what's added in ExecInterpExpr() or if I've
> missed some tool that can be used instead?

The easiest way is to just call an external function for the implementation of
the step. But yes, otherwise you need to handcraft it.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Wed, Jul 20, 2022 at 12:37 AM Andres Freund <andres@anarazel.de> wrote:
> On 2022-07-19 20:40:11 +0900, Amit Langote wrote:
> > About that, I was wondering if the blocks in llvm_compile_expr() need
> > to be hand-coded to match what's added in ExecInterpExpr() or if I've
> > missed some tool that can be used instead?
>
> The easiest way is to just call an external function for the implementation of
> the step. But yes, otherwise you need to handcraft it.

Ok, thanks.

So I started updating llvm_compile_expr() for handling the new
ExprEvalSteps that the patch adds to ExecExprInterp(), but quickly
realized that code could have been consolidated into less code, or
IOW, into fewer new ExprEvalSteps.  So, I refactored things that way
and am now retrying adding the code to llvm_compile_expr() based on
new, better consolidated, code.

Here's the updated version, without the llvm pieces, in case you'd
like to look at it even in this state.  I'll post a version with llvm
pieces filled in tomorrow.   (I have merged the different patches into
one for convenience.)

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Wed, Jul 20, 2022 at 11:09 PM Amit Langote <amitlangote09@gmail.com> wrote:
> On Wed, Jul 20, 2022 at 12:37 AM Andres Freund <andres@anarazel.de> wrote:
> > On 2022-07-19 20:40:11 +0900, Amit Langote wrote:
> > > About that, I was wondering if the blocks in llvm_compile_expr() need
> > > to be hand-coded to match what's added in ExecInterpExpr() or if I've
> > > missed some tool that can be used instead?
> >
> > The easiest way is to just call an external function for the implementation of
> > the step. But yes, otherwise you need to handcraft it.
>
> Ok, thanks.
>
> So I started updating llvm_compile_expr() for handling the new
> ExprEvalSteps that the patch adds to ExecExprInterp(), but quickly
> realized that code could have been consolidated into less code, or
> IOW, into fewer new ExprEvalSteps.  So, I refactored things that way
> and am now retrying adding the code to llvm_compile_expr() based on
> new, better consolidated, code.
>
> Here's the updated version, without the llvm pieces, in case you'd
> like to look at it even in this state.  I'll post a version with llvm
> pieces filled in tomorrow.   (I have merged the different patches into
> one for convenience.)

And here's a version with llvm pieces filled in.

Because I wrote all of it while not really understanding how the LLVM
constructs like blocks and branches work, the only reason I think
those llvm_compile_expr() additions may be correct is that all the
tests in jsonb_sqljson.sql pass even if I add the following line at
the top:

set jit_above_cost to 0;

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Thu, Jul 21, 2022 at 11:55 PM Amit Langote <amitlangote09@gmail.com> wrote:
> On Wed, Jul 20, 2022 at 11:09 PM Amit Langote <amitlangote09@gmail.com> wrote:
> > On Wed, Jul 20, 2022 at 12:37 AM Andres Freund <andres@anarazel.de> wrote:
> > > On 2022-07-19 20:40:11 +0900, Amit Langote wrote:
> > > > About that, I was wondering if the blocks in llvm_compile_expr() need
> > > > to be hand-coded to match what's added in ExecInterpExpr() or if I've
> > > > missed some tool that can be used instead?
> > >
> > > The easiest way is to just call an external function for the implementation of
> > > the step. But yes, otherwise you need to handcraft it.
> >
> > Ok, thanks.
> >
> > So I started updating llvm_compile_expr() for handling the new
> > ExprEvalSteps that the patch adds to ExecExprInterp(), but quickly
> > realized that code could have been consolidated into less code, or
> > IOW, into fewer new ExprEvalSteps.  So, I refactored things that way
> > and am now retrying adding the code to llvm_compile_expr() based on
> > new, better consolidated, code.
> >
> > Here's the updated version, without the llvm pieces, in case you'd
> > like to look at it even in this state.  I'll post a version with llvm
> > pieces filled in tomorrow.   (I have merged the different patches into
> > one for convenience.)
>
> And here's a version with llvm pieces filled in.
>
> Because I wrote all of it while not really understanding how the LLVM
> constructs like blocks and branches work, the only reason I think
> those llvm_compile_expr() additions may be correct is that all the
> tests in jsonb_sqljson.sql pass even if I add the following line at
> the top:
>
> set jit_above_cost to 0;

Oh and I did build --with-llvm. :-)


--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com
--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Alvaro Herrera
Date:
On 2022-Jul-21, Amit Langote wrote:

> Because I wrote all of it while not really understanding how the LLVM
> constructs like blocks and branches work, the only reason I think
> those llvm_compile_expr() additions may be correct is that all the
> tests in jsonb_sqljson.sql pass even if I add the following line at
> the top:

I suggest to build with --enable-coverage, then run the regression tests
and do "make coverage-html" and see if your code appears covered in the
report.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"¿Cómo puedes confiar en algo que pagas y que no ves,
y no confiar en algo que te dan y te lo muestran?" (Germán Poo)



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Fri, Jul 22, 2022 at 2:12 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
> On 2022-Jul-21, Amit Langote wrote:
>
> > Because I wrote all of it while not really understanding how the LLVM
> > constructs like blocks and branches work, the only reason I think
> > those llvm_compile_expr() additions may be correct is that all the
> > tests in jsonb_sqljson.sql pass even if I add the following line at
> > the top:
>
> I suggest to build with --enable-coverage, then run the regression tests
> and do "make coverage-html" and see if your code appears covered in the
> report.

Thanks for the suggestion.  I just did and it seems that both the
additions to ExecInterpExpr() and to llvm_compile_expr() are well
covered.

BTW, the only way I found to *forcefully* exercise llvm_compile_expr()
is to add `set jit_above_cost to 0` at the top of the test file, or
are we missing a force_jit_mode, like there is force_parallel_mode?

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
David Rowley
Date:
On Fri, 22 Jul 2022 at 15:22, Amit Langote <amitlangote09@gmail.com> wrote:
> BTW, the only way I found to *forcefully* exercise llvm_compile_expr()
> is to add `set jit_above_cost to 0` at the top of the test file, or
> are we missing a force_jit_mode, like there is force_parallel_mode?

I don't think we'd need any setting to hide the JIT counters from
EXPLAIN ANALYZE since those only show with COSTS ON, which we tend not
to do.

I think for testing, you could just zero all the jit*above_cost GUCs.

If you look at the config_extra in [1], you'll see that animal runs
the tests with modified JIT parameters.

BTW, I was working on code inside llvm_compile_expr() a few days ago
and I thought I'd gotten the new evaluation steps I was adding correct
as it worked fine with jit_above_cost=0, but on further testing, it
crashed with jit_inline_above_cost=0. Might be worth doing both to see
if everything works as intended.

David

[1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=desmoxytes&dt=2022-07-22%2003%3A04%3A03



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Fri, Jul 22, 2022 at 1:13 PM David Rowley <dgrowleyml@gmail.com> wrote:
> On Fri, 22 Jul 2022 at 15:22, Amit Langote <amitlangote09@gmail.com> wrote:
> > BTW, the only way I found to *forcefully* exercise llvm_compile_expr()
> > is to add `set jit_above_cost to 0` at the top of the test file, or
> > are we missing a force_jit_mode, like there is force_parallel_mode?
>
> I don't think we'd need any setting to hide the JIT counters from
> EXPLAIN ANALYZE since those only show with COSTS ON, which we tend not
> to do.

Ah, makes sense.

> I think for testing, you could just zero all the jit*above_cost GUCs.
>
> If you look at the config_extra in [1], you'll see that animal runs
> the tests with modified JIT parameters.
>
> BTW, I was working on code inside llvm_compile_expr() a few days ago
> and I thought I'd gotten the new evaluation steps I was adding correct
> as it worked fine with jit_above_cost=0, but on further testing, it
> crashed with jit_inline_above_cost=0. Might be worth doing both to see
> if everything works as intended.

Thanks for the pointer.

So I didn't see things going bust on re-testing with all
jit_*_above_cost parameters set to 0, so maybe the
llvm_compile_expression() additions are alright.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Fri, Jul 22, 2022 at 2:49 PM Amit Langote <amitlangote09@gmail.com> wrote:
> On Fri, Jul 22, 2022 at 1:13 PM David Rowley <dgrowleyml@gmail.com> wrote:
> > BTW, I was working on code inside llvm_compile_expr() a few days ago
> > and I thought I'd gotten the new evaluation steps I was adding correct
> > as it worked fine with jit_above_cost=0, but on further testing, it
> > crashed with jit_inline_above_cost=0. Might be worth doing both to see
> > if everything works as intended.
>
> Thanks for the pointer.
>
> So I didn't see things going bust on re-testing with all
> jit_*_above_cost parameters set to 0, so maybe the
> llvm_compile_expression() additions are alright.

Here's an updated version of the patch, with mostly cosmetic changes.
In particular, I added comments describing the new llvm_compile_expr()
blobs.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-07-27 We 04:01, Amit Langote wrote:
> On Fri, Jul 22, 2022 at 2:49 PM Amit Langote <amitlangote09@gmail.com> wrote:
>> On Fri, Jul 22, 2022 at 1:13 PM David Rowley <dgrowleyml@gmail.com> wrote:
>>> BTW, I was working on code inside llvm_compile_expr() a few days ago
>>> and I thought I'd gotten the new evaluation steps I was adding correct
>>> as it worked fine with jit_above_cost=0, but on further testing, it
>>> crashed with jit_inline_above_cost=0. Might be worth doing both to see
>>> if everything works as intended.
>> Thanks for the pointer.
>>
>> So I didn't see things going bust on re-testing with all
>> jit_*_above_cost parameters set to 0, so maybe the
>> llvm_compile_expression() additions are alright.
> Here's an updated version of the patch, with mostly cosmetic changes.
> In particular, I added comments describing the new llvm_compile_expr()
> blobs.
>


Andres,


this work has been done in response to a complaint from you. Does this
address your concerns satisfactorily?


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-07-29 14:27:36 -0400, Andrew Dunstan wrote:
> this work has been done in response to a complaint from you. Does this
> address your concerns satisfactorily?

Will look. Was on vacation for the last two weeks...

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-07-27 17:01:13 +0900, Amit Langote wrote:
> Here's an updated version of the patch, with mostly cosmetic changes.
> In particular, I added comments describing the new llvm_compile_expr()
> blobs.

- I've asked a couple times before: Why do we need space for every possible
  datatype at once in JsonItemCoercions? Can there be multiple "concurrent"
  coercions in process?

  The whole coercion stuff just seems incredibly clunky (in a slightly
  different shape before this patch). ExecEvalJsonExprItemCoercion() calls
  ExecPrepareJsonItemCoercion(), which gets a pointer to one of the per-type
  elements in JsonItemCoercionsState, dispatching on the type of the json
  object. Then we later call ExecGetJsonItemCoercion() (via a convoluted
  path), which again will dispatch on the type (extracting the json object
  again afaics!), to then somehow eventually get the coerced value.

  I cannot make any sense of this.  This code should not have been committed
  in this state.


- Looks like there's still some recursive expression states, namely
  JsonExprState->{result_coercion, coercions}?


- Looks like the JsonExpr code in ExecInitExprRec() is big enough to
  potentially benefit from splitting out into a separate function?


- looks like JsonExprPostEvalState could be moved to execExprInterp.c?


- I ran the patch against LLVM 14 built with assertions enabled, and it
  triggers an assertion failure:

#3  0x00007f75d165c242 in __GI___assert_fail (
    assertion=0x7f75c278d511 "getOperand(0)->getType() == getOperand(1)->getType() && \"Both operands to ICmp
instructionare not of the same type!\"",
 
    file=0x7f75c2780366 "/home/andres/src/llvm-project-14/llvm/include/llvm/IR/Instructions.h", line=1192,
    function=0x7f75c27d9dcc "void llvm::ICmpInst::AssertOK()") at assert.c:101
#4  0x00007f75c2b9b25c in llvm::ICmpInst::AssertOK (this=0x55e019290ca0) at
/home/andres/src/llvm-project-14/llvm/include/llvm/IR/Instructions.h:1191
#5  0x00007f75c2b9b0ea in llvm::ICmpInst::ICmpInst (this=0x55e019290ca0, pred=llvm::CmpInst::ICMP_EQ,
LHS=0x55e019290c10,RHS=0x55e01928ce80, NameStr="")
 
    at /home/andres/src/llvm-project-14/llvm/include/llvm/IR/Instructions.h:1246
#6  0x00007f75c2b93c99 in llvm::IRBuilderBase::CreateICmp (this=0x55e0192894f0, P=llvm::CmpInst::ICMP_EQ,
LHS=0x55e019290c10,RHS=0x55e01928ce80, Name="")
 
    at /home/andres/src/llvm-project-14/llvm/include/llvm/IR/IRBuilder.h:2202
#7  0x00007f75c2c1bc5d in LLVMBuildICmp (B=0x55e0192894f0, Op=LLVMIntEQ, LHS=0x55e019290c10, RHS=0x55e01928ce80,
Name=0x7f75d0d24cbc"")
 
    at /home/andres/src/llvm-project-14/llvm/lib/IR/Core.cpp:3927
#8  0x00007f75d0d20b1f in llvm_compile_expr (state=0x55e019201380) at
/home/andres/src/postgresql/src/backend/jit/llvm/llvmjit_expr.c:2392
...
#19 0x000055e0184c16d4 in exec_simple_query (query_string=0x55e01912f6e0 "SELECT JSON_EXISTS(NULL::jsonb, '$');") at
/home/andres/src/postgresql/src/backend/tcop/postgres.c:1204

  this triggers easily interactively - which is nice because that allows to
  dump the types:

  p getOperand(0)->getType()->dump() -> prints i64
  p getOperand(1)->getType()->dump() -> prints i32

  The immediate issue is that you're setting v_jumpaddrp up as a pointer to a
  pointer to size_t - but then compare it to i32.


  I first was confused why the code tries to load the jump target
  dynamically. But then I saw that the interpreted code sets it dynamically -
  why? That's completely unnecessary overhead afaics? There's just two
  possible jump targets, no?


- why is EvalJsonPathVar() in execExprInterp.c, when it's only ever called
  from within jsonpath_exec.c?

- s/JsobbValue/JsonbValue/


Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
Hi,

Thanks for looking into this.

On Tue, Aug 2, 2022 at 9:39 AM Andres Freund <andres@anarazel.de> wrote:
> On 2022-07-27 17:01:13 +0900, Amit Langote wrote:
> > Here's an updated version of the patch, with mostly cosmetic changes.
> > In particular, I added comments describing the new llvm_compile_expr()
> > blobs.
>
> - I've asked a couple times before: Why do we need space for every possible
>   datatype at once in JsonItemCoercions? Can there be multiple "concurrent"
>   coercions in process?

This topic has been a head-scratcher for me too from the beginning,
but I've since come to understand (convince myself) that we do need
the coercions for all possible types, because we don't know the type
of the JSON item that's going to pop out of the main JSON path
expression until we've run it through the JSON path executor that
ExecEvalJson() invokes.  So, it's not possible to statically assign
the coercion.  I am not really sure if different coercions may be used
in the same query over multiple evaluations of the same JSON path
expression, but maybe that's also possible.

>   The whole coercion stuff just seems incredibly clunky (in a slightly
>   different shape before this patch). ExecEvalJsonExprItemCoercion() calls
>   ExecPrepareJsonItemCoercion(), which gets a pointer to one of the per-type
>   elements in JsonItemCoercionsState, dispatching on the type of the json
>   object. Then we later call ExecGetJsonItemCoercion() (via a convoluted
>   path), which again will dispatch on the type (extracting the json object
>   again afaics!), to then somehow eventually get the coerced value.

I think it might be possible to make this a bit simpler, by not
leaving anything coercion-related in ExecEvalJsonExpr().  I left some
pieces there, because I thought the error of not finding an
appropriate coercion must be thrown right away as the code in
ExecEvalJsonExpr() does after calling ExecGetJsonItemCoercion().

ExecPrepareJsonItemCoercion() is called later when it's time to
actually evaluate the coercion.  If we move the error path to
ExecPrepareJsonItemCoercion(), both ExecGetJsonItemCoercion() and the
error path code in ExecEvalJsonExpr() will be unnecessary.  I will
give that a try.

> - Looks like there's still some recursive expression states, namely
>   JsonExprState->{result_coercion, coercions}?

So, the problem with inlining coercion evaluation into the main parent
JsonExpr's is that it needs to be wrapped in a sub-transaction to
catch any errors and return NULL instead.  I don't know a way to wrap
ExprEvalStep evaluation in a sub-transaction to achieve that effect.

> - Looks like the JsonExpr code in ExecInitExprRec() is big enough to
>   potentially benefit from splitting out into a separate function?

Thought about it too, so will do.

> - looks like JsonExprPostEvalState could be moved to execExprInterp.c?

OK, will give that a try.

> - I ran the patch against LLVM 14 built with assertions enabled, and it
>   triggers an assertion failure:
>
> #3  0x00007f75d165c242 in __GI___assert_fail (
>     assertion=0x7f75c278d511 "getOperand(0)->getType() == getOperand(1)->getType() && \"Both operands to ICmp
instructionare not of the same type!\"",
 
>     file=0x7f75c2780366 "/home/andres/src/llvm-project-14/llvm/include/llvm/IR/Instructions.h", line=1192,
>     function=0x7f75c27d9dcc "void llvm::ICmpInst::AssertOK()") at assert.c:101
> #4  0x00007f75c2b9b25c in llvm::ICmpInst::AssertOK (this=0x55e019290ca0) at
/home/andres/src/llvm-project-14/llvm/include/llvm/IR/Instructions.h:1191
> #5  0x00007f75c2b9b0ea in llvm::ICmpInst::ICmpInst (this=0x55e019290ca0, pred=llvm::CmpInst::ICMP_EQ,
LHS=0x55e019290c10,RHS=0x55e01928ce80, NameStr="")
 
>     at /home/andres/src/llvm-project-14/llvm/include/llvm/IR/Instructions.h:1246
> #6  0x00007f75c2b93c99 in llvm::IRBuilderBase::CreateICmp (this=0x55e0192894f0, P=llvm::CmpInst::ICMP_EQ,
LHS=0x55e019290c10,RHS=0x55e01928ce80, Name="")
 
>     at /home/andres/src/llvm-project-14/llvm/include/llvm/IR/IRBuilder.h:2202
> #7  0x00007f75c2c1bc5d in LLVMBuildICmp (B=0x55e0192894f0, Op=LLVMIntEQ, LHS=0x55e019290c10, RHS=0x55e01928ce80,
Name=0x7f75d0d24cbc"")
 
>     at /home/andres/src/llvm-project-14/llvm/lib/IR/Core.cpp:3927
> #8  0x00007f75d0d20b1f in llvm_compile_expr (state=0x55e019201380) at
/home/andres/src/postgresql/src/backend/jit/llvm/llvmjit_expr.c:2392
> ...
> #19 0x000055e0184c16d4 in exec_simple_query (query_string=0x55e01912f6e0 "SELECT JSON_EXISTS(NULL::jsonb, '$');") at
/home/andres/src/postgresql/src/backend/tcop/postgres.c:1204
>
>   this triggers easily interactively - which is nice because that allows to
>   dump the types:
>
>   p getOperand(0)->getType()->dump() -> prints i64
>   p getOperand(1)->getType()->dump() -> prints i32
>
>   The immediate issue is that you're setting v_jumpaddrp up as a pointer to a
>   pointer to size_t - but then compare it to i32.

Ooh, thanks for letting me know.  So maybe I am missing some
llvmjist_emit.h/type.c infrastructure to read an int32 value
(jumpdone) out of an int32 pointer (&jumpdone)?

>   I first was confused why the code tries to load the jump target
>   dynamically. But then I saw that the interpreted code sets it dynamically -
>   why? That's completely unnecessary overhead afaics? There's just two
>   possible jump targets, no?

Hmm, I looked at the code for other expressions that jump, especially
CASE WHEN, but they use ready-made EEOP_JUMP_IF_* steps, which can be
added statically.  I thought we can't use them in this case, because
the conditions are very ad-hoc, like if the JSON path computation
returned an "empty" item or if the "error" flag was set during that
computation, etc.

> - why is EvalJsonPathVar() in execExprInterp.c, when it's only ever called
>   from within jsonpath_exec.c?

Hadn't noticed that because the patch didn't really have to touch it,
but yes, maybe it makes sense to move it there.

> - s/JsobbValue/JsonbValue/

Oops, will fix.

--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-08-02 12:05:55 +0900, Amit Langote wrote:
> On Tue, Aug 2, 2022 at 9:39 AM Andres Freund <andres@anarazel.de> wrote:
> > On 2022-07-27 17:01:13 +0900, Amit Langote wrote:
> > > Here's an updated version of the patch, with mostly cosmetic changes.
> > > In particular, I added comments describing the new llvm_compile_expr()
> > > blobs.
> >
> > - I've asked a couple times before: Why do we need space for every possible
> >   datatype at once in JsonItemCoercions? Can there be multiple "concurrent"
> >   coercions in process?
> 
> This topic has been a head-scratcher for me too from the beginning,
> but I've since come to understand (convince myself) that we do need
> the coercions for all possible types, because we don't know the type
> of the JSON item that's going to pop out of the main JSON path
> expression until we've run it through the JSON path executor that
> ExecEvalJson() invokes.  So, it's not possible to statically assign
> the coercion.

Sure. But that doesn't mean we have to have memory for every possible type *at
the same time*.


> I am not really sure if different coercions may be used
> in the same query over multiple evaluations of the same JSON path
> expression, but maybe that's also possible.

Even if the type can change, I don't think that means we need to have space
for multiple types at the same time - there can't be multiple coercions
happening at the same time, otherwise there could be two coercions of the same
type as well. So we don't need memory for every coercion type.

> 
> >   The whole coercion stuff just seems incredibly clunky (in a slightly
> >   different shape before this patch). ExecEvalJsonExprItemCoercion() calls
> >   ExecPrepareJsonItemCoercion(), which gets a pointer to one of the per-type
> >   elements in JsonItemCoercionsState, dispatching on the type of the json
> >   object. Then we later call ExecGetJsonItemCoercion() (via a convoluted
> >   path), which again will dispatch on the type (extracting the json object
> >   again afaics!), to then somehow eventually get the coerced value.
> 
> I think it might be possible to make this a bit simpler, by not
> leaving anything coercion-related in ExecEvalJsonExpr().

Honestly, this code seems like it should just be rewritten from scratch.


> I left some pieces there, because I thought the error of not finding an
> appropriate coercion must be thrown right away as the code in
> ExecEvalJsonExpr() does after calling ExecGetJsonItemCoercion().
> 
> ExecPrepareJsonItemCoercion() is called later when it's time to
> actually evaluate the coercion.  If we move the error path to
> ExecPrepareJsonItemCoercion(), both ExecGetJsonItemCoercion() and the
> error path code in ExecEvalJsonExpr() will be unnecessary.  I will
> give that a try.

Why do we need the separation of prepare and then evaluation? They're executed
straight after each other?


> > - Looks like there's still some recursive expression states, namely
> >   JsonExprState->{result_coercion, coercions}?
> 
> So, the problem with inlining coercion evaluation into the main parent
> JsonExpr's is that it needs to be wrapped in a sub-transaction to
> catch any errors and return NULL instead.  I don't know a way to wrap
> ExprEvalStep evaluation in a sub-transaction to achieve that effect.

But we don't need to wrap arbitrary evaluation in a subtransaction - afaics
the coercion calls a single function, not an arbitrary expression?


> Ooh, thanks for letting me know.  So maybe I am missing some
> llvmjist_emit.h/type.c infrastructure to read an int32 value
> (jumpdone) out of an int32 pointer (&jumpdone)?

No, you just need to replace l_ptr(TypeSizeT) with l_ptr(LLVMInt32Type()).


> >   I first was confused why the code tries to load the jump target
> >   dynamically. But then I saw that the interpreted code sets it dynamically -
> >   why? That's completely unnecessary overhead afaics? There's just two
> >   possible jump targets, no?
> 
> Hmm, I looked at the code for other expressions that jump, especially
> CASE WHEN, but they use ready-made EEOP_JUMP_IF_* steps, which can be
> added statically.  I thought we can't use them in this case, because
> the conditions are very ad-hoc, like if the JSON path computation
> returned an "empty" item or if the "error" flag was set during that
> computation, etc.

The minimal fix would be to return the jump target from the function, and then
jump to that. That at least avoids the roundtrip to memory you have right now.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
Hi,

On Wed, Aug 3, 2022 at 12:00 AM Andres Freund <andres@anarazel.de> wrote:
> On 2022-08-02 12:05:55 +0900, Amit Langote wrote:
> > On Tue, Aug 2, 2022 at 9:39 AM Andres Freund <andres@anarazel.de> wrote:
> > > On 2022-07-27 17:01:13 +0900, Amit Langote wrote:
> > > > Here's an updated version of the patch, with mostly cosmetic changes.
> > > > In particular, I added comments describing the new llvm_compile_expr()
> > > > blobs.
> > >
> > > - I've asked a couple times before: Why do we need space for every possible
> > >   datatype at once in JsonItemCoercions? Can there be multiple "concurrent"
> > >   coercions in process?
> >
> > This topic has been a head-scratcher for me too from the beginning,
> > but I've since come to understand (convince myself) that we do need
> > the coercions for all possible types, because we don't know the type
> > of the JSON item that's going to pop out of the main JSON path
> > expression until we've run it through the JSON path executor that
> > ExecEvalJson() invokes.  So, it's not possible to statically assign
> > the coercion.
>
> Sure. But that doesn't mean we have to have memory for every possible type *at
> the same time*.
>
> > I am not really sure if different coercions may be used
> > in the same query over multiple evaluations of the same JSON path
> > expression, but maybe that's also possible.
>
> Even if the type can change, I don't think that means we need to have space
> for multiple types at the same time - there can't be multiple coercions
> happening at the same time, otherwise there could be two coercions of the same
> type as well. So we don't need memory for every coercion type.

Do you find it unnecessary to statically allocate memory for
JsonItemCoercionState for each possible coercion, as in the following
struct definition:

typedef struct JsonItemCoercionsState
{
    JsonItemCoercionState   null;
    JsonItemCoercionState   string;
    JsonItemCoercionState   numeric;
    JsonItemCoercionState   boolean;
    JsonItemCoercionState   date;
    JsonItemCoercionState   time;
    JsonItemCoercionState   timetz;
    JsonItemCoercionState   timestamp;
    JsonItemCoercionState   timestamptz;
    JsonItemCoercionState   composite;
} JsonItemCoercionsState;

A given JsonItemCoercionState (note singular Coercion) contains:

typedef struct JsonItemCoercionState
{
    /* Expression used to evaluate the coercion */
    JsonCoercion   *coercion;

    /* ExprEvalStep to compute this coercion's expression */
    int             jump_eval_expr;
} JsonItemCoercionState;

jump_eval_expr above is the address in JsonItemCoercions'
ExprState.steps of the 1st ExprEvalStep corresponding to
coercion->expr.  IIUC, all ExprEvalSteps needed to evaluate an
expression and its children must be allocated statically in
ExecInitExprRec(), and none on-the-fly as needed.  So, this considers
all coercions and allocates states of all statically.

> > >   The whole coercion stuff just seems incredibly clunky (in a slightly
> > >   different shape before this patch). ExecEvalJsonExprItemCoercion() calls
> > >   ExecPrepareJsonItemCoercion(), which gets a pointer to one of the per-type
> > >   elements in JsonItemCoercionsState, dispatching on the type of the json
> > >   object. Then we later call ExecGetJsonItemCoercion() (via a convoluted
> > >   path), which again will dispatch on the type (extracting the json object
> > >   again afaics!), to then somehow eventually get the coerced value.
> >
> > I think it might be possible to make this a bit simpler, by not
> > leaving anything coercion-related in ExecEvalJsonExpr().
>
> Honestly, this code seems like it should just be rewritten from scratch.

Based on what I wrote above, please let me know if I've misunderstood
your concerns about over-allocation of coercion state.  I can try to
rewrite one more time if I know what this should look like instead.

> > I left some pieces there, because I thought the error of not finding an
> > appropriate coercion must be thrown right away as the code in
> > ExecEvalJsonExpr() does after calling ExecGetJsonItemCoercion().
> >
> > ExecPrepareJsonItemCoercion() is called later when it's time to
> > actually evaluate the coercion.  If we move the error path to
> > ExecPrepareJsonItemCoercion(), both ExecGetJsonItemCoercion() and the
> > error path code in ExecEvalJsonExpr() will be unnecessary.  I will
> > give that a try.
>
> Why do we need the separation of prepare and then evaluation? They're executed
> straight after each other?

ExecPrepareJsonItemCoercion() is a helper routine to choose the
coercion and extract the Datum out of the JsonbValue produced by the
EEOP_JSONEXPR_PATH step to feed to the coercion expression's
ExprEvalStep.  The coercion evaluation will be done by jumping to said
step in ExecInterpExpr().

> > > - Looks like there's still some recursive expression states, namely
> > >   JsonExprState->{result_coercion, coercions}?
> >
> > So, the problem with inlining coercion evaluation into the main parent
> > JsonExpr's is that it needs to be wrapped in a sub-transaction to
> > catch any errors and return NULL instead.  I don't know a way to wrap
> > ExprEvalStep evaluation in a sub-transaction to achieve that effect.
>
> But we don't need to wrap arbitrary evaluation in a subtransaction - afaics
> the coercion calls a single function, not an arbitrary expression?

It can do EEOP_IOCOERCE for example, and the input/output function may
cause an error depending on what comes out of the JSON blob.

IIUC, those errors need to be caught to satisfy some SQL/JSON spec.

> > Ooh, thanks for letting me know.  So maybe I am missing some
> > llvmjist_emit.h/type.c infrastructure to read an int32 value
> > (jumpdone) out of an int32 pointer (&jumpdone)?
>
> No, you just need to replace l_ptr(TypeSizeT) with l_ptr(LLVMInt32Type()).

OK, thanks.

> > >   I first was confused why the code tries to load the jump target
> > >   dynamically. But then I saw that the interpreted code sets it dynamically -
> > >   why? That's completely unnecessary overhead afaics? There's just two
> > >   possible jump targets, no?
> >
> > Hmm, I looked at the code for other expressions that jump, especially
> > CASE WHEN, but they use ready-made EEOP_JUMP_IF_* steps, which can be
> > added statically.  I thought we can't use them in this case, because
> > the conditions are very ad-hoc, like if the JSON path computation
> > returned an "empty" item or if the "error" flag was set during that
> > computation, etc.
>
> The minimal fix would be to return the jump target from the function, and then
> jump to that. That at least avoids the roundtrip to memory you have right now.

You mean like this:

                    LLVMValueRef v_args[2];
                    LLVMValueRef v_ret;

                    /*
                     * Call ExecEvalJsonExprSkip() to decide if JSON path
                     * evaluation can be skipped.  This returns the step
                     * address to jump to.
                     */
                    v_args[0] = v_state;
                    v_args[1] = l_ptr_const(op, l_ptr(StructExprEvalStep));
                    v_ret = LLVMBuildCall(b,
                                          llvm_pg_func(mod,
"ExecEvalJsonExprSkip"),
                                          params, lengthof(params), "");

Actually, this is how I had started, but never figured out how to jump
to the address in v_ret.  As in, how to extract the plain C int32
value that is the jump address from v_ret, an LLVMValueRef, to use in
the following statement:

                    LLVMBuildBr(b, opblocks[<int32-in-v_ret>]);

--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

I tried to look into some of the questions from Amit, but I have e.g. no idea
what exactly the use of subtransactions tries to achieve - afaics 1a36bc9dba8
is the first patch to introduce needing to evaluate parts expressions in a
subtransaction - but there's not a single comment explaining why.

It's really hard to understand the new json code. It's a substantial amount of
new infrastructure, without any design documentation that I can find. And it's
not like it's just code that's equivalent to nearby stuff. To me this falls
way below our standards and I think it's *months* of work to fix.

Even just the expresion evaluation code: EvalJsonPathVar(),
ExecEvalJsonConstructor(), ExecEvalJsonExpr(), ExecEvalJson(). There's one
layer of subtransactions in one of the paths in ExecEvalJsonExpr(), another in
ExecEvalJson(). Some paths of ExecEvalJsonExpr() go through subtransactions,
others don't.

It's one thing for a small set of changes to be of this quality. But this is
pretty large - a quick summing of diffstat ends up with about 17k lines added,
of which ~2.5k are docs, ~4.8k are tests.


On 2022-08-04 17:01:48 +0900, Amit Langote wrote:
> On Wed, Aug 3, 2022 at 12:00 AM Andres Freund <andres@anarazel.de> wrote:
> > > >   The whole coercion stuff just seems incredibly clunky (in a slightly
> > > >   different shape before this patch). ExecEvalJsonExprItemCoercion() calls
> > > >   ExecPrepareJsonItemCoercion(), which gets a pointer to one of the per-type
> > > >   elements in JsonItemCoercionsState, dispatching on the type of the json
> > > >   object. Then we later call ExecGetJsonItemCoercion() (via a convoluted
> > > >   path), which again will dispatch on the type (extracting the json object
> > > >   again afaics!), to then somehow eventually get the coerced value.
> > >
> > > I think it might be possible to make this a bit simpler, by not
> > > leaving anything coercion-related in ExecEvalJsonExpr().
> >
> > Honestly, this code seems like it should just be rewritten from scratch.
>
> Based on what I wrote above, please let me know if I've misunderstood
> your concerns about over-allocation of coercion state.  I can try to
> rewrite one more time if I know what this should look like instead.

I don't know. I don't understand the design of what needs to have error
handling, and what not.


> > > >   I first was confused why the code tries to load the jump target
> > > >   dynamically. But then I saw that the interpreted code sets it dynamically -
> > > >   why? That's completely unnecessary overhead afaics? There's just two
> > > >   possible jump targets, no?
> > >
> > > Hmm, I looked at the code for other expressions that jump, especially
> > > CASE WHEN, but they use ready-made EEOP_JUMP_IF_* steps, which can be
> > > added statically.  I thought we can't use them in this case, because
> > > the conditions are very ad-hoc, like if the JSON path computation
> > > returned an "empty" item or if the "error" flag was set during that
> > > computation, etc.
> >
> > The minimal fix would be to return the jump target from the function, and then
> > jump to that. That at least avoids the roundtrip to memory you have right now.
>
> You mean like this:
>
>                     LLVMValueRef v_args[2];
>                     LLVMValueRef v_ret;
>
>                     /*
>                      * Call ExecEvalJsonExprSkip() to decide if JSON path
>                      * evaluation can be skipped.  This returns the step
>                      * address to jump to.
>                      */
>                     v_args[0] = v_state;
>                     v_args[1] = l_ptr_const(op, l_ptr(StructExprEvalStep));
>                     v_ret = LLVMBuildCall(b,
>                                           llvm_pg_func(mod,
> "ExecEvalJsonExprSkip"),
>                                           params, lengthof(params), "");
>
> Actually, this is how I had started, but never figured out how to jump
> to the address in v_ret.  As in, how to extract the plain C int32
> value that is the jump address from v_ret, an LLVMValueRef, to use in
> the following statement:
>
>                     LLVMBuildBr(b, opblocks[<int32-in-v_ret>]);

We could make that work, but even keeping it more similar to your current
code, you're already dealing with a variable jump target. Only that you load
it from a variable, instead of the function return type. So you could just
v_ret instead of v_jumpaddr, and your code would be simpler and faster.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
Hi Andres,

On Sat, Aug 6, 2022 at 5:37 AM Andres Freund <andres@anarazel.de> wrote:
> On 2022-08-04 17:01:48 +0900, Amit Langote wrote:
> > On Wed, Aug 3, 2022 at 12:00 AM Andres Freund <andres@anarazel.de> wrote:
> > > Honestly, this code seems like it should just be rewritten from scratch.
> >
> > Based on what I wrote above, please let me know if I've misunderstood
> > your concerns about over-allocation of coercion state.  I can try to
> > rewrite one more time if I know what this should look like instead.
>
> I don't know. I don't understand the design of what needs to have error
> handling, and what not.

AFAIK, there are two things that the JSON path code considers can
cause an error when evaluating a JsonExpr:

* Actual JSON path evaluation in ExecEvalJsonExpr(), because it
invokes JsonPath*() family of functions defined in jsonpath_exec.c,
which can possibly cause an error.  Actually, I looked again today as
to what goes on in them and it seems there is a throwErrors variable
being used to catch and prevent any errors found by the JSON path
machinery itself, and it has the same value as the throwErrors
variable in ExecEvalJsonExpr().  Given that the latter is set per the
ON ERROR specification (throw errors or return NULL / a default
expression in lieu), maybe this part doesn't really need a
sub-transaction.  To check, I took off the sub-transaction around this
part and can see that no tests fail.

* Evaluating coercion expression in ExecEvalJsonExprCoercion(), which
involves passing a user-specified expression through either
EEOP_IOCOERCE or json_populate_type(), both of which can cause errors
that are not suppressible as those in jsonpath_exec.c are.  So, this
part does need a sub-transaction to satisfy the ON ERROR behavior.  To
check, I took out the sub-transaction around the coercion evaluation,
and some tests in jsob_sqljson do indeed fail, like this, for example:

 SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int);
- json_value
-------------
-
-(1 row)
-
+ERROR:  invalid input syntax for type integer: "aaa"

Note that both the JSON path expression and the coercion would run as
part of the one EEOP_JSONEXPR ExecEvalStep before this patch and thus
would be wrapped under the same sub-transaction, even if only the
latter apparently needs it.

With this patch, I tried to keep that same behavior, but because the
coercion evaluation has now been broken out into its own step, it must
use another sub-transaction, given that the same sub-transaction can
no longer wrap both things.  But given my finding that the JSON path
expression doesn't really need one, maybe the new EEOP_JSONEXPR_PATH
step can run without one, while keeping it for the new
EEOP_JSONEXPR_COERCION step.

> > > > >   I first was confused why the code tries to load the jump target
> > > > >   dynamically. But then I saw that the interpreted code sets it dynamically -
> > > > >   why? That's completely unnecessary overhead afaics? There's just two
> > > > >   possible jump targets, no?
> > > >
> > > > Hmm, I looked at the code for other expressions that jump, especially
> > > > CASE WHEN, but they use ready-made EEOP_JUMP_IF_* steps, which can be
> > > > added statically.  I thought we can't use them in this case, because
> > > > the conditions are very ad-hoc, like if the JSON path computation
> > > > returned an "empty" item or if the "error" flag was set during that
> > > > computation, etc.
> > >
> > > The minimal fix would be to return the jump target from the function, and then
> > > jump to that. That at least avoids the roundtrip to memory you have right now.
> >
> > You mean like this:
> >
> >                     LLVMValueRef v_args[2];
> >                     LLVMValueRef v_ret;
> >
> >                     /*
> >                      * Call ExecEvalJsonExprSkip() to decide if JSON path
> >                      * evaluation can be skipped.  This returns the step
> >                      * address to jump to.
> >                      */
> >                     v_args[0] = v_state;
> >                     v_args[1] = l_ptr_const(op, l_ptr(StructExprEvalStep));
> >                     v_ret = LLVMBuildCall(b,
> >                                           llvm_pg_func(mod,
> > "ExecEvalJsonExprSkip"),
> >                                           params, lengthof(params), "");
> >
> > Actually, this is how I had started, but never figured out how to jump
> > to the address in v_ret.  As in, how to extract the plain C int32
> > value that is the jump address from v_ret, an LLVMValueRef, to use in
> > the following statement:
> >
> >                     LLVMBuildBr(b, opblocks[<int32-in-v_ret>]);
>
> We could make that work, but even keeping it more similar to your current
> code, you're already dealing with a variable jump target. Only that you load
> it from a variable, instead of the function return type. So you could just
> v_ret instead of v_jumpaddr, and your code would be simpler and faster.

Ah, I see you mean to continue to use all the LLVMBuildCondBr()s as
the code currently does, but use v_ret like in the code above, instead
of v_jumpaddr, to access the jump address returned by the
step-choosing function.  I've done that in the updated patch.  This
also allows us to get rid of all the jumpdone fields in the
ExprEvalStep.

I've also moved the blocks of code in ExecInitExprRec() that
initialize the state for JsonExpr and JsonItemCoercions into new
functions.  I've also moved EvalJsonPathVar() from execExprInterp.c to
jsonpath_exec.c where it's used.

--
Thanks, Amit Langote
EDB: http://www.enterprisedb.com

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
"Jonathan S. Katz"
Date:
On 8/5/22 4:36 PM, Andres Freund wrote:
> Hi,
> 
> I tried to look into some of the questions from Amit, but I have e.g. no idea
> what exactly the use of subtransactions tries to achieve - afaics 1a36bc9dba8
> is the first patch to introduce needing to evaluate parts expressions in a
> subtransaction - but there's not a single comment explaining why.
> 
> It's really hard to understand the new json code. It's a substantial amount of
> new infrastructure, without any design documentation that I can find. And it's
> not like it's just code that's equivalent to nearby stuff. To me this falls
> way below our standards and I think it's *months* of work to fix.
> 
> Even just the expresion evaluation code: EvalJsonPathVar(),
> ExecEvalJsonConstructor(), ExecEvalJsonExpr(), ExecEvalJson(). There's one
> layer of subtransactions in one of the paths in ExecEvalJsonExpr(), another in
> ExecEvalJson(). Some paths of ExecEvalJsonExpr() go through subtransactions,
> others don't.
> 
> It's one thing for a small set of changes to be of this quality. But this is
> pretty large - a quick summing of diffstat ends up with about 17k lines added,
> of which ~2.5k are docs, ~4.8k are tests.

The RMT met today to discuss the state of this open item surrounding the 
SQL/JSON feature set. We discussed the specific concerns raised about 
the code and debated four different options:

   1. Do nothing and continue with the community process of stabilizing 
the code without significant redesign

   2. Recommend holding up the v15 release to allow for the code to be 
redesigned and fixed (as based on Andres' estimates, this would push the 
release out several months).

   3. Revert the problematic parts of the code but try to include some 
of the features in the v15 release (e.g. JSON_TABLE)

   4. Revert the feature set and redesign and try to include for v16

Based on the concerns raised, the RMT is recommending option #4, to 
revert the SQL/JSON changes for v15, and come back with a redesign for v16.

If folks think there are some bits we can include in v15, we can 
consider option #3. (Personally, I would like to see if we can keep 
JSON_TABLE, but if there is too much overlap with the problematic 
portions of the code I am fine with waiting for v16).

At this stage in the release process coupled with the concerns, we're a 
bit worried about introducing changes that are unpredictable in terms of 
stability and maintenance. We also do not want to hold up the release 
while this feature set is goes through a redesign without agreement on 
what such a design would look like as well as a timeline.

Note that the above are the RMT's recommendations; while the RMT can 
explicitly call for a revert, we want to first guide the discussion on 
the best path forward knowing the challenges for including these 
features in v15.

Thanks,

Jonathan

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-08-09 Tu 09:59, Jonathan S. Katz wrote:
> On 8/5/22 4:36 PM, Andres Freund wrote:
>> Hi,
>>
>> I tried to look into some of the questions from Amit, but I have e.g.
>> no idea
>> what exactly the use of subtransactions tries to achieve - afaics
>> 1a36bc9dba8
>> is the first patch to introduce needing to evaluate parts expressions
>> in a
>> subtransaction - but there's not a single comment explaining why.
>>
>> It's really hard to understand the new json code. It's a substantial
>> amount of
>> new infrastructure, without any design documentation that I can find.
>> And it's
>> not like it's just code that's equivalent to nearby stuff. To me this
>> falls
>> way below our standards and I think it's *months* of work to fix.
>>
>> Even just the expresion evaluation code: EvalJsonPathVar(),
>> ExecEvalJsonConstructor(), ExecEvalJsonExpr(), ExecEvalJson().
>> There's one
>> layer of subtransactions in one of the paths in ExecEvalJsonExpr(),
>> another in
>> ExecEvalJson(). Some paths of ExecEvalJsonExpr() go through
>> subtransactions,
>> others don't.
>>
>> It's one thing for a small set of changes to be of this quality. But
>> this is
>> pretty large - a quick summing of diffstat ends up with about 17k
>> lines added,
>> of which ~2.5k are docs, ~4.8k are tests.
>
> The RMT met today to discuss the state of this open item surrounding
> the SQL/JSON feature set. We discussed the specific concerns raised
> about the code and debated four different options:
>
>   1. Do nothing and continue with the community process of stabilizing
> the code without significant redesign
>
>   2. Recommend holding up the v15 release to allow for the code to be
> redesigned and fixed (as based on Andres' estimates, this would push
> the release out several months).
>
>   3. Revert the problematic parts of the code but try to include some
> of the features in the v15 release (e.g. JSON_TABLE)
>
>   4. Revert the feature set and redesign and try to include for v16
>
> Based on the concerns raised, the RMT is recommending option #4, to
> revert the SQL/JSON changes for v15, and come back with a redesign for
> v16.
>
> If folks think there are some bits we can include in v15, we can
> consider option #3. (Personally, I would like to see if we can keep
> JSON_TABLE, but if there is too much overlap with the problematic
> portions of the code I am fine with waiting for v16).
>
> At this stage in the release process coupled with the concerns, we're
> a bit worried about introducing changes that are unpredictable in
> terms of stability and maintenance. We also do not want to hold up the
> release while this feature set is goes through a redesign without
> agreement on what such a design would look like as well as a timeline.
>
> Note that the above are the RMT's recommendations; while the RMT can
> explicitly call for a revert, we want to first guide the discussion on
> the best path forward knowing the challenges for including these
> features in v15.
>
>

I very much doubt option 3 is feasible. The parts that are controversial
go back at least in part to the first patches of the series. Trying to
salvage anything would almost certainly be more disruptive than trying
to fix it.

I'm not sure what the danger is to stability, performance or correctness
in applying the changes Amit has proposed for release 15. But if that
danger is judged to be too great then I agree we should revert.

I should add that I'm very grateful to Amit for his work, and I'm sure
it isn't wasted effort, whatever the decision.


cheers


andrew



--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
"Jonathan S. Katz"
Date:
On 8/9/22 11:03 AM, Andrew Dunstan wrote:
> 
> On 2022-08-09 Tu 09:59, Jonathan S. Katz wrote:

>> The RMT met today to discuss the state of this open item surrounding
>> the SQL/JSON feature set. We discussed the specific concerns raised
>> about the code and debated four different options:
>>
>>    1. Do nothing and continue with the community process of stabilizing
>> the code without significant redesign
>>
>>    2. Recommend holding up the v15 release to allow for the code to be
>> redesigned and fixed (as based on Andres' estimates, this would push
>> the release out several months).
>>
>>    3. Revert the problematic parts of the code but try to include some
>> of the features in the v15 release (e.g. JSON_TABLE)
>>
>>    4. Revert the feature set and redesign and try to include for v16
>>
>> Based on the concerns raised, the RMT is recommending option #4, to
>> revert the SQL/JSON changes for v15, and come back with a redesign for
>> v16.
>>
>> If folks think there are some bits we can include in v15, we can
>> consider option #3. (Personally, I would like to see if we can keep
>> JSON_TABLE, but if there is too much overlap with the problematic
>> portions of the code I am fine with waiting for v16).
>>
>> At this stage in the release process coupled with the concerns, we're
>> a bit worried about introducing changes that are unpredictable in
>> terms of stability and maintenance. We also do not want to hold up the
>> release while this feature set is goes through a redesign without
>> agreement on what such a design would look like as well as a timeline.
>>
>> Note that the above are the RMT's recommendations; while the RMT can
>> explicitly call for a revert, we want to first guide the discussion on
>> the best path forward knowing the challenges for including these
>> features in v15.
>>
>>
> 
> I very much doubt option 3 is feasible. The parts that are controversial
> go back at least in part to the first patches of the series. Trying to
> salvage anything would almost certainly be more disruptive than trying
> to fix it.
> 
> I'm not sure what the danger is to stability, performance or correctness
> in applying the changes Amit has proposed for release 15. But if that
> danger is judged to be too great then I agree we should revert.

Speaking personally, I would like to see what we could do to include 
support for this batch of the SQL/JSON features in v15. What is included 
looks like it closes most of the gap on what we've been missing 
syntactically since the standard was adopted, and the JSON_TABLE work is 
very convenient for converting JSON data into a relational format. I 
believe having this feature set is important for maintaining standards 
compliance, interoperability, tooling support, and general usability. 
Plus, JSON still seems to be pretty popular :)

Rereading the thread for the umpteenth time, I have seen Amit working 
through Andres' concerns. From my read, the ones that seem pressing are:

* Lack of design documentation, which may be leading to some of the 
general design concerns
* The use of substransactions within the executor, though docs 
explaining the decisions on that could alleviate it (I realize this is a 
big topic and any summary I give won't capture the full nuance)
* Debate on how to handle the type coercions

(Please correct me if I've missed anything).

I hope that these can be addressed satisfactorily in a reasonable (read: 
now a much shorter) timeframe so we can include the SQL/JSON work in v15.

With my RMT hat on, the issue is that we're now at beta 3 and we still 
have not not reached a resolution on this open item. Even if it were 
committed tomorrow, we would definitely need a beta 4, and we would want 
to let the code bake a bit more to ensure we get adequate test coverage 
on it. This would likely put the release date into October, presuming we 
have not found any other issues that could cause a release delay.

With my advocacy hat on, we're at the point in the release cycle where I 
kick off the GA release process (e.g. announcement drafting). Not 
knowing the final status of a feature that's likely to be highlighted 
makes it difficult to write said release as well as kick off the other 
machinery (e.g. translations). If there is at least a decision on next 
steps, I can adjust the GA release process timeline.

> I should add that I'm very grateful to Amit for his work, and I'm sure
> it isn't wasted effort, whatever the decision.

+1. While I've been quiet on this thread to date, I have definitely seen 
Amit working hard on addressing the concerns.

Thanks,

Jonathan

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-08-09 14:04:48 -0400, Jonathan S. Katz wrote:
> On 8/9/22 11:03 AM, Andrew Dunstan wrote:
> > 
> > On 2022-08-09 Tu 09:59, Jonathan S. Katz wrote:
> 
> > > The RMT met today to discuss the state of this open item surrounding
> > > the SQL/JSON feature set. We discussed the specific concerns raised
> > > about the code and debated four different options:
> > > 
> > >    1. Do nothing and continue with the community process of stabilizing
> > > the code without significant redesign
> > > 
> > >    2. Recommend holding up the v15 release to allow for the code to be
> > > redesigned and fixed (as based on Andres' estimates, this would push
> > > the release out several months).

Obviously that's a question of the resources brought to bear.

From my angle: I've obviously some of my own work to take care of as well, but
it's also just hard to improve something that includes a lot of undocumented
design decisions.


> > >    3. Revert the problematic parts of the code but try to include some
> > > of the features in the v15 release (e.g. JSON_TABLE)

> > I very much doubt option 3 is feasible. The parts that are controversial
> > go back at least in part to the first patches of the series. Trying to
> > salvage anything would almost certainly be more disruptive than trying
> > to fix it.

Agreed.


> > >    4. Revert the feature set and redesign and try to include for v16
> > > 
> > > Based on the concerns raised, the RMT is recommending option #4, to
> > > revert the SQL/JSON changes for v15, and come back with a redesign for
> > > v16.
> > > 
> > > If folks think there are some bits we can include in v15, we can
> > > consider option #3. (Personally, I would like to see if we can keep
> > > JSON_TABLE, but if there is too much overlap with the problematic
> > > portions of the code I am fine with waiting for v16).
> > > 
> > > At this stage in the release process coupled with the concerns, we're
> > > a bit worried about introducing changes that are unpredictable in
> > > terms of stability and maintenance. We also do not want to hold up the
> > > release while this feature set is goes through a redesign without
> > > agreement on what such a design would look like as well as a timeline.
> > > 
> > > Note that the above are the RMT's recommendations; while the RMT can
> > > explicitly call for a revert, we want to first guide the discussion on
> > > the best path forward knowing the challenges for including these
> > > features in v15.

Unless we decide on 4 immediately, I think it might be worth starting a
separate thread to get more attention. The subject doesn't necessarily have
everyone follow along.



> > I'm not sure what the danger is to stability, performance or correctness
> > in applying the changes Amit has proposed for release 15. But if that
> > danger is judged to be too great then I agree we should revert.

My primary problem is that as-is the code is nearly unmaintainable. It's hard
for Amit to fix that, given that he's not one of the original authors.


> Rereading the thread for the umpteenth time, I have seen Amit working
> through Andres' concerns. From my read, the ones that seem pressing are:
> 
> * Lack of design documentation, which may be leading to some of the general
> design concerns

> * The use of substransactions within the executor, though docs explaining
> the decisions on that could alleviate it (I realize this is a big topic and
> any summary I give won't capture the full nuance)

I don't think subtransactions per-se are a fundamental problem. I think the
error handling implementation is ridiculously complicated, and while I started
to hack on improving it, I stopped when I really couldn't understand what
errors it actually needs to handle when and why.


> * Debate on how to handle the type coercions

That's partially related to the error handling issue above.

One way this code could be drastically simplified is to force all
type-coercions to go through the "io coercion" path, which could be
implemented as a single execution step (which thus could trivially
start/finish a subtransaction) and would remove a lot of the complicated code
around coercions.


> > I should add that I'm very grateful to Amit for his work, and I'm sure
> > it isn't wasted effort, whatever the decision.
> 
> +1. While I've been quiet on this thread to date, I have definitely seen
> Amit working hard on addressing the concerns.

+1

Greetings,

Andres Freund



"Jonathan S. Katz" <jkatz@postgresql.org> writes:
> Speaking personally, I would like to see what we could do to include 
> support for this batch of the SQL/JSON features in v15. What is included 
> looks like it closes most of the gap on what we've been missing 
> syntactically since the standard was adopted, and the JSON_TABLE work is 
> very convenient for converting JSON data into a relational format. I 
> believe having this feature set is important for maintaining standards 
> compliance, interoperability, tooling support, and general usability. 
> Plus, JSON still seems to be pretty popular :)
> ...
> I hope that these can be addressed satisfactorily in a reasonable (read: 
> now a much shorter) timeframe so we can include the SQL/JSON work in v15.

We have delayed releases for $COOL_FEATURE in the past, and I think
our batting average on that is still .000: not once has it worked out
well.  I think we're better off getting the pain over with quickly,
so I regretfully vote for revert.  And for a full redesign/rewrite
before we try again; based on Andres' comments, it needs that.

            regards, tom lane



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-08-09 15:17:44 -0400, Tom Lane wrote:
> We have delayed releases for $COOL_FEATURE in the past, and I think
> our batting average on that is still .000: not once has it worked out
> well.

I think it semi worked when jsonb (?) first went in - it took a while and a
lot of effort from a lot of people, but in the end we made it work, and it was
a success from our user's perspectives, I think. OTOH, it's not a great sign
this is around json again...

- Andres



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
"Jonathan S. Katz"
Date:
On 8/9/22 3:22 PM, Andres Freund wrote:
> Hi,
> 
> On 2022-08-09 15:17:44 -0400, Tom Lane wrote:
>> We have delayed releases for $COOL_FEATURE in the past, and I think
>> our batting average on that is still .000: not once has it worked out
>> well.
> 
> I think it semi worked when jsonb (?) first went in - it took a while and a
> lot of effort from a lot of people, but in the end we made it work, and it was
> a success from our user's perspectives, I think. 

Yeah, this was the example I was thinking of. To continue with the 
baseball analogy, it was a home-run from a PR perspective, and I can say 
as a power user at the time, the 9.4 JSONB representation worked well 
for my use case. Certainly newer functionality has made JSON easier to 
work with in PG.

(I can't remember what the 9.5 hold up was).

The cases where we either delayed/punted on $COOL_FEATURE that cause me 
concern are the ones where we say "OK, well fix this in the next 
release" and we are then waiting, 2, 3, 4 releases for the work to be 
completed. And to be clear, I'm thinking of this as "known issues" vs. 
"iterating towards the whole solution".

> OTOH, it's not a great sign  this is around json again...

Yeah, I was thinking about that too.

Per Andres comment upthread, let's open a new thread to discuss the 
SQL/JSON + v15 topic to improve visibility and get more feedback. I can 
do that shortly.

We can continue with the technical discussion in here.

Jonathan

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
"Jonathan S. Katz"
Date:
On 8/9/22 2:57 PM, Andres Freund wrote:
> Hi,
> 
> On 2022-08-09 14:04:48 -0400, Jonathan S. Katz wrote:

>>>>     2. Recommend holding up the v15 release to allow for the code to be
>>>> redesigned and fixed (as based on Andres' estimates, this would push
>>>> the release out several months).
> 
> Obviously that's a question of the resources brought to bear.
> 
>  From my angle: I've obviously some of my own work to take care of as well, but
> it's also just hard to improve something that includes a lot of undocumented
> design decisions.

*nod*

>>>>     4. Revert the feature set and redesign and try to include for v16

> Unless we decide on 4 immediately, I think it might be worth starting a
> separate thread to get more attention. The subject doesn't necessarily have
> everyone follow along.

*nod* I'll do this shortly.


>> Rereading the thread for the umpteenth time, I have seen Amit working
>> through Andres' concerns. From my read, the ones that seem pressing are:
>>
>> * Lack of design documentation, which may be leading to some of the general
>> design concerns
> 
>> * The use of substransactions within the executor, though docs explaining
>> the decisions on that could alleviate it (I realize this is a big topic and
>> any summary I give won't capture the full nuance)
> 
> I don't think subtransactions per-se are a fundamental problem. I think the
> error handling implementation is ridiculously complicated, and while I started
> to hack on improving it, I stopped when I really couldn't understand what
> errors it actually needs to handle when and why.

Ah, thanks for the clarification. That makes sense.

>> * Debate on how to handle the type coercions
> 
> That's partially related to the error handling issue above.
> 
> One way this code could be drastically simplified is to force all
> type-coercions to go through the "io coercion" path, which could be
> implemented as a single execution step (which thus could trivially
> start/finish a subtransaction) and would remove a lot of the complicated code
> around coercions.

If we went down this path, would this make you feel more comfortable 
with including this work in the v15 release?

Thanks,

Jonathan

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-08-09 Tu 15:50, Jonathan S. Katz wrote:
> On 8/9/22 3:22 PM, Andres Freund wrote:
>> Hi,
>>
>> On 2022-08-09 15:17:44 -0400, Tom Lane wrote:
>>> We have delayed releases for $COOL_FEATURE in the past, and I think
>>> our batting average on that is still .000: not once has it worked out
>>> well.
>>
>> I think it semi worked when jsonb (?) first went in - it took a while
>> and a
>> lot of effort from a lot of people, but in the end we made it work,
>> and it was
>> a success from our user's perspectives, I think. 
>
> Yeah, this was the example I was thinking of. To continue with the
> baseball analogy, it was a home-run from a PR perspective, and I can
> say as a power user at the time, the 9.4 JSONB representation worked
> well for my use case. Certainly newer functionality has made JSON
> easier to work with in PG.
>
> (I can't remember what the 9.5 hold up was).
>
> The cases where we either delayed/punted on $COOL_FEATURE that cause
> me concern are the ones where we say "OK, well fix this in the next
> release" and we are then waiting, 2, 3, 4 releases for the work to be
> completed. And to be clear, I'm thinking of this as "known issues" vs.
> "iterating towards the whole solution".


Where we ended up with jsonb was a long way from where we started, but
technical difficulties were largely confined because it didn't involve
anything like the parser or the expression evaluation code. Here the SQL
Standards Committee has imposed a pretty substantial technical burden on
us and the code that Andres complains of is attempting to deal with that.


>
>> OTOH, it's not a great sign  this is around json again...
>
> Yeah, I was thinking about that too.


Ouch :-(

I think after 10 years of being involved with our JSON features, I'm
going to take a substantial break on that front.


cheers


andrew


--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
"Jonathan S. Katz"
Date:
On 8/9/22 4:15 PM, Andrew Dunstan wrote:
> 
> On 2022-08-09 Tu 15:50, Jonathan S. Katz wrote:
>> On 8/9/22 3:22 PM, Andres Freund wrote:

>>
>>> OTOH, it's not a great sign  this is around json again...
>>
>> Yeah, I was thinking about that too.
> 
> 
> Ouch :-(
> 
> I think after 10 years of being involved with our JSON features, I'm
> going to take a substantial break on that front.

I hope that wasn't taken as a sleight, but just an observation. There 
are other feature areas where I can make similar observations. All this 
work around a database system is challenging as there are many 
considerations that need to be made.

You've done an awesome job driving the JSON work forward and it is 
greatly appreciated.

Thanks,

Jonathan

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andrew Dunstan
Date:
On 2022-08-09 Tu 16:19, Jonathan S. Katz wrote:
> On 8/9/22 4:15 PM, Andrew Dunstan wrote:
>>
>> On 2022-08-09 Tu 15:50, Jonathan S. Katz wrote:
>>> On 8/9/22 3:22 PM, Andres Freund wrote:
>
>>>
>>>> OTOH, it's not a great sign  this is around json again...
>>>
>>> Yeah, I was thinking about that too.
>>
>>
>> Ouch :-(
>>
>> I think after 10 years of being involved with our JSON features, I'm
>> going to take a substantial break on that front.
>
> I hope that wasn't taken as a sleight, but just an observation. There
> are other feature areas where I can make similar observations. All
> this work around a database system is challenging as there are many
> considerations that need to be made.
>
> You've done an awesome job driving the JSON work forward and it is
> greatly appreciated.
>
>


Thanks, I appreciate that (and I wasn't fishing for compliments). It's
more that I feel a bit tired of it ... some of my colleagues will
confirm that I've been saying this for a while, so it's not spurred by
this setback.


cheers


andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com




Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Amit Langote
Date:
On Wed, Aug 10, 2022 at 3:57 AM Andres Freund <andres@anarazel.de> wrote:
> One way this code could be drastically simplified is to force all
> type-coercions to go through the "io coercion" path, which could be
> implemented as a single execution step (which thus could trivially
> start/finish a subtransaction) and would remove a lot of the complicated code
> around coercions.

Could you please clarify how you think we might do the io coercion
wrapped with a subtransaction all as a single execution step?  I
would've thought that we couldn't do the sub-transaction without
leaving ExecInterpExpr() anyway, so maybe you meant the io coercion
itself was done using some code outside ExecInterpExpr()?

The current JsonExpr code does it by recursively calling
ExecInterpExpr() using the nested ExprState expressly for the
coercion.

-- 
Thanks, Amit Langote
EDB: http://www.enterprisedb.com



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
"Jonathan S. Katz"
Date:
On 8/10/22 9:27 AM, Amit Langote wrote:
> On Wed, Aug 10, 2022 at 3:57 AM Andres Freund <andres@anarazel.de> wrote:
>> One way this code could be drastically simplified is to force all
>> type-coercions to go through the "io coercion" path, which could be
>> implemented as a single execution step (which thus could trivially
>> start/finish a subtransaction) and would remove a lot of the complicated code
>> around coercions.
> 
> Could you please clarify how you think we might do the io coercion
> wrapped with a subtransaction all as a single execution step?  I
> would've thought that we couldn't do the sub-transaction without
> leaving ExecInterpExpr() anyway, so maybe you meant the io coercion
> itself was done using some code outside ExecInterpExpr()?
> 
> The current JsonExpr code does it by recursively calling
> ExecInterpExpr() using the nested ExprState expressly for the
> coercion.

With RMT hat on, Andres do you have any thoughts on this?

Thanks,

Jonathan

Attachment

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2022-08-11 13:08:27 -0400, Jonathan S. Katz wrote:
> On 8/10/22 9:27 AM, Amit Langote wrote:
> > On Wed, Aug 10, 2022 at 3:57 AM Andres Freund <andres@anarazel.de> wrote:
> > > One way this code could be drastically simplified is to force all
> > > type-coercions to go through the "io coercion" path, which could be
> > > implemented as a single execution step (which thus could trivially
> > > start/finish a subtransaction) and would remove a lot of the complicated code
> > > around coercions.
> > 
> > Could you please clarify how you think we might do the io coercion
> > wrapped with a subtransaction all as a single execution step?  I
> > would've thought that we couldn't do the sub-transaction without
> > leaving ExecInterpExpr() anyway, so maybe you meant the io coercion
> > itself was done using some code outside ExecInterpExpr()?
> > 
> > The current JsonExpr code does it by recursively calling
> > ExecInterpExpr() using the nested ExprState expressly for the
> > coercion.

The basic idea is to rip out all the type-dependent stuff out and replace it
with a single JSON_IOCERCE step, which has a parameter about whether to wrap
things in a subtransaction or not. That step would always perform the coercion
by calling the text output function of the input and the text input function
of the output.


> With RMT hat on, Andres do you have any thoughts on this?

I think I need to prototype how it'd look like to give a more detailed
answer. I have a bunch of meetings over the next few hours, but after that I
can give it a shot.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Tom Lane
Date:
I wrote:
> Andres Freund <andres@anarazel.de> writes:
>> Maybe it's worth sticking a StaticAssert() for the struct size
>> somewhere.

> Indeed.  I thought we had one already.

>> I'm a bit wary about that being too noisy, there are some machines with
>> odd alignment requirements. Perhaps worth restricting the assertion to
>> x86-64 + armv8 or such?

> I'd put it in first and only reconsider if it shows unfixable problems.

Now that we've got the sizeof(ExprEvalStep) under control, shouldn't
we do the attached?

            regards, tom lane

diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86e1ac1e65..06c3adc0a1 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -669,6 +669,10 @@ typedef struct ExprEvalStep
     }            d;
 } ExprEvalStep;
 
+/* Enforce the size rule given in the comment above */
+StaticAssertDecl(sizeof(ExprEvalStep) <= 64,
+                 "size of ExprEvalStep exceeds 64 bytes");
+
 
 /* Non-inline data for container operations */
 typedef struct SubscriptingRefState

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2023-02-22 16:34:44 -0500, Tom Lane wrote:
> I wrote:
> > Andres Freund <andres@anarazel.de> writes:
> >> Maybe it's worth sticking a StaticAssert() for the struct size
> >> somewhere.
> 
> > Indeed.  I thought we had one already.
> 
> >> I'm a bit wary about that being too noisy, there are some machines with
> >> odd alignment requirements. Perhaps worth restricting the assertion to
> >> x86-64 + armv8 or such?
> 
> > I'd put it in first and only reconsider if it shows unfixable problems.
> 
> Now that we've got the sizeof(ExprEvalStep) under control, shouldn't
> we do the attached?

Indeed. Pushed.

Let's hope there's no rarely used architecture with odd alignment rules.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Corey Huinker
Date:
On Wed, Feb 22, 2023 at 5:47 PM Andres Freund <andres@anarazel.de> wrote:
Hi,

On 2023-02-22 16:34:44 -0500, Tom Lane wrote:
> I wrote:
> > Andres Freund <andres@anarazel.de> writes:
> >> Maybe it's worth sticking a StaticAssert() for the struct size
> >> somewhere.
>
> > Indeed.  I thought we had one already.
>
> >> I'm a bit wary about that being too noisy, there are some machines with
> >> odd alignment requirements. Perhaps worth restricting the assertion to
> >> x86-64 + armv8 or such?
>
> > I'd put it in first and only reconsider if it shows unfixable problems.
>
> Now that we've got the sizeof(ExprEvalStep) under control, shouldn't
> we do the attached?

Indeed. Pushed.

Let's hope there's no rarely used architecture with odd alignment rules.

Greetings,

Andres Freund



I have a question about this that may affect some of my future work. 

My not-ready-for-16 work on CAST( ... ON DEFAULT ... ) involved making FuncExpr/IoCoerceExpr/ArrayCoerceExpr have a safe_mode flag, and that necessitates adding a reserror boolean to ExprEvalStep for subsequent steps to test if the error happened.

Will that change be throwing some architectures over the 64 byte count?
 

Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Tom Lane
Date:
Corey Huinker <corey.huinker@gmail.com> writes:
> My not-ready-for-16 work on CAST( ... ON DEFAULT ... ) involved making
> FuncExpr/IoCoerceExpr/ArrayCoerceExpr have a safe_mode flag, and that
> necessitates adding a reserror boolean to ExprEvalStep for subsequent steps
> to test if the error happened.

Why do you want it in ExprEvalStep ... couldn't it be in ExprState?
I can't see why you'd need more than one at a time during evaluation.

            regards, tom lane



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2023-02-23 13:39:14 -0500, Corey Huinker wrote:
> My not-ready-for-16 work on CAST( ... ON DEFAULT ... ) involved making
> FuncExpr/IoCoerceExpr/ArrayCoerceExpr have a safe_mode flag, and that
> necessitates adding a reserror boolean to ExprEvalStep for subsequent steps
> to test if the error happened.

I think if that requires adding a new variable to each ExprEvalStep, it's
DOA. The overhead would be too high. But I don't see why it would need to be
added all ExprEvalSteps instead of individual steps, or perhaps ExprEvalState?


> Will that change be throwing some architectures over the 64 byte count?

It would.

I find the 'pahole' tool very useful for looking at struct layout.


struct ExprEvalStep {
        intptr_t                   opcode;               /*     0     8 */
        Datum *                    resvalue;             /*     8     8 */
        _Bool *                    resnull;              /*    16     8 */
        union {
                struct {
                        int        last_var;             /*    24     4 */
                        _Bool      fixed;                /*    28     1 */

                        /* XXX 3 bytes hole, try to pack */

                        TupleDesc  known_desc;           /*    32     8 */
                        const TupleTableSlotOps  * kind; /*    40     8 */
                } fetch;                                 /*    24    24 */
...
                struct {
                        Datum *    values;               /*    24     8 */
                        _Bool *    nulls;                /*    32     8 */
                        int        nelems;               /*    40     4 */
                        MinMaxOp   op;                   /*    44     4 */
                        FmgrInfo * finfo;                /*    48     8 */
                        FunctionCallInfo fcinfo_data;    /*    56     8 */
                } minmax;                                /*    24    40 */
...

        } d;                                             /*    24    40 */

        /* size: 64, cachelines: 1, members: 4 */
};


We don't have memory to spare in the "general" portion of ExprEvalStep
(currently 24 bytes), as several of the type-specific portions are already 40
bytes large.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Andres Freund
Date:
Hi,

On 2023-02-23 13:56:56 -0500, Tom Lane wrote:
> Corey Huinker <corey.huinker@gmail.com> writes:
> > My not-ready-for-16 work on CAST( ... ON DEFAULT ... ) involved making
> > FuncExpr/IoCoerceExpr/ArrayCoerceExpr have a safe_mode flag, and that
> > necessitates adding a reserror boolean to ExprEvalStep for subsequent steps
> > to test if the error happened.
> 
> Why do you want it in ExprEvalStep ... couldn't it be in ExprState?
> I can't see why you'd need more than one at a time during evaluation.

I don't know exactly what CAST( ... ON DEFAULT ... ) is aiming for - I guess
it wants to assign a different value when the cast fails?  Is the default
expression a constant, or does it need to be runtime evaluated?  If a const,
then the cast steps just could assign the new value. If runtime evaluation is
needed I'd expect the various coerce steps to jump to the value implementing
the default expression in case of a failure.

So I'm not sure we even need a reserror field in ExprState.

Greetings,

Andres Freund



Re: PG 15 (and to a smaller degree 14) regression due to ExprEvalStep size

From
Corey Huinker
Date:
On Thu, Feb 23, 2023 at 2:39 PM Andres Freund <andres@anarazel.de> wrote:
Hi,

On 2023-02-23 13:56:56 -0500, Tom Lane wrote:
> Corey Huinker <corey.huinker@gmail.com> writes:
> > My not-ready-for-16 work on CAST( ... ON DEFAULT ... ) involved making
> > FuncExpr/IoCoerceExpr/ArrayCoerceExpr have a safe_mode flag, and that
> > necessitates adding a reserror boolean to ExprEvalStep for subsequent steps
> > to test if the error happened.
>
> Why do you want it in ExprEvalStep ... couldn't it be in ExprState?
> I can't see why you'd need more than one at a time during evaluation.

I don't know exactly what CAST( ... ON DEFAULT ... ) is aiming for - I guess
it wants to assign a different value when the cast fails?  Is the default
expression a constant, or does it need to be runtime evaluated?  If a const,
then the cast steps just could assign the new value. If runtime evaluation is
needed I'd expect the various coerce steps to jump to the value implementing
the default expression in case of a failure.

The default expression is itself a cast expression. So CAST (expr1 AS some_type DEFAULT expr2 ON ERROR) would basically be a safe-mode cast of expr1 to some_type, and only upon failure would the non-safe cast of expr2 to some_type be executed. Granted, the most common use case would be for expr2 to be a constant or something that folds into a constant, but the proposed spec allows for it.

My implementation involved adding a setting to CoalesceExpr that tested for error flags rather than null flags, hence putting it in ExprEvalStep and ExprState (perhaps mistakenly). Copying and adapting EEOP_JUMP_IF_NOT_NULL lead me to this:

          EEO_CASE(EEOP_JUMP_IF_NOT_ERROR)
          {    
              /* Transfer control if current result is non-error */
              if (!*op->reserror)
              {    
                  *op->reserror = false;
                  EEO_JUMP(op->d.jump.jumpdone);
              }    
 
              /* reset error flag */
              *op->reserror = false;
 
              EEO_NEXT();
          }