ReactionLikeSynchronizer reprocessa curtidas de PostAction já descartadas a cada hora

Encontramos um problema em um site Discourse auto-hospedado em que o ReactionLikeSynchronizer parece estar disparando erros 500/504 para ações dos usuários.

O job parece estar repetidamente marcando como lixadas as mesmas 26.766 curtidas de PostAction a cada hora, mesmo que todas já tenham sido marcadas como excluídas. Durante essas execuções de sincronização mais longas, as solicitações normais dos usuários parecem ficar bloqueadas no PostgreSQL e atingir o tempo limite de 30 segundos do Pitchfork.

Ambiente

  • Discourse auto-hospedado na versão v2026.10.0-latest-277-ge1f6bd561
  • PostgreSQL 18.4
  • discourse_reactions_like_sync_enabled = true
  • Este é um site antigo, portanto há uma grande quantidade de dados históricos de reações

Relatos dos Usuários

  • As reações às vezes falham e produzem a mensagem “Algo deu errado”.
  • Tópicos visitados por um usuário às vezes permanecem marcados como não lidos
  • Os relatos estão agrupados em torno de uma vez por hora, no início da hora.

Logs e Dados

13:58:58 ReactionLikeSynchronizer inicia
13:59:50 Tempo limite do worker do Pitchfork / 500
14:00:02 Tempo limite do worker do Pitchfork / 500
14:00:18 Tempo limite do worker do Pitchfork / 500
14:00:48 ReactionLikeSynchronizer concluído

15:00:09 sincronização inicia
15:01:16 tempo limite da solicitação / 500
15:01:40 tempo limite da solicitação / 500
15:01:45 sincronização concluída

Saída do Sincronizador

Inseridas 0 curtidas de post action, recuperadas 0 curtidas de post action lixadas. (0 no total)
Lixadas 26766 curtidas de post action.

Notavelmente, a mesma contagem de 26766 aparece em todas as execuções que verifiquei.

Conteúdo do Banco de Dados

SELECT
  COUNT(*) AS total,
  COUNT(*) FILTER (WHERE pa.deleted_at IS NULL) AS currently_active,
  COUNT(*) FILTER (WHERE pa.deleted_at IS NOT NULL) AS already_trashed
FROM post_actions pa
JOIN discourse_reactions_reaction_users ru
  ON ru.user_id = pa.user_id
 AND ru.post_id = pa.post_id
JOIN discourse_reactions_reactions r
  ON r.id = ru.reaction_id
WHERE pa.post_action_type_id = 2
  AND r.reaction_value IN (
    '-1','poop','face_with_symbols_on_mouth','enraged_face',
    'face_vomiting','nauseated_face','angry','kitchen_knife',
    'water_pistol','bomb','clown_face','unamused_face','roll_eyes',
    'confused','smirking_face','disappointed_face','pensive_face',
    'lying_face','weary_face','tired_face','confounded_face',
    'persevering_face','smiling_face_with_horns','angry_face_with_horns',
    'cigarette','person_gesturing_no','prohibited','no_entry',
    'dagger','cross_mark'
  );
 total | currently_active | already_trashed

-------+------------------+-----------------

 26766 |                0 |           26766

Observações

Para o que vale, parece que ReactionLikeSynchronizer#trash_excluded_post_actions corresponde a curtidas que foram excluídas, mesmo que já tenham sido removidas. A consulta parece ser esta:

UPDATE post_actions
SET deleted_at = NOW()
FROM discourse_reactions_reaction_users ru
INNER JOIN discourse_reactions_reactions
  ON discourse_reactions_reactions.id = ru.reaction_id
WHERE post_actions.user_id = ru.user_id
  AND post_actions.post_id = ru.post_id
  AND post_actions.post_action_type_id = :like
  AND discourse_reactions_reactions.reaction_value IN (:excluded_from_like)
RETURNING post_actions.id

Notavelmente, isso não exclui ações já removidas por meio de algo como AND post_actions.deleted_at IS NULL.

Isso parece resultar no mesmo conjunto de linhas sendo reprocessado a cada hora. Posso adicionar um stack trace, se necessário.

Como solução temporária, desativei discourse_reactions_like_sync_enabled e estou monitorando se os erros deixam de ocorrer a cada hora.

Editado para corrigir erros de digitação e ajustar a consulta na seção “Notas”.