Re: Adding LEFT JOIN to a query has increased execution time 10 times - Mailing list pgsql-general

From Alexander Farber
Subject Re: Adding LEFT JOIN to a query has increased execution time 10 times
Date
Msg-id CAADeyWgOOKMyyhEm1DV5btpZ_0+dGdqAsMcA7HuWF4-nXFeq_g@mail.gmail.com
Whole thread Raw
In response to Re: Adding LEFT JOIN to a query has increased execution time 10 times  (Alexander Farber <alexander.farber@gmail.com>)
Responses Re: Adding LEFT JOIN to a query has increased execution time 10times  (legrand legrand <legrand_legrand@hotmail.com>)
Re: Adding LEFT JOIN to a query has increased execution time 10 times  (Andrew Gierth <andrew@tao11.riddles.org.uk>)
List pgsql-general
Good evening, thank you for the useful hints!

With the further help of the IRC folks the query has been optimized (7-10 seconds -> 0.3 second) by adding the following indices:

CREATE INDEX ON words_games (player1, COALESCE(finished, 'INFINITY'));
CREATE INDEX ON words_games (player2, COALESCE(finished, 'INFINITY'));
CREATE INDEX ON words_moves (gid, played DESC);
CREATE INDEX ON words_social (uid, stamp DESC);
CREATE INDEX ON words_geoip USING SPGIST (block);

and switching to LEFT JOIN LATERAL for finding the most recent records in words_moves and words_social tables:

SELECT
...
-- find move record with the most recent timestamp
LEFT JOIN LATERAL (SELECT * FROM words_moves m WHERE m.gid = g.gid ORDER BY m.played DESC LIMIT 1) AS m ON TRUE
LEFT JOIN words_geoip i2 ON (CASE WHEN g.player1 = in_uid THEN u2.ip ELSE u1.ip END) <<= i2.block
LEFT JOIN LATERAL (SELECT * FROM words_social AS s1 WHERE s1.uid = u1.uid ORDER BY s1.stamp DESC LIMIT 1) AS s1 ON TRUE
LEFT JOIN LATERAL (SELECT * FROM words_social AS s2 WHERE s2.uid = u2.uid ORDER BY s2.stamp DESC LIMIT 1) AS s2 ON TRUE
...
WHERE in_uid IN (g.player1, g.player2)
AND COALESCE(g.finished, 'INFINITY') > (CURRENT_TIMESTAMP - INTERVAL '1 day')

Also I have increased the following parameters  in postgresql.conf -

from_collapse_limit = 24
join_collapse_limit = 24

Now the whole query looks as following and the EXPLAIN output pasted is below -

