Re: A performance issue with Memoize

From: Pavel Stehule <pavel(dot)stehule(at)gmail(dot)com>
To: Richard Guo <guofenglinux(at)gmail(dot)com>
Cc: PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>, David Rowley <dgrowleyml(at)gmail(dot)com>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Subject: Re: A performance issue with Memoize
Date: 2023-10-20 11:43:02
Message-ID: CAFj8pRAGeGxuU2H_rPhYuBTtkjVoWODmWaKzKqYjpVxM5vKmvA@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

pá 20. 10. 2023 v 13:01 odesílatel Richard Guo <guofenglinux(at)gmail(dot)com>
napsal:

> I noticed $subject with the query below.
>
> set enable_memoize to off;
>
> explain (analyze, costs off)
> select * from tenk1 t1 left join lateral
> (select t1.two as t1two, * from tenk1 t2 offset 0) s
> on t1.two = s.two;
> QUERY PLAN
>
> ------------------------------------------------------------------------------------
> Nested Loop Left Join (actual time=0.050..59578.053 rows=50000000 loops=1)
> -> Seq Scan on tenk1 t1 (actual time=0.027..2.703 rows=10000 loops=1)
> -> Subquery Scan on s (actual time=0.004..4.819 rows=5000 loops=10000)
> Filter: (t1.two = s.two)
> Rows Removed by Filter: 5000
> -> Seq Scan on tenk1 t2 (actual time=0.002..3.834 rows=10000
> loops=10000)
> Planning Time: 0.666 ms
> Execution Time: 60937.899 ms
> (8 rows)
>
> set enable_memoize to on;
>
> explain (analyze, costs off)
> select * from tenk1 t1 left join lateral
> (select t1.two as t1two, * from tenk1 t2 offset 0) s
> on t1.two = s.two;
> QUERY PLAN
>
> ------------------------------------------------------------------------------------------
> Nested Loop Left Join (actual time=0.061..122684.607 rows=50000000
> loops=1)
> -> Seq Scan on tenk1 t1 (actual time=0.026..3.367 rows=10000 loops=1)
> -> Memoize (actual time=0.011..9.821 rows=5000 loops=10000)
> Cache Key: t1.two, t1.two
> Cache Mode: binary
> Hits: 0 Misses: 10000 Evictions: 9999 Overflows: 0 Memory
> Usage: 1368kB
> -> Subquery Scan on s (actual time=0.008..5.188 rows=5000
> loops=10000)
> Filter: (t1.two = s.two)
> Rows Removed by Filter: 5000
> -> Seq Scan on tenk1 t2 (actual time=0.004..4.081
> rows=10000 loops=10000)
> Planning Time: 0.607 ms
> Execution Time: 124431.388 ms
> (12 rows)
>
> The execution time (best of 3) is 124431.388 VS 60937.899 with and
> without memoize.
>
> The Memoize runtime stats 'Hits: 0 Misses: 10000 Evictions: 9999'
> seems suspicious to me, so I've looked into it a little bit, and found
> that the MemoizeState's keyparamids and its outerPlan's chgParam are
> always different, and that makes us have to purge the entire cache each
> time we rescan the memoize node.
>
> But why are they always different? Well, for the query above, we have
> two NestLoopParam nodes, one (with paramno 1) is created when we replace
> outer-relation Vars in the scan qual 't1.two = s.two', the other one
> (with paramno 0) is added from the subquery's subplan_params, which was
> created when we replaced uplevel vars with Param nodes for the subquery.
> That is to say, the chgParam would be {0, 1}.
>
> When it comes to replace outer-relation Vars in the memoize keys, the
> two 't1.two' Vars are both replaced with the NestLoopParam with paramno
> 1, because it is the first NLP we see in root->curOuterParams that is
> equal to the Vars in memoize keys. That is to say, the memoize node's
> keyparamids is {1}.
>
> I haven't thought thoroughly about the fix yet. But one way I'm
> thinking is that in create_subqueryscan_plan() we can first add the
> subquery's subplan_params to root->curOuterParams, and then replace
> outer-relation Vars in scan_clauses afterwards. That can make us be
> able to share the same PARAM_EXEC slot for the same Var that both
> belongs to the subquery's uplevel vars and to the NestLoop's
> outer-relation vars. To be concrete, something like attached.
>
> With this change the same query runs much faster and the Memoize runtime
> stats looks more normal.
>
> explain (analyze, costs off)
> select * from tenk1 t1 left join lateral
> (select t1.two as t1two, * from tenk1 t2 offset 0) s
> on t1.two = s.two;
> QUERY PLAN
>
> --------------------------------------------------------------------------------------
> Nested Loop Left Join (actual time=0.063..21177.476 rows=50000000 loops=1)
> -> Seq Scan on tenk1 t1 (actual time=0.025..5.415 rows=10000 loops=1)
> -> Memoize (actual time=0.001..0.234 rows=5000 loops=10000)
> Cache Key: t1.two, t1.two
> Cache Mode: binary
> Hits: 9998 Misses: 2 Evictions: 0 Overflows: 0 Memory Usage:
> 2735kB
> -> Subquery Scan on s (actual time=0.009..5.169 rows=5000
> loops=2)
> Filter: (t1.two = s.two)
> Rows Removed by Filter: 5000
> -> Seq Scan on tenk1 t2 (actual time=0.006..4.050
> rows=10000 loops=2)
> Planning Time: 0.593 ms
> Execution Time: 22486.621 ms
> (12 rows)
>
> Any thoughts?
>

+1

it would be great to fix this problem - I've seen this issue a few times.

Regards

Pavel

> Thanks
> Richard
>

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Stephen Frost 2023-10-20 12:35:04 Re: [PoC/RFC] Multiple passwords, interval expirations
Previous Message Kumar, Sachin 2023-10-20 11:14:32 RE: Initial Schema Sync for Logical Replication