mailcow: поиск не находит письма — ломаный Flatcurve
АйТи Фреш
Linux, Docker и DevOps

Поиск по почте в mailcow не находит письма: ломаный индекс Flatcurve после обновления

Автор: , директор ООО «АйТи-Фреш» · · ~16 мин чтения
Полнотекстовый поиск mailcow не находит письма из-за сломанного или неполного индекса Flatcurve
Письма на месте, но индекс поиска пуст или недостроен — это не потеря почты, а отдельная проблема.

Если после обновления mailcow сотрудники не находят письма по тексту, а строка поиска просто пустая — почти всегда виноват переход на новый движок Flatcurve, а не сломанная почта. Разбираю, что изменилось с января 2025 года, почему поиск иногда работает ровно до первого нового письма и что делать с ошибкой Term too long.

Симптом: письма есть, поиск их не находит

«ТрофейЛавка» — охотничий магазин на 19 рабочих мест, у них почта на mailcow: заказы от поставщиков оружия и снаряжения, переписка с таможенными брокерами по разрешительным документам, обращения клиентов. Мы обслуживаем их корпоративную почту с 2023 года. В феврале 2025 года, после планового обновления на релиз 2025-01, менеджер по закупкам написал: не могу найти письмо от поставщика по слову из темы, хотя письмо точно есть, я его вижу в папке глазами.

Первая проверка — убедиться, что дело не в правах и не в конкретном ящике. Поиск по тексту письма в SOGo у всех пользователей крутился по минуте и дольше, а на больших ящиках обрывался по таймауту клиента и возвращал пустой список; при этом поиск по отправителю и теме отвечал быстро. Это два разных механизма: заголовки Dovecot берёт из своего кэша (dovecot.index.cache), а тело письма без FTS-плагина он вынужден искать, открывая и читая каждое сообщение подряд. Полнотекстового индекса, который избавляет от этого перебора, в тот момент просто не существовал.

Причина обнаружилась сразу в changelog обновления: с релиза 2025-01 mailcow заменил движок полнотекстового поиска Solr на Flatcurve. Официальная документация формулирует это прямо — с января 2025 года Solr заменён на Flatcurve, все существующие индексы FTS устарели и подлежат удалению. И что важнее для симптома «поиск молчит»: после обновления до 2025-01 полнотекстовый поиск автоматически отключается в целях безопасности, и включить его нужно вручную. Подробный разбор всех пороговых уведомлений и служебных процедур после обновления mailcow есть в регламенте эксплуатации, который мы ведём для клиентских серверов.

Первым делом при жалобе «поиск не работает» проверьте `grep SKIP_FTS mailcow.conf`. Если стоит `SKIP_FTS=y` после недавнего обновления — это почти наверняка объясняет всё.

Что изменилось: Solr → Flatcurve и где теперь лежит индекс

Flatcurve — не косметическая замена, а другой движок с другой архитектурой хранения. Solr работал как отдельный сервис в своём контейнере и со своим томом solr-vol-1, который приходилось поддерживать и обновлять независимо. Flatcurve встроен прямо в Dovecot как FTS-плагин на базе библиотеки Xapian и не требует отдельного контейнера или тома — документация подчёркивает, что Flatcurve работает даже на менее мощных системах и в перспективе станет FTS-движком Dovecot по умолчанию.

Индексы Flatcurve хранятся в уже существующем томе vmail-index, по одной подпапке fts-flatcurve на каждую IMAP-папку пользователя. Структура выглядит так: /var/vmail_index/user@domain/.INBOX/fts-flatcurve/index.NNN/ с файлами flintlock, iamglass, postlist.glass, termlist.glass внутри. То есть у каждой папки почтового ящика — свой независимый индекс, и это объясняет, почему в некоторых случаях у пользователя может искаться, например, «Входящие», но не искаться «Архив»: индексация идёт по папкам, а не по ящику целиком.

Для «ТрофейЛавки» это означало, что после обновления solr-vol-1 стал мёртвым грузом — mailcow во время каждого следующего обновления сам предлагает его удалить, если он ещё существует. Реальная работа была не в переносе старых данных (индекс всё равно не мигрирует), а в правильном включении нового движка и первичной индексации с нуля.

