rspamd против SpamAssassin: производительность, точность и переход без даунтайма
SpamAssassin — Perl-монолит из эпохи, когда поток мерили сотнями писем в час. Разбираем, почему на современном трафике rspamd даёт тот же recall при в разы меньшей latency, и показываем пошаговый zero-downtime переход: shadow-режим, прогрев байеса, маппинг порогов и перехват milter-сокета.
EvilMail Team20 июля 2026 г.11 мин чтения
Откройте top на почтовом релее с SpamAssassin под реальной нагрузкой, и вы увидите знакомую картину: россыпь spamd-детей по 50-70 МБ каждый, amavisd, форки Perl, и время обработки одного письма, гуляющее от 200 до 800 мс. Пока идёт письмо, процесс синхронно ждёт ответа RBL, потом лезет в bayes на BerkeleyDB с файловой блокировкой, потом отдаёт тело в clamav. Три копирования тела письма по цепочке postfix → amavisd → spamassassin → clamav и ни одной параллельной проверки.
Это не баг конфигурации. Это архитектура, спроектированная тогда, когда сервер обрабатывал сотни писем в час, а не десятки тысяч. SpamAssassin по-прежнему отлично ловит спам — но платит за это латентностью и памятью, которые на потоке превращаются в очередь и в раздутый mail.log. rspamd решает ту же задачу принципиально другим способом, и на 2-3 доменах с живым трафиком переход окупается уже в первый месяц. Разберём почему — и как перейти, не устроив себе аварию в понедельник.
Где SpamAssassin упирается в потолок
Типичная зрелая связка выглядит так: Postfix отдаёт письмо в amavisd-new
(content-filter на 10024, реинжект обратно на 10025), amavis дёргает
spamd
, тот прогоняет сотни regex-правил, лезет в байес, ждёт RBL, затем clamav возвращает вердикт по вирусам. Каждый шаг — синхронный. Общее время письма — это
сумма всех ожиданий
, а не максимум из них.
Отдельная боль — обновления. Правила приезжают раз в сутки через sa-update по cron. Байес живёт в bayes_toks (BerkeleyDB) с блокировкой файла: под конкурентной записью обучение сериализуется, и на большом корпусе sa-learn начинает подтормаживать весь узел. Вынести байес в MySQL/Redis через bayes_store_module можно, но это уже подпорка к архитектуре, которая изначально файловая.
И память. Каждый spamd-ребёнок держит свой интерпретатор Perl со скомпилированными правилами. Восемь детей — это легко 400-500 МБ резидента только под антиспам, и это масштабируется линейно с параллелизмом.
Как устроен rspamd и почему он быстрее
rspamd — это event-driven демон на C с воркер-моделью. Ключевые воркеры:
normal — сканирование писем;
rspamd_proxy — milter-режим для Postfix, слушает на 11332;
controller — обучение, веб-интерфейс и статистика на 11334;
fuzzy_storage — хранилище нечётких хэшей.
Главное отличие — проверки идут параллельно. Встроенный асинхронный резолвер шлёт все DNS/RBL-запросы разом и не блокирует воркер на ожидании. Пока летят RBL-запросы, параллельно считаются SPF/DKIM/DMARC, проверяется fuzzy, опрашивается байес в Redis, отрабатывает нейросеть. rspamd не выдаёт жёсткий вердикт «спам/не спам» на каждом правиле — он копит символы (symbols), каждый со своим весом, и в конце суммирует их в единый score. Композитные правила и Lua-плагины позволяют комбинировать символы без перекомпиляции демона.
Байес в rspamd — атомарные операции в Redis, без файловых блокировок. Обучение с нескольких узлов не конфликтует, а сам классификатор доступен всем воркерам сразу.
Точность: почему цифры врут без своего корпуса
«rspamd точнее SpamAssassin» — утверждение без смысла, пока у вас нет собственного трафика. Точность антиспама измеряется на вашем ham и вашем спаме, а не на чужом бенчмарке. Считать нужно три числа: TP (пойманный спам), FP (ham, ошибочно помеченный как спам) и FN (пропущенный спам). И держать в голове главное: FP дороже FN. Пропущенное спам-письмо раздражает; зарезанное деловое письмо теряет клиента.
Где rspamd объективно богаче из коробки — это модули, которых у SA нет вообще:
neural — нейросеть поверх сработавших символов, дообучается на вашем потоке;
fuzzy_check — нечёткие хэши против массовых рассылок, привязка к fuzzy_storage;
url_reputation, ip_reputation, репутация по DKIM/SPF/DMARC;
replies — вайтлистинг ответов на исходящую переписку;
phishing, ratelimit, greylist.
Честная оговорка: ваши миллионы sa-learn-писем не переносятся 1:1. Токенизация и формат байеса другие. Байес rspamd прогревается заново — это нормально и закладывается в план миграции.
Бенчмарк на одном железе
Любые абсолютные цифры без методики — маркетинг. Правильный замер: один корпус (GTUBE-строка плюс реальный ham/spam mbox), одно железо, замер через milter в обоих случаях. Порядок величин, который вы стабильно увидите на 4 vCPU:
Latency p50/p95: синхронная цепочка SA/amavis — десятки-сотни мс на письмо; rspamd — единицы-десятки мс, потому что RBL/DNS идут параллельно.
RSS памяти: несколько spamd-детей по ~50-70 МБ каждый против одного демона rspamd плюс байес-кэш в Redis.
Пропускная способность: rspamd переваривает кратно больше писем/сек на том же ядре именно за счёт неблокирующего I/O.
Переменные, которые нужно фиксировать при замере: задержки конкретных RBL, размер байес-корпуса и включён ли clamav в цепочке — он легко становится доминирующим слагаемым.
Пошаговый переход без даунтайма
Сердце миграции — не рубить SA сразу. rspamd поднимается параллельно и сначала только смотрит.
Фаза 1. Shadow — только лог. rspamd установлен, rspamd_proxy слушает на 11332, но Postfix ещё ходит в старую цепочку через amavis. Смотрим mail.log и веб-контроллер, сверяем, что символы срабатывают адекватно, ничего не режем.
Фаза 2. Прогрев 2-4 недели. Обучаем байес и наполняем fuzzy на живом потоке. Автообучение плюс ручное дообучение:
bash
# ручное обучение из mbox
rspamc learn_spam /var/mail/corpus/spam.mbox
rspamc learn_ham /var/mail/corpus/ham.mbox
# по одному письму из пайпа
cat suspicious.eml | rspamc learn_spam
# контроль корпуса
rspamc stat # learned ham/spam, hits, версии символов
Автообучение включаем в /etc/rspamd/local.d/classifier-bayes.conf (autolearn = true;). Важно: не скармливайте дубли — обучение на повторах смещает классификатор. Корпус чистим по Message-ID.
Фаза 3. Маппинг порогов. SA required_score 5.0 переводим в actions rspamd. Ключевое отличие — у rspamd не один порог, а лестница действий. В /etc/rspamd/local.d/actions.conf:
milter_default_action = accept — это ваша страховка: если rspamd упадёт, письмо пройдёт, а не отобьётся баунсом. Откат за 30 секунд — вернуть smtpd_milters на старый сокет amavis и postfix reload.
Фаза 5. Снос. Через неделю чистых метрик выключаем amavisd и SpamAssassin. Ни в коем случае не держите amavis и rspamd-milter активными на приём одновременно — получите двойную фильтрацию и дублирующиеся заголовки.
Обучение, DKIM и грабли исходящей почты
Самая частая авария новичков — гнать полный набор символов на исходящей аутентифицированной почте. Ваши же письма получают reject по RBL или SPF-символам, а IP теряет репутацию. Лечится модулем settings — для authenticated-отправителей отключаем reject и лишние символы. В /etc/rspamd/local.d/settings.conf:
rspamd умеет подписывать DKIM сам через плагин dkim_signing — в большинстве случаев это позволяет выкинуть opendkim из цепочки. Генерируем ключ и настраиваем домен:
bash
rspamadm dkim_keygen -s mail -d evilmail.pro -k /var/lib/rspamd/dkim/evilmail.pro.mail.key
Redis не на localhost добавляет RTT на каждую байес-проверку. Для HA используйте отдельные write_servers/read_servers в local.d/redis.conf, но реплику держите близко по сети.
Greylisting-модуль rspamd конфликтует с postgrey и любым внешним greylisting-milter. Оставьте один.
Веб-контроллер (11334) не выставляйте в интернет голым. Пароль через rspamadm pw в worker-controller.inc, сверху nginx с basic auth или SSH-туннель.
Все переопределения кладём только в local.d/*.conf и override.d/, а не в rspamd.conf. Итоговый эффективный конфиг всегда проверяется одной командой: rspamadm configdump.
Чеклист миграции
[ ] Сделать бэкап байеса SA (bayes_toks) — на случай отката к старой цепочке.
[ ] Поднять Redis, проверить доступность с почтового узла.
[ ] Установить rspamd, запустить в shadow-режиме (milter слушает, Postfix ещё на amavis).
[ ] Проверить эффективный конфиг через rspamadm configdump.
[ ] Прогреть байес и fuzzy 2-4 недели, чистить корпус от дублей.
[ ] Настроить settings.conf для authenticated: no reject на исходящих.
[ ] Перенести DKIM в dkim_signing, обновить DNS TXT, выключить opendkim.
[ ] Замапить required_score 5.0 на actions, подобрать пороги по своему FPR.
[ ] Переключить smtpd_milters на 11332, milter_default_action = accept.
[ ] Держать SA как fallback неделю, мониторить FP каждый день.
[ ] Снести amavisd/SA, задокументировать все local.d-файлы в репозитории конфигов.
Проверить, что всё живо, можно в одну строку: rspamc < message.eml покажет сработавшие символы и итоговый score. Для контроля спам-детекта скормите GTUBE-строку, для проверки clamav — EICAR. Если на реальном трафике фазы прогрева FPR держится около нуля, а спам стабильно уезжает в add_header/reject — вы готовы перехватывать milter. На инфраструктуре evilmail.pro именно так и выстроен приём: rspamd на C-демоне, байес и fuzzy в Redis, DKIM подписывается на месте, а старая Perl-цепочка осталась только в истории миграции.