Резюме
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.
Корневая причина
Существует два отдельных порога, которые вместе создают разрыв:
- Задача запускается, когда количество reviewables выше
min_priority_thresholdсоставляет не менееmin_reviewables(15) — это грубая проверка, игнорирующая требование:target_count. - Однако расчет процентилей, определяющий
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, но у каждого из них только один флаг или один рецензент, что не является редкостью для небольших или средних сообществ.
Предлагаемые исправления (варианты)
- Обеспечить, чтобы запрос процентилей возвращал достаточное количество строк перед записью
- После выполнения запроса процентилей проверить, равно ли значение процентиля
0, и были ли возвращены какие-либо строки подзапросом.
Если строк не было возвращено, не перезаписывать существующее значениеpriority_high; вместо этого пропустить запись, сохранить предыдущее значение или использовать значение по умолчанию из конфигурации. - Это самый безопасный и наименее инвазивный подход.
- После выполнения запроса процентилей проверить, равно ли значение процентиля
- Настроить триггер задачи с учетом
target_count- Изменить предварительную проверку задачи так, чтобы она запускалась только тогда, когда количество reviewables, удовлетворяющих условию
HAVING COUNT(*) >= :target_count, составляет не менееmin_reviewables.
Иными словами, подсчитывать reviewables, сгруппированные поid, которые удовлетворяют условиюCOUNT(*) >= target_count, и запускать задачу только если этот подсчет ≥min_reviewables.
- Изменить предварительную проверку задачи так, чтобы она запускалась только тогда, когда количество reviewables, удовлетворяющих условию
- Дать администраторам возможность вручную задавать
score_to_hide_postилиpriority_high- Добавить опцию в административный интерфейс для прямого ввода или изменения значений
score_to_hide_postилиpriority_high. - Таким образом, даже если запрос процентилей выдает неожиданные результаты (например, из-за недостаточного количества выборок), система сможет использовать разумный порог, заданный администратором, предотвращая ошибки, вызванные автоматическими расчетами.
- Добавить опцию в административный интерфейс для прямого ввода или изменения значений