Score_to_hide_post становится 0, когда у каждого reviewables только один флаг

Резюме

score_to_hide_post может обнуляться, так как фоновая задача Jobs::ReviewablePriorities записывает priority_#{priorities[:high]} как 0, когда запрос на вычисление процентилей не возвращает ни одной строки. Эта задача запускается при условии reviewable_count >= 15, однако расчет процентилей учитывает только те reviewables, которые удовлетворяют условию HAVING COUNT(*) >= :target_count (где target_count обычно равен 2). Если большинство reviewables имеют только один флаг, подзапрос для процентилей возвращает пустой результат → 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.

  • Задача вычисляет high с помощью SQL:
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.


Корневая причина

Существует два отдельных порога, которые вместе создают разрыв:

  1. Задача запускается, когда количество reviewables выше min_priority_threshold составляет не менее min_reviewables (15) — это грубая проверка, игнорирующая требование :target_count.
  2. Однако расчет процентилей, определяющий high, учитывает только те reviewables, у которых COUNT(*) >= :target_count (то есть как минимум 2 записи в reviewable_scores). Если у многих reviewables есть только один флаг, подзапрос не возвращает строк, и процентиль возвращает значение по умолчанию 0.0.

Таким образом, задача может запуститься (поскольку по грубой проверке есть ≥ 15 reviewables), но агрегация процентилей не находит подходящих строк (так как ни одна не удовлетворяет условию HAVING), из-за чего high становится 0, а затем priority_high записывается как 0. Это значение передается в score_to_hide_post, обнуляя его.


Влияние

  • score_to_hide_post становится равным 0, что может привести к некорректному скрытию постов или нарушению логики, зависящей от разумного порога скрытия.
  • Эта проблема возникает на сайтах, где существует много reviewables, но у каждого из них только один флаг или один рецензент, что не является редкостью для небольших или средних сообществ.

Предлагаемые исправления (варианты)

  1. Обеспечить, чтобы запрос процентилей возвращал достаточное количество строк перед записью
    • После выполнения запроса процентилей проверить, равно ли значение процентиля 0, и были ли возвращены какие-либо строки подзапросом.
      Если строк не было возвращено, не перезаписывать существующее значение priority_high; вместо этого пропустить запись, сохранить предыдущее значение или использовать значение по умолчанию из конфигурации.
    • Это самый безопасный и наименее инвазивный подход.
  2. Настроить триггер задачи с учетом target_count
    • Изменить предварительную проверку задачи так, чтобы она запускалась только тогда, когда количество reviewables, удовлетворяющих условию HAVING COUNT(*) >= :target_count, составляет не менее min_reviewables.
      Иными словами, подсчитывать reviewables, сгруппированные по id, которые удовлетворяют условию COUNT(*) >= target_count, и запускать задачу только если этот подсчет ≥ min_reviewables.
  3. Дать администраторам возможность вручную задавать score_to_hide_post или priority_high
    • Добавить опцию в административный интерфейс для прямого ввода или изменения значений score_to_hide_post или priority_high.
    • Таким образом, даже если запрос процентилей выдает неожиданные результаты (например, из-за недостаточного количества выборок), система сможет использовать разумный порог, заданный администратором, предотвращая ошибки, вызванные автоматическими расчетами.