セルフホストされた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 を無効にし、毎時発生していたエラーが停止するか監視しています。