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

Reply via email to