BUG #16241: Degraded hash join performance

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

BUG #16241: Degraded hash join performance

PG Doc comments form
The following bug has been logged on the website:

Bug reference:      16241
Logged by:          Thomas Butz
Email address:      [hidden email]
PostgreSQL version: 12.1
Operating system:   Modified postgres:12 docker image (Debian Buster)
Description:        

I'm currently analyzing a slowdown in the performance of our OSM tile
rendering toolchain. While the following query finishes quite fast with
PostgreSQL 11.4/Postgis 2.5 it takes a lot longer using PostgreSQL
12.1/Postgis 3.0:

SELECT ST_AsBinary("way") AS geom,"construction","highway","name","tunnel"
FROM (SELECT
            way,
            CASE WHEN substr(highway, length(highway)-4, 5) = '_link' THEN
substr(highway, 0, length(highway)-4) ELSE highway END,
            CASE WHEN (tunnel = 'yes' OR tunnel = 'building_passage' OR
covered = 'yes') THEN 'yes' ELSE 'no' END AS tunnel,
            construction,
            localized_streetname as name,
            CASE
              WHEN oneway IN ('yes', '-1') THEN oneway
              WHEN junction IN ('roundabout') AND (oneway IS NULL OR NOT
oneway IN ('no', 'reversible')) THEN 'yes'
              ELSE NULL
            END AS oneway,
            horse, bicycle
          FROM planet_osm_line l
          JOIN (VALUES -- this join is also putting a condition on what is
selected. features not matching it do not make it into the results.
              ('motorway', 380),
              ('trunk', 370),
              ('primary', 360),
              ('secondary', 350),
              ('tertiary', 340),
              ('residential', 330),
              ('unclassified', 330),
              ('road', 330),
              ('living_street', 320),
              ('pedestrian', 310),
              ('raceway', 300),
              ('motorway_link', 240),
              ('trunk_link', 230),
              ('primary_link', 220),
              ('secondary_link', 210),
              ('tertiary_link', 200),
              ('service', 150),
              ('construction', 10)
            ) AS ordertable (highway, prio)
            USING (highway)
          WHERE highway IN ('motorway', 'motorway_link', 'trunk',
'trunk_link', 'primary', 'primary_link', 'secondary', 'secondary_link',
'tertiary',
                            'tertiary_link', 'residential', 'unclassified',
'road', 'service', 'pedestrian', 'raceway', 'living_street',
'construction')
            AND (name IS NOT NULL
              OR oneway IN ('yes', '-1')
              OR junction IN ('roundabout'))
          ORDER BY
            z_order DESC, -- put important roads first
            COALESCE(layer, 0), -- put top layered roads first
            length(name) DESC, -- Try to fit big labels in first
            name DESC, -- Force a consistent ordering between differently
named streets
            l.osm_id DESC -- Force an ordering for streets of the same name,
e.g. dualized roads
        ) AS roads_text_name WHERE "way" &&
ST_SetSRID('BOX3D(1222380.956336539 6339381.37785938,1233387.888409604
6350388.309932444)'::box3d, 3857)

I've uploaded detailed EXPLAINE ANALYZE output for both scenarios:

PostgreSQL 11.4 (execution time: 140.5ms)
https://explain.depesz.com/s/BsO7

PostgreSQL 12.1 (execution time: 3394.2ms)
https://explain.depesz.com/s/TMLO

The settings/hardware on both machines is nearly identical. The only
difference is the imported data volume which is higher for the 12.1
server(europe vs germany/austria/switzerland) but that shouldn't impact the
hash join performance as the number of rows at this point should be roughly
the same.

The only culprit seems to be that the hash join takes a lot longer to finish
and i can't figure out why.

Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Andres Freund
Hi,

On 2020-02-03 15:33:02 +0000, PG Bug reporting form wrote:

> The following bug has been logged on the website:
>
> Bug reference:      16241
> Logged by:          Thomas Butz
> Email address:      [hidden email]
> PostgreSQL version: 12.1
> Operating system:   Modified postgres:12 docker image (Debian Buster)
> Description:        
>
> I'm currently analyzing a slowdown in the performance of our OSM tile
> rendering toolchain. While the following query finishes quite fast with
> PostgreSQL 11.4/Postgis 2.5 it takes a lot longer using PostgreSQL
> 12.1/Postgis 3.0:

Hm. This sounds somehow familar.

