要約
バックグラウンドジョブ Jobs::ReviewablePriorities が、パーセンタイルクエリが行を返さない場合に priority_#{priorities[:high]} を 0 として書き込むため、score_to_hide_post が 0 になる可能性があります。このジョブは reviewable_count >= 15 の場合にトリガーされますが、パーセンタイル計算には HAVING COUNT(*) >= :target_count (通常 target_count は 2) を満たすレビュー対象のみが含まれます。ほとんどのレビュー対象が1つのフラグしか持たない場合、パーセンタイルサブクエリは何も返さず、high は 0 になり、score_to_hide_post = ((high * ratio) * scale).truncate(2) は 0 に評価されます。これにより、非表示のしきい値がゼロに収縮し、不正確な非表示動作が発生します。
由来(関連コード/事実)
score_to_hide_postは以下を通じて計算されます。
score_to_hide_post = ((high.to_f * ratio) * scale).truncate(2)
highはプラグインストアから読み取られます。
PluginStore.get("reviewables", "priority_#{priorities[:high]}")
- そのプラグインストアのエントリは
Jobs::ReviewablePriorities(システムジョブ) によって書き込まれます。
ジョブは以下の場合に実行されます。
reviewable_count = Reviewable.approved.where("score > ?", min_priority_threshold).count
return if reviewable_count < self.class.min_reviewables
ここで self.class.min_reviewables は 15 です。
- ジョブは SQL を使用して
highを計算します。
SELECT COALESCE(PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY score), 0.0) AS medium,
COALESCE(PERCENTILE_DISC(0.85) WITHIN GROUP (ORDER BY score), 0.0) AS high
FROM (
SELECT r.score
FROM reviewables AS r
INNER JOIN reviewable_scores AS rs ON rs.reviewable_id = r.id
WHERE r.score > :min_priority AND r.status = 1
GROUP BY r.id
HAVING COUNT(*) >= :target_count
) AS x
:target_count は通常 2 です。
原因
2つの別々のしきい値が組み合わさってギャップが生じます。
- ジョブは、
min_priority_thresholdを超えるレビュー対象が少なくともmin_reviewables(15) 個ある場合にトリガーされます。これは:target_count要件を無視した大まかなカウントです。 - しかし、
highを生成するパーセンタイル計算には、COUNT(*) >= :target_count(つまり、少なくとも2つのレビュー対象スコア) を満たすレビュー対象のみが含まれます。多くのレビュー対象がそれぞれ1つのフラグしか持たない場合、サブクエリは行を返さず、パーセンタイルはフォールバックの0.0を返します。
そのため、ジョブは実行される可能性があります (大まかなカウントで ≥ 15 個のレビュー対象があるため) が、パーセンタイル集計には適切な行がなく (HAVING を満たすものがないため)、high が 0 になり、その結果 priority_high が 0 として書き込まれます。これが score_to_hide_post にフィードされ、収縮します。
影響
score_to_hide_postが0になり、誤って投稿が非表示とみなされたり、合理的な非表示しきい値に依存するロジックが壊れたりする可能性があります。- これは、多くのレビュー対象が存在するが、それぞれが単一のフラグ/レビュアーしか持たないサイトで発生します。これは、小規模または中規模のコミュニティでは珍しくありません。
提案される修正(オプション)
- 書き込む前にパーセンタイルクエリが十分な行を返すことを保証する
- パーセンタイルクエリを実行した後、パーセンタイル値が
0であり、サブクエリが行を返したかどうかを確認します。 - 行が返されなかった場合は、既存の
priority_highを上書きせず、書き込みをスキップするか、以前の値を使用するか、設定済みのデフォルト値にフォールバックします。 - これは最も安全で、侵襲性の低いアプローチです。
target_countを考慮してジョブトリガーを調整する
HAVING COUNT(*) >= :target_countを満たすレビュー対象の数がmin_reviewables以上の場合にのみ実行されるように、ジョブの事前チェックを変更します。
つまり、COUNT(*) >= target_countを満たすレビュー対象をidでグループ化してカウントし、そのカウントが ≥min_reviewablesの場合にのみ続行します。
- 管理者が
score_to_hide_postまたはpriority_highを手動で設定できるようにする
- 管理インターフェースで
score_to_hide_postまたはpriority_highを直接入力または調整するオプションを提供します。 - これにより、パーセンタイルクエリが予期しない結果を生成した場合でも (例: サンプルが少なすぎるため)、システムは管理者が指定した合理的なしきい値を使用でき、自動計算によるエラーを防ぐことができます。