Если Solr у вас был подключён давно и с кастомными настройками памяти в docker-compose.override.yml — не забудьте убрать эти оверрайды при переходе, иначе они просто повиснут мёртвым грузом в конфиге.
Схема индексации писем в mailcow: от Maildir через Dovecot и Flatcurve к индексу в томе vmail-index
Flatcurve хранит индекс прямо в томе почты, отдельный сервис Solr больше не нужен.

Как включить Flatcurve и пересобрать индекс правильно

Включение — одна строка в конфиге и пересоздание стека. В mailcow.conf меняете SKIP_FTS=y на SKIP_FTS=n, затем docker compose up -d. В свежих установках generate_config.sh сразу пишет SKIP_FTS=n. А при обновлении старой установки update.sh дописывает в mailcow.conf недостающие параметры, и для SKIP_FTS он дописывает именно y (вместе с FTS_HEAP=128 и FTS_PROCS=1) — защитная мера, чтобы не запускать тяжёлую переиндексацию без ведома администратора. Применяется правка только пересозданием контейнера через docker compose up -d: restart или перезагрузка хоста оставляют контейнер со старыми переменными.

# mailcow.conf
SKIP_FTS=n

docker compose up -d

Дальше — вопрос, с которым путаются чаще всего: doveadm fts rescan и doveadm index — это НЕ взаимозаменяемые команды, хотя обе относятся к FTS и обе упоминаются в одном разделе документации. rescan сверяет, какие письма уже проиндексированы, с тем, что реально есть в ящике, и убирает из индекса ссылки на удалённые сообщения — то есть чинит рассинхронизацию между тем, что Dovecot знает об индексе, и тем, что реально лежит в Maildir, но не строит индекс заново с нуля. Если индекса не существует вообще (как было у «ТрофейЛавки» сразу после включения Flatcurve), одного rescan недостаточно — он просто подтвердит, что индексировать нечего, и поиск продолжит молчать. Реальная (пере)индексация — это doveadm index:

# один пользователь
docker compose exec dovecot-mailcow doveadm index -u user@domain '*'

# все пользователи — медленнее и рискованнее, не запускать в рабочее время
docker compose exec dovecot-mailcow doveadm index -A '*'

Документация прямо предупреждает: индексация может занять время, возможна повышенная нагрузка на систему, вплоть до зависаний в редких случаях, поэтому процесс стоит запускать под наблюдением и не встраивать в UI намеренно — управлять им нужно только через CLI. Для «ТрофейЛавки» с их девятнадцатью ящиками я прогнал doveadm index -u по очереди для каждого адреса вечером, вне рабочего времени — на почтовый ящик с полугодовым архивом переписки с поставщиками ушло около четырёх минут.

Не запускайте `doveadm index -A` на проде в рабочее время. Даже на 19 ящиках это заметная нагрузка на CPU и диск; на сотнях ящиков — риск деградации всей почты на время индексации.
Сравнение команд doveadm fts rescan и doveadm index в mailcow: что чинит рассинхронизацию, а что строит индекс заново
rescan чинит рассинхронизацию, а настоящую переиндексацию делает только doveadm index.

Ловушка №1: поиск работает после индексации и ломается на первом новом письме

После первичной индексации у «ТрофейЛавки» поиск действительно заработал — и это первое, что все проверяют и на чём успокаиваются. Но есть сценарий, задокументированный в Issue #6320 на GitHub: на ящике с 170 000 писем поиск после doveadm index -A '*' возвращал около 38 000 совпадений по тестовой фразе, но стоило прийти одному новому письму от другого провайдера — следующий поиск переставал возвращать результаты вообще, а через некоторое время IMAP-клиент выдавал случайную ошибку. Похожий принцип «не всё, что выглядит стабильным, стабильно под нагрузкой» я разбирал и для самого Docker Compose в проде — в best practices для небольшого бизнеса.

Причина в том, как Dovecot ведёт себя, когда индекс отстал от ящика. Документация mailcow описывает автоиндексацию упрощённо («при 20 и более новых письмах или при поиске»), но в самом Dovecot параметр fts_autoindex_max_recent_msgs = 20 из mailcow-овского fts.conf работает как фильтр: папки, где писем с флагом \Recent больше этого числа, из автоматической индексации исключаются. Когда при поиске Dovecot видит, что индекс неполон, он пытается доиндексировать хвост, а если не успевает или индексация падает — по умолчанию откатывается на поиск без FTS, то есть открывает письма подряд. На ящике в 10–20 тысяч писем это лишние секунды. На 170 тысячах — минуты, и клиент отваливается по таймауту: именно так автор Issue и объяснил свой случай.