Query - http://dpaste.com/1AQM800
Explain - https://explain.depesz.com/s/pq79

        EXPLAIN ANALYZE SELECT
                        g.gid,
                        EXTRACT(EPOCH FROM g.created)::int AS created,
                        EXTRACT(EPOCH FROM g.finished)::int AS finished,
                        g.letters AS letters,
                        g.values AS values,
                        g.bid AS bid,
                        CARDINALITY(g.pile) AS pilelen,
                        m.tiles AS tiles,
                        m.score AS score,
                        CASE WHEN g.player1 = 5 THEN g.player1 ELSE g.player2 END AS player1,
                        CASE WHEN g.player1 = 5 THEN g.player2 ELSE g.player1 END AS player2,
                        CASE WHEN g.player1 = 5 THEN g.score1  ELSE g.score2  END AS score1,
                        CASE WHEN g.player1 = 5 THEN g.score2  ELSE g.score1  END AS score2,
                        CASE WHEN g.player1 = 5 THEN g.state1  ELSE g.state2  END AS state1,
                        CASE WHEN g.player1 = 5 THEN g.hint1   ELSE g.hint2   END AS hint1,
                        CASE WHEN g.player1 = 5 THEN g.chat1   ELSE g.chat2   END AS chat1,
                        u1.elo AS elo1,
                        u2.elo AS elo2,                        
                        i2.lat AS lat2,
                        i2.lng AS lng2,                        
                        s1.given AS given1,
                        s2.given AS given2,
                        s1.photo AS photo1,
                        s2.photo AS photo2,
                        EXTRACT(EPOCH FROM CASE WHEN g.player1 = 5 THEN g.played1 ELSE g.played2 END)::int AS played1,
                        EXTRACT(EPOCH FROM CASE WHEN g.player1 = 5 THEN g.played2 ELSE g.played1 END)::int AS played2,
                        ARRAY_TO_STRING(CASE WHEN g.player1 = 5 THEN g.hand1 ELSE g.hand2 END, '') AS hand1,
                        CASE
                                WHEN g.finished IS NOT NULL THEN NULL
                                WHEN g.player2 IS NULL THEN NULL
                                WHEN g.player1 = 5 AND g.played1 < g.played2
                                        THEN EXTRACT(EPOCH FROM g.played2 + interval '24 hour' - CURRENT_TIMESTAMP)::int
                                WHEN g.player2 = 5 AND (g.played2 IS NULL OR g.played2 < g.played1)
                                        THEN EXTRACT(EPOCH FROM g.played1 + interval '24 hour' - CURRENT_TIMESTAMP)::int
                                ELSE NULL
                        END AS left1,
                        CASE
                                WHEN g.finished IS NOT NULL THEN NULL
                                WHEN g.player2 IS NULL THEN NULL
                                WHEN g.player1 = 5 AND (g.played2 IS NULL OR g.played2 < g.played1)
                                        THEN EXTRACT(EPOCH FROM g.played1 + interval '24 hour' - CURRENT_TIMESTAMP)::int
                                WHEN g.player2 = 5 AND g.played1 < g.played2
                                        THEN EXTRACT(EPOCH FROM g.played2 + interval '24 hour' - CURRENT_TIMESTAMP)::int
                                ELSE NULL
                        END AS left2
                FROM words_games g
                LEFT JOIN LATERAL (select tiles, score from words_moves m where m.gid = g.gid order by m.played desc limit 1) as m on true
                LEFT JOIN words_users u1 ON u1.uid = 5
                LEFT JOIN words_users u2 ON u2.uid = (CASE WHEN g.player1 = 5 THEN g.player2 ELSE g.player1 END)
                LEFT JOIN words_geoip i2 ON (CASE WHEN g.player1 = 5 THEN u2.ip ELSE u1.ip END) <<= i2.block              
                LEFT JOIN LATERAL (select * from words_social as s1 where s1.uid = u1.uid order by s1.stamp desc limit 1) as s1 on true
                LEFT JOIN LATERAL (select * from words_social as s2 where s2.uid = u2.uid order by s2.stamp desc limit 1) as s2 on true
                WHERE 5 IN (g.player1, g.player2)
                AND COALESCE(g.finished,'infinity') > (CURRENT_TIMESTAMP - INTERVAL '1 day')

                                                                                                                                                 QUERY PLAN                                                                                                                                                
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=514.37..18613546.07 rows=1029592 width=1430) (actual time=0.095..0.287 rows=8 loops=1)
   ->  Nested Loop Left Join  (cost=514.09..9921215.61 rows=1029592 width=1780) (actual time=0.086..0.247 rows=8 loops=1)
         ->  Nested Loop Left Join  (cost=513.80..1352436.19 rows=1029592 width=1696) (actual time=0.081..0.229 rows=8 loops=1)
               ->  Nested Loop Left Join  (cost=10.29..925.20 rows=67 width=1694) (actual time=0.038..0.111 rows=8 loops=1)
                     ->  Nested Loop Left Join  (cost=10.01..476.76 rows=67 width=1679) (actual time=0.035..0.090 rows=8 loops=1)
                           ->  Nested Loop Left Join  (cost=9.72..467.62 rows=67 width=1664) (actual time=0.030..0.081 rows=8 loops=1)
                                 ->  Bitmap Heap Scan on words_games g  (cost=9.29..263.40 rows=67 width=1456) (actual time=0.020..0.038 rows=8 loops=1)
                                       Recheck Cond: (((5 = player1) AND (COALESCE(finished, 'infinity'::timestamp with time zone) > (CURRENT_TIMESTAMP - '1 day'::interval))) OR ((5 = player2) AND (COALESCE(finished, 'infinity'::timestamp with time zone) > (CURRENT_TIMESTAMP - '1 day'::interval))))
                                       Filter: (COALESCE(finished, 'infinity'::timestamp with time zone) > (CURRENT_TIMESTAMP - '1 day'::interval))
                                       Heap Blocks: exact=8
                                       ->  BitmapOr  (cost=9.29..9.29 rows=67 width=0) (actual time=0.014..0.014 rows=0 loops=1)
                                             ->  Bitmap Index Scan on words_games_player1_coalesce_idx  (cost=0.00..4.33 rows=4 width=0) (actual time=0.008..0.008 rows=1 loops=1)
                                                   Index Cond: ((5 = player1) AND (COALESCE(finished, 'infinity'::timestamp with time zone) > (CURRENT_TIMESTAMP - '1 day'::interval)))
                                             ->  Bitmap Index Scan on words_games_player2_coalesce_idx  (cost=0.00..4.92 rows=63 width=0) (actual time=0.006..0.006 rows=7 loops=1)
                                                   Index Cond: ((5 = player2) AND (COALESCE(finished, 'infinity'::timestamp with time zone) > (CURRENT_TIMESTAMP - '1 day'::interval)))
                                 ->  Limit  (cost=0.43..3.03 rows=1 width=216) (actual time=0.004..0.005 rows=1 loops=8)
                                       ->  Index Scan using words_moves_gid_played_idx on words_moves m  (cost=0.43..107.05 rows=41 width=216) (actual time=0.004..0.004 rows=1 loops=8)
                                             Index Cond: (gid = g.gid)
                           ->  Materialize  (cost=0.29..8.31 rows=1 width=15) (actual time=0.001..0.001 rows=1 loops=8)
                                 ->  Index Scan using words_users_pkey on words_users u1  (cost=0.29..8.30 rows=1 width=15) (actual time=0.004..0.004 rows=1 loops=1)
                                       Index Cond: (uid = 5)
                     ->  Index Scan using words_users_pkey on words_users u2  (cost=0.29..6.69 rows=1 width=15) (actual time=0.002..0.002 rows=1 loops=8)
                           Index Cond: (uid = CASE WHEN (g.player1 = 5) THEN g.player2 ELSE g.player1 END)
               ->  Bitmap Heap Scan on words_geoip i2  (cost=503.51..20018.14 rows=15367 width=23) (actual time=0.014..0.014 rows=1 loops=8)
                     Recheck Cond: (CASE WHEN (g.player1 = 5) THEN u2.ip ELSE u1.ip END <<= block)
                     Heap Blocks: exact=7
                     ->  Bitmap Index Scan on words_geoip_block_idx  (cost=0.00..499.67 rows=15367 width=0) (actual time=0.013..0.013 rows=1 loops=8)
                           Index Cond: (CASE WHEN (g.player1 = 5) THEN u2.ip ELSE u1.ip END <<= block)
         ->  Limit  (cost=0.29..8.30 rows=1 width=180) (actual time=0.002..0.002 rows=1 loops=8)
               ->  Index Scan using words_social_uid_stamp_idx on words_social s1  (cost=0.29..8.30 rows=1 width=180) (actual time=0.002..0.002 rows=1 loops=8)
                     Index Cond: (uid = u1.uid)
   ->  Limit  (cost=0.29..8.30 rows=1 width=180) (actual time=0.002..0.002 rows=1 loops=8)
         ->  Index Scan using words_social_uid_stamp_idx on words_social s2  (cost=0.29..8.30 rows=1 width=180) (actual time=0.002..0.002 rows=1 loops=8)
               Index Cond: (uid = u2.uid)
 Planning time: 0.587 ms
 Execution time: 0.367 ms
(36 rows)

I was told that it still could be improved (by rearranging WHERE clauses?)

Regards
Alex

pgsql-general by date:

Previous
From: Ron
Date:
Subject: Re: About SSL connection
Next
From: Adrian Klaver
Date:
Subject: Re: About SSL connection