Поиск по почте в 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 есть в регламенте эксплуатации, который мы ведём для клиентских серверов.
- поиск по заголовкам (тема, отправитель) идёт по кэшу Dovecot и от FTS-индекса не зависит
- без индекса Flatcurve поиск по телу — полный перебор писем: минуты ожидания и таймауты
- после обновления mailcow до 2025-01 FTS отключается автоматически
- старые индексы Solr несовместимы с Flatcurve и не мигрируют
Что изменилось: 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 во время каждого следующего обновления сам предлагает его удалить, если он ещё существует. Реальная работа была не в переносе старых данных (индекс всё равно не мигрирует), а в правильном включении нового движка и первичной индексации с нуля.
- Flatcurve встроен в Dovecot, отдельный контейнер/том не нужен
- индексы лежат в томе `vmail-index`, отдельная папка `fts-flatcurve` на каждую IMAP-папку
- старый `solr-vol-1` можно и нужно удалить — mailcow сам предложит это при обновлении
- индексация идёт по папкам, а не по ящику целиком — проверяйте конкретную папку, а не «почту вообще»
Как включить 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 по очереди для каждого адреса вечером, вне рабочего времени — на почтовый ящик с полугодовым архивом переписки с поставщиками ушло около четырёх минут.
- `SKIP_FTS=n` в mailcow.conf + `docker compose up -d` — включить движок
- `doveadm fts rescan` — чинит рассинхронизацию индекса, НЕ строит индекс заново
- `doveadm index -u user@domain '*'` — реальная (пере)индексация одного ящика
- `doveadm index -A '*'` — все ящики разом, самый рискованный вариант по нагрузке
Ловушка №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 ящиков «ТрофейЛавки» я эти параметры не трогал, дефолтов хватило с запасом.
- автоиндексация срабатывает при накоплении ≥20 непроиндексированных писем (по умолчанию) либо при самом поиске
- на больших активных ящиках один новый документ может временно «положить» поиск до докстройки индекса
- обходной путь из Issue — `fts_autoindex_max_recent_msgs=999` плюс параметры Dovecot 2.4; на Dovecot 2.3 их аналоги — `fts_enforced` и `fts_index_timeout`
- это тюнинг для крупных ящиков, а не рекомендация по умолчанию для всех
Ловушка №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 письма и его адресом отправителя, а не переиндексировать всё подряд вслепую.
- `InvalidArgumentError: Term too long (> 245)` — аномально длинный токен в адресе отправителя ломает индексацию письма
- исправлено на уровне mailcow: адресные токены при индексации теперь обрезаются
- актуальная сборка mailcow эту конкретную ошибку на новых письмах не даёт
- если всё же встретилась — обновить mailcow, затем переиндексировать точечно, не всё подряд
Чек-лист: диагностика поиска в 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 и тестовый поиск на одном контрольном ящике с заведомо известным письмом внутри. Если поиск не находит контрольное письмо в течение суток после обновления — это сигнал разбираться сразу, а не через неделю, когда накопится десяток жалоб и потерянного рабочего времени сотрудников.
- проверить конфиг: `SKIP_FTS`, `FTS_PROCS`, `FTS_HEAP` в mailcow.conf
- `doveadm fts rescan -u` — быстрая мягкая починка, пробовать первой
- `docker compose logs dovecot-mailcow` — грепать по fts/flatcurve/index
- полная `doveadm index -u '*'` — только вне рабочего времени, с контролем нагрузки
- не откатывать обновление и не выключать FTS насовсем — искать причину до конца
Частые вопросы
Почему после обновления 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, а не запускать индексацию всех пользователей разом в рабочий день.
Источники
- mailcow docs: Full-Text Search — Переход Solr → Flatcurve с 2025-01, структура индекса в томе vmail-index, команды doveadm fts rescan / doveadm index, параметры FTS_PROCS и FTS_HEAP. https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-fts/
- mailcow blog: Janmooary 2025 Update — Официальное объявление о замене Solr на Flatcurve, автоматическое отключение SKIP_FTS после обновления, авто-индексация при 20+ новых письмах, предупреждение о нагрузке CPU. https://mailcow.email/posts/2025/release-2025-01/
- GitHub Issue #6320: Unable to reliably use FTS when using Flatcurve on Dovecot with large mailbox — Кейс с ящиком на 170 000 писем: поиск ломается на новом письме, обходные настройки fts.conf (fts_autoindex_max_recent_msgs, fts_search_add_missing, fts_search_timeout, fts_search_read_fallback). https://github.com/mailcow/mailcow-dockerized/issues/6320
- GitHub Issue #5986: Flatcurve: Problems with long E-Mails — Точная формулировка ошибки InvalidArgumentError: Term too long (> 245) при переиндексации письма с длинным адресным токеном, дата 06.08.2024. https://github.com/mailcow/mailcow-dockerized/issues/5986
- GitHub PR #6006: flatcurve-fts: limit tokenizers size in e-mail adress — Исправление, ограничивающее размер адресных токенов при индексации Flatcurve, закрывает Issue #5986. https://github.com/mailcow/mailcow-dockerized/pull/6006