I first thought it might be a bug that I'm literally just now am trying
to find again, because I made a note to fix it before the next set of
minor releases. Without writing down the bug number :(.

But the bug I am thinking of was around hash *aggregates* and subplans,
not joins.

It'd be useful if you could get a perf profile with callgraphs. You
would have to install debugging symbols for both postgres and postgis,
and then do something like:

SELECT pg_backend_pid(); to get the backend pid
perf record --call-graph dwarf -p $pid_of_session
and then run the query a times in the session you got the pid for
and then cancel the perf record run with ctrl-c

You can then look at the perf profile interactively with
perf report --no-children
and send the created file of
perf report --no-children > somefile-no-children
perf report --children > somefile-children

- Andres


Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Thomas Butz
In reply to this post by PG Doc comments form
I've executed the query twice and attached the generated reports.


> Hi,
>
> On 2020-02-03 15:33:02 +0000, PG Bug reporting form wrote:
>> The following bug has been logged on the website:
>>
>> Bug reference:      16241
>> Logged by:          Thomas Butz
>> Email address:      [hidden email]
>> PostgreSQL version: 12.1
>> Operating system:   Modified postgres:12 docker image (Debian Buster)
>> Description:
>>
>> I'm currently analyzing a slowdown in the performance of our OSM tile
>> rendering toolchain. While the following query finishes quite fast with
>> PostgreSQL 11.4/Postgis 2.5 it takes a lot longer using PostgreSQL
>> 12.1/Postgis 3.0:
>
> Hm. This sounds somehow familar.
>
> I first thought it might be a bug that I'm literally just now am trying
> to find again, because I made a note to fix it before the next set of
> minor releases. Without writing down the bug number :(.
>
> But the bug I am thinking of was around hash *aggregates* and subplans,
> not joins.
>
> It'd be useful if you could get a perf profile with callgraphs. You
> would have to install debugging symbols for both postgres and postgis,
> and then do something like:
>
> SELECT pg_backend_pid(); to get the backend pid
> perf record --call-graph dwarf -p $pid_of_session
> and then run the query a times in the session you got the pid for
> and then cancel the perf record run with ctrl-c
>
> You can then look at the perf profile interactively with
> perf report --no-children
> and send the created file of
> perf report --no-children > somefile-no-children
> perf report --children > somefile-children
>
> - Andres

Thomas Butz

report-children.txt (2M) Download Attachment
report-no-children.txt (132K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Andres Freund
In reply to this post by PG Doc comments form
Hi,


On 2020-02-04 10:02:45 +0100, Thomas Butz wrote:
> I've executed the query twice and attached the generated reports.

Interesting! The no-children one clearly shows that a lot of the the
time is spent evaluating regular expressions (there's other regex
functions in the profile too):

    23.36%  postgres  postgres            [.] subcolor

and that this is inside a stack of plpgsql functions:

               |                     |          |--18.95%--exec_stmt_block
               |                     |          |          exec_stmt
               |                     |          |          plpgsql_exec_function
               |                     |          |          plpgsql_call_handler
               |                     |          |          ExecInterpExpr
               |                     |          |          ExecEvalExpr (inlined)
               |                     |          |          exec_eval_simple_expr
               |                     |          |          (inlined)
[...]
               |                     |          |          plpgsql_exec_function
               |                     |          |          plpgsql_call_handler
               |                     |          |          ExecInterpExpr
               |                     |          |          ExecEvalExpr (inlined)
               |                     |          |          exec_eval_simple_expr (inlined)
[...]
               |                     |          |          plpgsql_exec_function
               |                     |          |          plpgsql_call_handler
               |                     |          |          ExecInterpExpr
               |                     |          |          ExecEvalExpr (inlined)
[...]


I'm not aware of any relevant regular expression evaluation changes
between 11 and 12. Tom, does this trigger anything?

Thomas, could you provide a EXPLAIN (ANALYZE, VERBOSE) for both
versions? Clearly the hash join is where the time is spent:

        ->  Hash Join  (cost=1.000..6436.030 rows=44 width=517) (actual time=5.532..3383.213 rows=2779 loops=1)
                Buffers: shared hit=9077
              ->  Index Scan using planet_osm_hstore_line_way_idx on planet_osm_hstore_line planet_osm_hstore_line  (cost=0.550..6419.380 rows=491 width=352) (actual time=0.423..19.147 rows=2779 loops=1)
                      Index Cond: (planet_osm_hstore_line.way && '01030000A0110F00000100000005000000AF78D2F4ECA632411CD92E58CD2E58410000000000000000AF78D2F4ECA63241E4EED5138D3958410000000000000000D3CF6EE3EBD13241E4EED5138D3958410000000000000000D3CF6EE3EBD132411CD92E58CD2E58410000000000000000AF78D2F4ECA632411CD92E58CD2E58410000000000000000'::geometry)
                      Filter: ((((planet_osm_hstore_line.tags -> 'name'::text) IS NOT NULL) OR ((planet_osm_hstore_line.tags -> 'oneway'::text) = ANY ('{yes,-1}'::text[])) OR ((planet_osm_hstore_line.tags -> 'junction'::text) = 'roundabout'::text)) AND ((planet_osm_hstore_line.tags -> 'highway'::text) = ANY ('{motorway,motorway_link,trunk,trunk_link,primary,primary_link,secondary,secondary_link,tertiary,tertiary_link,residential,unclassified,road,service,pedestrian,raceway,living_street,construction}'::text[])))
                      Buffers: shared hit=9077
              ->  Hash  (cost=0.230..0.230 rows=18 width=32) (actual time=0.029..0.030 rows=18 loops=1)
                    ->  Values Scan  (cost=0.000..0.230 rows=18 width=32) (actual time=0.003..0.017 rows=18 loops=1)

but I don't think it's the hash condition itself - there's no plpgsql
functions necessary for the join as far as I can tell. A VERBOSE EXPLAIN
might help up with knowing what the function being called actually is.

It seems quite possible that all that time is spent inside plpgsql
functions that are part of postgis. And that the postgis version
difference between 2.5 and 3.0 is the relevant part here, not the
postgres version difference. Or it's just in the application code:

The sort step shows among others the following column to be sorted on:
osml10n_get_streetname_from_tags(planet_osm_hstore_line.tags, true, false, ' - '::text, 'de'::text, planet_osm_hstore_line.way, NULL::text)) DESC
which is actually likely computed as the output of the HashJoin
node. And is the only obvious candidate for a plpgsql function
potentially taking a lot of the time.

Is that function defined exactly the same between the version? Is the
underlying dataset roughly the same size?  I suggest running the query
without it, and checking how long it takes.

Regards,

Andres


Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Thomas Butz
I was able to reduce it to a minimal example:

SELECT localized_streetname
FROM planet_osm_line
WHERE "way" && ST_SetSRID('BOX3D(1222380.956336539 6339381.37785938,1233387.888409604 6350388.309932444)'::box3d, 3857)

Your diagnosis with osml10n_get_streetname_from_tags was spot on. I'm going to report it to the author of the osml10n extension.

Thank you for your help!

PS: I've created the execution plans using EXPLAIN (ANALYZE, VERBOSE, COSTS, BUFFERS, TIMING, SUMMARY, FORMAT json) but i wasn't aware that i might have to check the "Output" section of a node to find potential performance problems. How do you find such slow outputs without using perf?

> Von: "Andres Freund" <[hidden email]>
> An: "Thomas Butz" <[hidden email]>, "Tom Lane" <[hidden email]>
> CC: "pgsql-bugs" <[hidden email]>
> Gesendet: Dienstag, 4. Februar 2020 14:44:08
> Betreff: Re: BUG #16241: Degraded hash join performance

> Hi,
>
>
> On 2020-02-04 10:02:45 +0100, Thomas Butz wrote:
>> I've executed the query twice and attached the generated reports.
>
> Interesting! The no-children one clearly shows that a lot of the the
> time is spent evaluating regular expressions (there's other regex
> functions in the profile too):
>
>    23.36%  postgres  postgres            [.] subcolor
>
> and that this is inside a stack of plpgsql functions:
>
>               |                     |          |--18.95%--exec_stmt_block
>               |                     |          |          exec_stmt
>               |                     |          |          plpgsql_exec_function
>               |                     |          |          plpgsql_call_handler
>               |                     |          |          ExecInterpExpr
>               |                     |          |          ExecEvalExpr (inlined)
>               |                     |          |          exec_eval_simple_expr
>               |                     |          |          (inlined)
> [...]
>               |                     |          |          plpgsql_exec_function
>               |                     |          |          plpgsql_call_handler
>               |                     |          |          ExecInterpExpr
>               |                     |          |          ExecEvalExpr (inlined)
>               |                     |          |          exec_eval_simple_expr (inlined)
> [...]
>               |                     |          |          plpgsql_exec_function
>               |                     |          |          plpgsql_call_handler
>               |                     |          |          ExecInterpExpr
>               |                     |          |          ExecEvalExpr (inlined)
> [...]
>
>
> I'm not aware of any relevant regular expression evaluation changes
> between 11 and 12. Tom, does this trigger anything?
>
> Thomas, could you provide a EXPLAIN (ANALYZE, VERBOSE) for both
> versions? Clearly the hash join is where the time is spent:
>
>        ->  Hash Join  (cost=1.000..6436.030 rows=44 width=517) (actual
>        time=5.532..3383.213 rows=2779 loops=1)
>                Buffers: shared hit=9077
>              ->  Index Scan using planet_osm_hstore_line_way_idx on planet_osm_hstore_line
>              planet_osm_hstore_line  (cost=0.550..6419.380 rows=491 width=352) (actual
>              time=0.423..19.147 rows=2779 loops=1)
>                      Index Cond: (planet_osm_hstore_line.way &&
>                      '01030000A0110F00000100000005000000AF78D2F4ECA632411CD92E58CD2E58410000000000000000AF78D2F4ECA63241E4EED5138D3958410000000000000000D3CF6EE3EBD13241E4EED5138D3958410000000000000000D3CF6EE3EBD132411CD92E58CD2E58410000000000000000AF78D2F4ECA632411CD92E58CD2E58410000000000000000'::geometry)
>                      Filter: ((((planet_osm_hstore_line.tags -> 'name'::text) IS NOT NULL) OR
>                      ((planet_osm_hstore_line.tags -> 'oneway'::text) = ANY ('{yes,-1}'::text[])) OR
>                      ((planet_osm_hstore_line.tags -> 'junction'::text) = 'roundabout'::text)) AND
>                      ((planet_osm_hstore_line.tags -> 'highway'::text) = ANY
>                      ('{motorway,motorway_link,trunk,trunk_link,primary,primary_link,secondary,secondary_link,tertiary,tertiary_link,residential,unclassified,road,service,pedestrian,raceway,living_street,construction}'::text[])))
>                      Buffers: shared hit=9077
>              ->  Hash  (cost=0.230..0.230 rows=18 width=32) (actual time=0.029..0.030 rows=18
>              loops=1)
>                    ->  Values Scan  (cost=0.000..0.230 rows=18 width=32) (actual time=0.003..0.017
>                    rows=18 loops=1)
>
> but I don't think it's the hash condition itself - there's no plpgsql
> functions necessary for the join as far as I can tell. A VERBOSE EXPLAIN
> might help up with knowing what the function being called actually is.
>
> It seems quite possible that all that time is spent inside plpgsql
> functions that are part of postgis. And that the postgis version
> difference between 2.5 and 3.0 is the relevant part here, not the
> postgres version difference. Or it's just in the application code:
>
> The sort step shows among others the following column to be sorted on:
> osml10n_get_streetname_from_tags(planet_osm_hstore_line.tags, true, false, ' -
> '::text, 'de'::text, planet_osm_hstore_line.way, NULL::text)) DESC
> which is actually likely computed as the output of the HashJoin
> node. And is the only obvious candidate for a plpgsql function
> potentially taking a lot of the time.
>
> Is that function defined exactly the same between the version? Is the
> underlying dataset roughly the same size?  I suggest running the query
> without it, and checking how long it takes.
>
> Regards,
>
> Andres

--

Thomas Butz


Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Andres Freund
Hi,

On 2020-02-04 15:49:10 +0100, Thomas Butz wrote:
> I was able to reduce it to a minimal example:
>
> SELECT localized_streetname
> FROM planet_osm_line
> WHERE "way" && ST_SetSRID('BOX3D(1222380.956336539 6339381.37785938,1233387.888409604 6350388.309932444)'::box3d, 3857)
>
> Your diagnosis with osml10n_get_streetname_from_tags was spot on. I'm going to report it to the author of the osml10n extension.

Cool. Still worth checking whether there's a 11 vs 12 issue inside that function.



> Thank you for your help!
>
> PS: I've created the execution plans using EXPLAIN (ANALYZE, VERBOSE, COSTS, BUFFERS, TIMING, SUMMARY, FORMAT json) but i wasn't aware that i might have to check the "Output" section of a node to find potential performance problems. How do you find such slow outputs without using perf?

It's not trivial, unfortunately :(. You can use track_functions to get
more insight, but that's better for server-wide analysis, than query
specific ones.  auto_explain also has options to track nested queries,
which might help as well.

I hope we can extend EXPLAIN with more information about this kind of
thing at some point.

Regards,

Andres


Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Tom Lane-2
In reply to this post by Andres Freund
Andres Freund <[hidden email]> writes:
> Interesting! The no-children one clearly shows that a lot of the the
> time is spent evaluating regular expressions (there's other regex
> functions in the profile too):
>     23.36%  postgres  postgres            [.] subcolor

Huh ...

> I'm not aware of any relevant regular expression evaluation changes
> between 11 and 12. Tom, does this trigger anything?

(1) Nope, I'm not either; the last non-back-patched change in that
code was c54159d44 in v10.

(2) subcolor() is part of regex compilation, not execution, which makes
one wonder why it's showing up at all.  Maybe the regex cache in
adt/regexp.c is overflowing and preventing useful caching?  But
that didn't change in v12 either.  Are these test cases really
100% equivalent?  I'm wondering if there are a few more "hot"
regex patterns in the v12 data ...

(3) Where the heck is the regex use coming from at all?  I don't
see any regex operators in the plan.  Maybe it's inside the
plpgsql function?

                        regards, tom lane


Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Andres Freund
Hi,

On 2020-02-04 11:00:29 -0500, Tom Lane wrote:

> Andres Freund <[hidden email]> writes:
> > Interesting! The no-children one clearly shows that a lot of the the
> > time is spent evaluating regular expressions (there's other regex
> > functions in the profile too):
> >     23.36%  postgres  postgres            [.] subcolor
>
> Huh ...
>
> > I'm not aware of any relevant regular expression evaluation changes
> > between 11 and 12. Tom, does this trigger anything?
>
> (1) Nope, I'm not either; the last non-back-patched change in that
> code was c54159d44 in v10.
>
> (2) subcolor() is part of regex compilation, not execution, which makes
> one wonder why it's showing up at all.  Maybe the regex cache in
> adt/regexp.c is overflowing and preventing useful caching?  But
> that didn't change in v12 either.  Are these test cases really
> 100% equivalent?  I'm wondering if there are a few more "hot"
> regex patterns in the v12 data ...

They are not 100% equivalent, but the part of the plan we see is very
similar rowcount wise. It's possible that the functions differ more
however, there are different postgis versions involved, and apparently
also an "osml10n" extension.


> (3) Where the heck is the regex use coming from at all?  I don't
> see any regex operators in the plan.  Maybe it's inside the
> plpgsql function?

It definitely is. The stack shows at least two levels of plpgsql
functions. And Thomas has since confirmed that removing the functioncall
fixes the issue.

Based on the name I think this is somewhere around this:
https://github.com/giggls/mapnik-german-l10n/blob/master/plpgsql/get_localized_name_from_tags.sql#L120
The callgraph indicates that most of the cost comes from within
textregexreplace_noopt.

Not clear why the cache isn't fixing this - there are no variables in
the regexp_replace calls as far as I can see.

Greetings,

Andres Freund


Reply | Threaded
Open this post in threaded view
|

Re: BUG #16241: Degraded hash join performance

Thomas Butz
I've opened an issue: https://github.com/giggls/mapnik-german-l10n/issues/40

I suspect that the number of executed regexp_replace calls is the culprit here.
The cache of regexp.c seems to be limited to 32 entries which might be to low to keep all involved regexes cached.


> Hi,
>
> On 2020-02-04 11:00:29 -0500, Tom Lane wrote:
>> Andres Freund <[hidden email]> writes:
>> > Interesting! The no-children one clearly shows that a lot of the the
>> > time is spent evaluating regular expressions (there's other regex
>> > functions in the profile too):
>> >     23.36%  postgres  postgres            [.] subcolor
>>
>> Huh ...
>>
>> > I'm not aware of any relevant regular expression evaluation changes
>> > between 11 and 12. Tom, does this trigger anything?
>>
>> (1) Nope, I'm not either; the last non-back-patched change in that
>> code was c54159d44 in v10.
>>
>> (2) subcolor() is part of regex compilation, not execution, which makes
>> one wonder why it's showing up at all.  Maybe the regex cache in
>> adt/regexp.c is overflowing and preventing useful caching?  But
>> that didn't change in v12 either.  Are these test cases really
>> 100% equivalent?  I'm wondering if there are a few more "hot"
>> regex patterns in the v12 data ...
>
> They are not 100% equivalent, but the part of the plan we see is very
> similar rowcount wise. It's possible that the functions differ more
> however, there are different postgis versions involved, and apparently
> also an "osml10n" extension.
>
>
>> (3) Where the heck is the regex use coming from at all?  I don't
>> see any regex operators in the plan.  Maybe it's inside the
>> plpgsql function?
>
> It definitely is. The stack shows at least two levels of plpgsql
> functions. And Thomas has since confirmed that removing the functioncall
> fixes the issue.
>
> Based on the name I think this is somewhere around this:
> https://github.com/giggls/mapnik-german-l10n/blob/master/plpgsql/get_localized_name_from_tags.sql#L120
> The callgraph indicates that most of the cost comes from within
> textregexreplace_noopt.
>
> Not clear why the cache isn't fixing this - there are no variables in
> the regexp_replace calls as far as I can see.
>
> Greetings,
>
> Andres Freund

--

Thomas Butz