Tarefas de longa duração do Sidekiq

Ainda assim, ele precisa executar a subconsulta para calcular os ranks em cada tópico em toda a tabela de posts, independentemente de quantos ranks estão sendo atualizados.

Essa subconsulta leva cerca de 40 segundos para ser executada e, em seguida, está dentro de um loop aninhado que precisa ser executado para a junção antes mesmo de chegarmos à cláusula WHERE. Isso parece ser o que está inflando o custo da consulta.

SELECT id, percent_rank()                                                     
    OVER (PARTITION BY topic_id ORDER BY SCORE DESC) as percent_rank 
FROM posts
1 curtida

Analisei o código e agora entendi. Essa consulta é executada repetidamente até limpar todos os posts cujo rank precisa ser atualizado. Por isso, enquanto eu observava os processos SQL, via várias execuções diferentes dessa consulta ao longo de horas, conforme uma execução terminava e outra começava.

No entanto, parece que o LIMIT realmente não ajuda muito na complexidade da consulta. Na verdade, descobri que, se eu aumentar o limite para 200000, o plano de consulta melhora significativamente.
Com 20000, vejo um enorme loop aninhado com custo próximo a 200 milhões:

-> Nested Loop (cost=6033694.44..190270554.32 rows=26680560 width=16)

Se eu simplesmente aumentar o limite para 200000, ele gera este plano de consulta, que parece muito mais limpo:

                                                            QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
 Update on posts  (cost=7334394.89..8873863.82 rows=200000 width=833)
   ->  Nested Loop  (cost=7334394.89..8873863.82 rows=200000 width=833)
         ->  Subquery Scan on x  (cost=7334394.45..7370147.82 rows=200000 width=48)
               ->  Limit  (cost=7334394.45..7368147.82 rows=200000 width=12)
                     ->  Hash Join  (cost=7334394.45..11837188.36 rows=26680560 width=12)
                           Hash Cond: (posts_1.id = y.id)
                           Join Filter: ((posts_1.percent_rank IS NULL) OR (y.percent_rank <> posts_1.percent_rank))
                           ->  Hash Join  (cost=35403.20..2845221.73 rows=26703700 width=12)
                                 Hash Cond: (posts_1.topic_id = topics.id)
                                 ->  Seq Scan on posts posts_1  (cost=0.00..2739721.00 rows=26703700 width=16)
                                 ->  Hash  (cost=23918.11..23918.11 rows=918807 width=4)
                                       ->  Index Only Scan using topics_pkey on topics  (cost=0.42..23918.11 rows=918807 width=4)
                           ->  Hash  (cost=6834805.00..6834805.00 rows=26703700 width=12)
                                 ->  Subquery Scan on y  (cost=6033694.00..6834805.00 rows=26703700 width=12)
                                       ->  WindowAgg  (cost=6033694.00..6567768.00 rows=26703700 width=24)
                                             ->  Sort  (cost=6033694.00..6100453.25 rows=26703700 width=16)
                                                   Sort Key: posts_2.topic_id, posts_2.score DESC
                                                   ->  Seq Scan on posts posts_2  (cost=0.00..2739721.00 rows=26703700 width=16)
         ->  Index Scan using posts_pkey on posts  (cost=0.44..7.52 rows=1 width=789)
               Index Cond: (id = x.id)
 JIT:
   Functions: 30
   Options: Inlining true, Optimization true, Expressions true, Deforming true
(23 rows)

Fico me perguntando o que faz com que ele mude de comportamento. Pode valer a pena aumentar esse limite pelo menos após uma importação grande como a que fizemos, enquanto ele limpa o backlog.

2 curtidas