Перейти к содержимому

Очередь модерации переполнена

Автомодерация помечает подозрительный контент, а разбирает его человек. Если входящий поток обгоняет ревьюеров — очередь растёт, и токсичный ответ висит часами до просмотра. Ловим два сигнала: длину очереди и возраст самого старого непросмотренного элемента. Второй важнее — он про SLA, а не про объём.

Раз в несколько минут cloud-функция с timer-trigger смотрит длину и возраст головы очереди:

import os, time, requests
MAX_LEN = 50 # тревога, если ждёт больше стольких элементов
SLA_MINUTES = 30 # ничто не должно ждать ревью дольше этого
def handler(event, context):
pending = db_fetch_pending() # список dict с полем created_at (epoch)
n = len(pending)
if n == 0:
return {"statusCode": 200}
oldest_age_min = (time.time() - min(p["created_at"] for p in pending)) / 60
if oldest_age_min > SLA_MINUTES:
notify("🛡️ Модерация: просрочен SLA",
f"Самый старый элемент ждёт {oldest_age_min:.0f} мин "
f"(SLA {SLA_MINUTES} мин).\nВ очереди: {n}.",
priority=9)
elif n > MAX_LEN:
notify("🛡️ Очередь модерации растёт",
f"В очереди: {n} (лимит {MAX_LEN}).\n"
f"Самый старый: {oldest_age_min:.0f} мин.",
priority=7)
return {"statusCode": 200}
def notify(title, message, priority):
requests.post(f"{os.environ['NOTIFLY_URL']}/message",
params={"token": os.environ["NOTIFLY_TOKEN"]},
json={"title": title, "message": message, "priority": priority},
timeout=5)

Возраст (SLA) идёт с приоритетом выше длины: маленькая, но застоявшаяся очередь опаснее большой, но быстро разбираемой.

2. Дедупликация, пока очередь не разобрали

Заголовок раздела «2. Дедупликация, пока очередь не разобрали»

Проверка бежит каждые 5 минут, а очередь может висеть переполненной час — не хочется двенадцать одинаковых пушей. Флаг с TTL:

import redis
R = redis.from_url(os.environ["REDIS_URL"])
def notify_once(title, message, priority, cooldown=1800):
if R.set("alert:" + title, "1", nx=True, ex=cooldown):
notify(title, message, priority)

3. Подкрепить heartbeat: «ревьюеры вообще работают?»

Заголовок раздела «3. Подкрепить heartbeat: «ревьюеры вообще работают?»»

Отдельный тихий сигнал — не то что очередь длинная, а то что её перестали разбирать (все ушли на выходные, воркер завис). Пусть код, обрабатывающий элемент, пингует heartbeat при каждом ревью:

def on_item_reviewed(item):
save_decision(item)
requests.get(os.environ["MODERATION_PING_URL"], timeout=5) # heartbeat

Задайте intervalSec под ожидаемый темп ревью — и если ревью встало, Notifly пришлёт push сам, даже когда очередь ещё в пределах лимита.

  • длину очереди и лимит;
  • возраст самого старого элемента и SLA;
  • разбивку по причинам флага (токсичность, PII, спам);
  • ссылку на дашборд модерации через extras с URL.