Откройте 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 на живом потоке. Автообучение плюс ручное дообучение:
# ручное обучение из 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:
greylist = 4;
add_header = 6;
rewrite_subject = 8; # опционально
reject = 15;Эти числа — стартовая точка, а не догма. Подбирайте под свой FPR по логам фазы прогрева, не копируйте вслепую.
Фаза 4. Переключение milter. Переводим Postfix на rspamd, SA оставляем установленным как fallback на неделю. В main.cf:
smtpd_milters = inet:localhost:11332
non_smtpd_milters = inet:localhost:11332
milter_protocol = 6
milter_mail_macros = i {mail_addr} {client_addr} {client_ptr} {auth_authen}
milter_default_action = acceptmilter_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:
authenticated {
authenticated = "yes";
apply {
actions { reject = null; }
symbols_disabled = ["RBL_SENDERSCORE", "R_SPF_FAIL"];
}
}rspamd умеет подписывать DKIM сам через плагин dkim_signing — в большинстве случаев это позволяет выкинуть opendkim из цепочки. Генерируем ключ и настраиваем домен:
rspamadm dkim_keygen -s mail -d evilmail.pro -k /var/lib/rspamd/dkim/evilmail.pro.mail.key/etc/rspamd/local.d/dkim_signing.conf:
domain {
evilmail.pro {
selector = "mail";
path = "/var/lib/rspamd/dkim/evilmail.pro.mail.key";
}
}DNS-запись: mail._domainkey.evilmail.pro TXT "v=DKIM1; k=rsa; p=<base64>" — публичную часть печатает dkim_keygen.
Остальные грабли из практики:
- 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).
- [ ] Закрыть веб-контроллер паролем + nginx/SSH-туннель.
- [ ] Проверить эффективный конфиг через
rspamadm configdump. - [ ] Прогреть байес и fuzzy 2-4 недели, чистить корпус от дублей.
- [ ] Настроить
settings.confдля authenticated: no reject на исходящих. - [ ] Перенести DKIM в
dkim_signing, обновить DNS TXT, выключить opendkim. - [ ] Замапить
required_score 5.0на actions, подобрать пороги по своему FPR. - [ ] Переключить
Проверить, что всё живо, можно в одну строку: rspamc < message.eml покажет сработавшие символы и итоговый score. Для контроля спам-детекта скормите GTUBE-строку, для проверки clamav — EICAR. Если на реальном трафике фазы прогрева FPR держится около нуля, а спам стабильно уезжает в add_header/reject — вы готовы перехватывать milter. На инфраструктуре evilmail.pro именно так и выстроен приём: rspamd на C-демоне, байес и fuzzy в Redis, DKIM подписывается на месте, а старая Perl-цепочка осталась только в истории миграции.


