ReactionLikeSynchronizer re-traite les « J'aime » des PostAction déjà supprimées chaque heure

Nous avons rencontré un problème sur un site Discourse auto-hébergé dans lequel ReactionLikeSynchronizer semble déclencher des erreurs 500/504 pour les actions des utilisateurs.

Le travail semble mettre répétitivement à la corbeille les mêmes 26 766 likes de PostAction chaque heure, alors que tous ont déjà été marqués comme supprimés. Pendant ces exécutions de synchronisation plus longues, les requêtes utilisateur normales semblent être bloquées dans PostgreSQL et atteindre le délai d’attente de 30 secondes de Pitchfork.

Environnement

  • Discourse auto-hébergé en version v2026.10.0-latest-277-ge1f6bd561
  • PostgreSQL 18.4
  • discourse_reactions_like_sync_enabled = true
  • Il s’agit d’un ancien site, il y a donc beaucoup de données de réactions historiques

Signalements des utilisateurs

  • Les réactions échouent parfois et affichent un message « Quelque chose s’est mal passé ».
  • Les sujets visités par un utilisateur restent parfois marqués comme non lus
  • Les signalements sont regroupés environ une fois par heure, en début d’heure.

Journaux et données

13:58:58 Début de ReactionLikeSynchronizer
13:59:50 Délai d'attente du travailleur Pitchfork / 500
14:00:02 Délai d'attente du travailleur Pitchfork / 500
14:00:18 Délai d'attente du travailleur Pitchfork / 500
14:00:48 Fin de ReactionLikeSynchronizer

15:00:09 Début de la synchronisation
15:01:16 Délai d'attente de la requête / 500
15:01:40 Délai d'attente de la requête / 500
15:01:45 Fin de la synchronisation

Sortie du synchroniseur

0 likes d'action de publication insérés, 0 likes d'action de publication récupérés de la corbeille. (0 au total)
26766 likes d'action de publication mis à la corbeille.

Il est à noter que le même nombre de 26766 apparaît lors de chaque exécution que j’ai vérifiée.

Contenu de la base de données

SELECT
  COUNT(*) AS total,
  COUNT(*) FILTER (WHERE pa.deleted_at IS NULL) AS actuellement_actifs,
  COUNT(*) FILTER (WHERE pa.deleted_at IS NOT NULL) AS deja_mis_a_la_corbeille
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 | actuellement_actifs | deja_mis_a_la_corbeille

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

 26766 |                0 |           26766

Remarques

Pour ce que ça vaut, il semble que ReactionLikeSynchronizer#trash_excluded_post_actions corresponde aux likes qui ont été exclus, même s’ils ont déjà été supprimés. La requête ressemble à ceci :

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

Il est à noter que cela n’exclut pas les actions supprimées via quelque chose comme AND post_actions.deleted_at IS NULL.

Cela semble entraîner le re-traitement du même ensemble de lignes chaque heure. Je peux ajouter une pile d’exceptions si nécessaire.

En tant que solution de contournement, j’ai désactivé discourse_reactions_like_sync_enabled et je surveille si les erreurs cessent de se produire chaque heure.

Modifié pour corriger les fautes de frappe et la requête dans la section « Notes ».