ReactionLikeSynchronizer が、すでにゴミ箱へ移動済みの PostAction のいいねを毎時間再処理しています

セルフホストされたDiscourseサイトにおいて、ReactionLikeSynchronizerがユーザー操作に対して500/504エラーを引き起こしている問題が発生しています。

このジョブは、すべての「いいね」(PostAction) が削除済みとしてマークされているにもかかわらず、毎時同じ26,766件の「いいね」を繰り返しゴミ箱に移動しているようです。これらの長時間にわたる同期処理中、通常のユーザーリクエストはPostgreSQLでブロックされ、Pitchforkの30秒タイムアウトに達しているようです。

環境

  • バージョン v2026.10.0-latest-277-ge1f6bd561 のセルフホストDiscourse
  • PostgreSQL 18.4
  • discourse_reactions_like_sync_enabled = true
  • 古いサイトのため、歴史的なリアクションデータが大量に存在します

ユーザーからの報告

  • リアクションが失敗し、「Something went wrong」メッセージが表示されることがあります。
  • ユーザーが訪問したトピックが、未読のまま表示されることがあります。
  • 報告は、毎時正時付近に集中しています。

ログとデータ

13:58:58 ReactionLikeSynchronizer 開始
13:59:50 Pitchfork ワーカータイムアウト / 500
14:00:02 Pitchfork ワーカータイムアウト / 500
14:00:18 Pitchfork ワーカータイムアウト / 500
14:00:48 ReactionLikeSynchronizer 完了

15:00:09 同期開始
15:01:16 リクエストタイムアウト / 500
15:01:40 リクエストタイムアウト / 500
15:01:45 同期完了

Synchronizer の出力

Inserted 0 post action likes, recovered 0 trashed post action likes. (0 total)
Trashed 26766 post action likes.

注目すべきは、確認したすべての実行で同じ26,766という数が表示されることです。

データベースの内容

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

備考

参考までに、ReactionLikeSynchronizer#trash_excluded_post_actions は、すでに削除されている場合でも、除外対象となった「いいね」にマッチするようです。クエリは以下のようになっています:

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

注目すべきは、AND post_actions.deleted_at IS NULL のような条件で削除済みアクションを除外していない点です。

これにより、同じ行セットが毎時再処理されているようです。必要に応じてスタックトレースを追加できます。

回避策として、discourse_reactions_like_sync_enabled を無効にし、毎時発生していたエラーが停止するか監視しています。

誤字の修正と「Notes」セクション内のクエリの修正を反映しました。