Автор Issue приводит обходной путь — правки в data/conf/dovecot/conf.d/fts.conf: fts_autoindex_max_recent_msgs=999 (не исключать из автоиндексации папки с большим числом свежих писем), fts_search_add_missing=yes (доиндексировать недостающее прямо при поиске), fts_search_timeout=30s (сколько ждать доиндексации) и fts_search_read_fallback=no (не откатываться на перебор писем). Тут есть подвох, который я проверил по документации Dovecot: три последних параметра — это имена из Dovecot 2.4, а mailcow сейчас собран на Dovecot 2.3.21, где секция plugin молча принимает любые ключи. В ветке 2.3 то же самое задаётся параметрами fts_enforced (значения no/yes/body — падать ли с ошибкой вместо отката на перебор) и fts_index_timeout (в fts.conf mailcow уже стоит 300s). Так что копировать строки из Issue буквально бессмысленно — сначала проверьте версию Dovecot командой docker compose exec dovecot-mailcow dovecot --version. Важная оговорка: это обходной путь из обсуждения конкретной проблемы на большом ящике, а не универсальный профиль настроек, который стоит копировать не глядя на маленький почтовый сервер — для 19 ящиков «ТрофейЛавки» я эти параметры не трогал, дефолтов хватило с запасом.

Прежде чем копировать чужой fts.conf из обсуждения на GitHub — оцените размер и активность своих ящиков. На типовом офисе до полусотни человек дефолтные настройки Flatcurve обычно справляются без тюнинга.

Ловушка №2: InvalidArgumentError Term too long при переиндексации

Второй частый сбой ловится не по «поиск не находит», а прямо на этапе doveadm index: команда завершается с ошибкой вида InvalidArgumentError: Term too long (> 245) и указывает конкретный UID письма. По документированному в Issue #5986 случаю, причина — аномально длинный токен внутри адреса отправителя (в примере из обсуждения это закодированный технический email вида au+mq6tanjq...@survey.pledgebox, десятки символов подряд без разделителей), который Xapian, лежащий в основе Flatcurve, отказывается принимать как единый термин индекса.

Проблема была закрыта на уровне самого mailcow ещё в августе 2024 года, до официального релиза Flatcurve: PR #6006 ограничивает размер токенов при индексации — в штатном fts.conf сейчас стоят fts_tokenizer_email_address = maxlen=100 и fts_tokenizer_generic = algorithm=simple maxlen=30, чтобы такие письма не валили весь процесс переиндексации. В любом официальном релизе с Flatcurve (2025-01 и новее) эта правка уже есть. Встретить ошибку вживую сейчас реалистичнее, если кто-то правил data/conf/dovecot/conf.d/fts.conf руками и потерял строки с maxlen, — поэтому первым делом сравните свой fts.conf со штатным из репозитория.

Если ошибка всё же появилась — паниковать не о чем: doveadm index -u user@domain '*' останавливается на проблемном письме, но остальной ящик к этому моменту чаще всего уже проиндексирован. Первый шаг — обновить mailcow до актуальной версии (git pull в каталоге mailcow-dockerized и ./update.sh, если ещё не сделано), затем повторить индексацию точечно по конкретной папке. Если ошибка повторяется даже на свежей версии — это повод завести отдельный тикет с конкретным UID письма и его адресом отправителя, а не переиндексировать всё подряд вслепую.

Ошибка на одном письме не означает, что сломан весь индекс. Проверьте прогресс по конкретной папке, прежде чем перезапускать индексацию всего ящика заново.
Дерево диагностики поломанного поиска mailcow: конфиг SKIP_FTS, разрыв на новых письмах, ошибка Term too long
Три разных симптома одной проблемы требуют трёх разных проверок, а не одной общей переиндексации.

Чек-лист: диагностика поиска в mailcow за 10 минут

Порядок, которым я прохожу жалобу «поиск не находит письма» на любом клиентском mailcow. Сначала — конфиг: grep -E 'SKIP_FTS|FTS_PROCS|FTS_HEAP' mailcow.conf покажет, включён ли движок вообще и с какими лимитами он работает. Дальше — состояние индекса конкретного пользователя без запуска тяжёлой переиндексации:

# проверить и мягко починить рассинхронизацию индекса, без полной переиндексации
docker compose exec dovecot-mailcow doveadm fts rescan -u user@domain

# посмотреть логи dovecot на ошибки индексации/поиска за последний час
docker compose logs dovecot-mailcow --since 1h | grep -i -E 'fts|flatcurve|index'

Если rescan не помог и в логах пусто — переходите к полноценной doveadm index -u user@domain '*' для конкретного ящика, вне рабочего времени, с наблюдением за нагрузкой (docker stats dovecot-mailcow). Если ошибка Term too long — сначала проверьте версию mailcow и обновитесь при возможности, потом переиндексируйте точечно. Если поиск то работает, то нет именно на активных больших ящиках — посмотрите в сторону тюнинга fts_autoindex_max_recent_msgs и связанных параметров, но только на тех ящиках, где реально идёт большой поток писем, а не превентивно на весь сервер.

Что я делаю не в первую очередь: не откатываю обновление mailcow и не выключаю Flatcurve совсем, даже если решение проблемы кажется небыстрым. Полнотекстовый поиск — не критичный для доставки почты сервис, но для клиента вроде «ТрофейЛавки», где менеджеры каждый день поднимают старую переписку с поставщиками по ключевым словам, его отсутствие — это реальные потерянные часы работы, и чинить его стоит до конца, а не жить без него месяцами.

После разбора я завёл себе привычку проверять состояние FTS отдельным пунктом в послеобновленческом чек-листе mailcow, а не ждать жалобы от пользователей. Сразу после каждого крупного обновления — быстрый grep SKIP_FTS mailcow.conf и тестовый поиск на одном контрольном ящике с заведомо известным письмом внутри. Если поиск не находит контрольное письмо в течение суток после обновления — это сигнал разбираться сразу, а не через неделю, когда накопится десяток жалоб и потерянного рабочего времени сотрудников.

Если вы совсем недавно [разворачивали mailcow с нуля](https://itfresh.ru/articles/ustanovka-mailcow-poshagovo.html), эта проблема вас не коснётся — FTS включён по умолчанию на свежих установках. Она типична именно для серверов, которые прошли обновление с версий до 2025-01.

Частые вопросы

Почему после обновления mailcow полнотекстовый поиск вообще перестал работать?

С релиза 2025-01 mailcow заменил движок поиска Solr на Flatcurve, и при обновлении с версии, где использовался Solr, полнотекстовый поиск (SKIP_FTS) автоматически отключается в целях безопасности. Нужно вручную поставить SKIP_FTS=n в mailcow.conf, выполнить docker compose up -d и проиндексировать ящики заново — старые индексы Solr не совместимы с Flatcurve и не переносятся.

В чём разница между doveadm fts rescan и doveadm index?

doveadm fts rescan сверяет индекс с реальным содержимым ящика и убирает из него ссылки на удалённые письма — это починка рассинхронизации, а не построение индекса. Реальную (пере)индексацию делает doveadm index -u user@domain '*'. Спутать их — частая причина, почему администратор считает, что «переиндексировал», а поиск так и не заработал.

Почему поиск работает сразу после индексации, а потом снова перестаёт находить письма?

Когда индекс отстал от ящика, Dovecot при поиске пытается доиндексировать хвост, а если не успевает — откатывается на перебор писем без FTS. На ящиках в сотни тысяч писем такой перебор длится минутами, и клиент отваливается по таймауту. В задокументированном случае с ящиком на 170 000 писем это лечится тонкой настройкой параметров fts.conf, но это не универсальная рекомендация для небольших ящиков.

Что значит ошибка InvalidArgumentError: Term too long (> 245)?

Индексатор Flatcurve на базе Xapian отказывается принимать аномально длинный неразрывный токен — чаще всего это встречается в закодированных email-адресах отправителя. Проблема исправлена в самом mailcow параметрами `maxlen` для токенизаторов в fts.conf; если ошибка всё же появилась, проверьте, не потерялись ли эти строки при ручной правке файла.

Безопасно ли запускать полную переиндексацию всех ящиков командой doveadm index -A?

Технически да, но документация прямо предупреждает о риске повышенной нагрузки на систему вплоть до зависаний в редких случаях. На проде лучше индексировать ящики по одному вне рабочего времени и следить за нагрузкой через docker stats, а не запускать индексацию всех пользователей разом в рабочий день.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи