Снесли служебные файлы Dovecot в Maildir: почему клиенты выкачивают всю почту заново и куда делись метки
Это статья для сисадмина, который ночью освобождал место на почтовом сервере, снёс в Maildir всё, что начинается на «dovecot», и утром получил load average 14, забитый канал и полтора десятка звонков «пропали мои метки». Покажу, какие файлы в Maildir — кэш, а какие данные; почему смена UID заставляет Outlook и Thunderbird выкачать ящик с нуля; что из потерянного реально вернуть из бэкапа, а что уже нет; и как разложить каталоги так, чтобы следующая «чистка индексов» физически не могла добраться до управляющих файлов. С командами, разбором формата dovecot-uidlist и реальным ремонтом на 24 ящика в стоматологической клинике.
«Почистили кэш Dovecot» — и утром сервер в полке
Звонок в 9:20: «почта тормозит, Outlook второй час что-то качает, и пропали все мои цветные категории». Захожу на сервер — load average 14 на четырёх ядрах, iowait 64 %, исходящего трафика за утро 17 ГБ при обычных 600 МБ в сутки. Смотрю историю команд предыдущей смены и нахожу красивое: find /var/vmail -name 'dovecot*' -delete. На разделе кончилось место, человек решил «почистить индексы, они же всё равно пересоберутся».
Пересоберутся. Но не все. В каталоге Maildir рядом лежат файлы двух принципиально разных классов, и по имени они почти не различаются — и те и другие начинаются на dovecot. Одни — кэш, их можно сносить хоть каждый день, ничего не случится. Другие — данные, и их удаление эквивалентно потере части почтового ящика. Документация Dovecot говорит об этом прямым текстом, без иносказаний: индексные файлы можно удалять и пересобирать без побочных эффектов, но если удалить control files — «сообщения получат новые UID и, возможно, потеряются названия ключевых слов».
Вот эта одна фраза из мануала и стоит вам ночи ремонта плюс дня объяснений с бухгалтерией. Дальше разберу по шагам: какие именно файлы трогать нельзя, почему смена UID заставляет клиента выкачать ящик с нуля, что из потерянного реально вернуть, и как разложить каталоги так, чтобы грабли больше не собирались.
Сразу успокою по одному пункту, потому что паника обычно начинается не с того. Сами письма при такой «чистке» не пропадают. Ни одного байта. Файлы в cur/ и new/ лежат как лежали, find по маске dovecot* их не трогает. Вы потеряли не почту, вы потеряли метаданные о почте — и это лечится дешевле, чем восстановление 240 ГБ из бэкапа. Но потеряли по-настоящему, и делать вид, что «клиент сам разберётся», не выйдет.
- `dovecot-uidlist` — карта «IMAP UID → имя файла в Maildir». Данные. Удалять нельзя.
- `dovecot-keywords` — карта «буква a–z в имени файла → название пользовательской метки». Данные. Удалять нельзя.
- `subscriptions` — список подписанных папок в корне Maildir. Формально не индекс; удалите — у пользователя схлопнется дерево папок в клиенте.
- `dovecot.index`, `dovecot.index.log`, `dovecot.index.cache` — кэш. Удаляются безопасно, Dovecot пересоберёт при следующем открытии папки.
- `dovecot.list.index*`, `dovecot.mailbox.log` — служебное про сами папки (GUID, история создания и переименования), а не про письма. Без нужды не трогать: сломаете инкрементальную синхронизацию dsync.
Что на самом деле лежит в каталоге Maildir
Разберём по файлам. Возьмём папку одного ящика на боевом сервере — это стандартная раскладка Maildir++ с виртуальными пользователями:
ls -la /var/vmail/dental-dom.example/i.ivanova/Maildir/.Laboratoriya/В выводе вы увидите три каталога — cur, new, tmp — и горсть служебных файлов. cur и new содержат сами письма, по одному файлу на сообщение. Всё остальное — метаданные, и именно с ними надо разбираться внимательно.
dovecot-uidlist — самый важный файл. Он держит соответствие между IMAP UID сообщения и именем файла на диске. Формат текстовый, читается глазами. Первая строка — заголовок:
3 V1275660208 N25022 G3085f01b7f11094c501100008c4a11c1
25006 :1276528487.M364837P9451.kurkku,S=1355,W=1394:2,
25017 W2481 :1276533073.M242911P3632.kurkku:2,FПервое число 3 — версия формата, которую Dovecot использует начиная с v1.1 (версия 1 совместима с Courier). Пример взят из официальной документации, имя хоста kurkku в именах файлов — оттуда же. V1275660208 — UIDVALIDITY папки. N25022 — UID, который будет выдан следующему добавленному сообщению. G3085f01b... — 128-битный глобальный идентификатор папки в hex. Дальше идут записи: UID, необязательные поля (например W2481 — размер сообщения в RFC822-представлении, с CRLF), двоеточие и последнее известное имя файла.
dovecot-keywords — второй управляющий файл, и про него забывают вообще все. В имени файла Maildir пользовательские метки кодируются одной буквой: :2,STa означает флаги Seen, Trashed и пользовательскую метку под буквой a. А что такое a — написано только здесь:
0 $Junk
1 $NonJunk
2 Договоры
3 СтраховыеПозиция 0 соответствует букве a, позиция 1 — b и так далее. В имени файла помещается максимум 26 меток; всё, что сверх, живёт в индексах. Удалили dovecot-keywords — буквы в именах файлов остались, а расшифровки нет. Клиент видит либо пустоту, либо непонятный набор служебных ключевых слов вместо ваших «Договоров» и «Страховых».
И третья группа — индексы: dovecot.index (текущее состояние флагов и заголовков), dovecot.index.log (журнал транзакций), dovecot.index.cache (кэш разобранных заголовков и структуры MIME, чтобы не перечитывать письма при каждом поиске). Вот это — производные данные. Их можно удалить в любой момент; Dovecot пересоберёт их из имён файлов и из dovecot-uidlist. Именно потому, что индексы восстанавливаются из uidlist, а не наоборот, uidlist и является тем самым единственным источником правды.
- Данные, невосстановимые: `dovecot-uidlist`, `dovecot-keywords`, `subscriptions`.
- Кэш, восстановимый: `dovecot.index`, `dovecot.index.log`, `dovecot.index.cache`, `dovecot.index.thread`.
- Про структуру папок: `dovecot.list.index`, `dovecot.list.index.log`, `dovecot.mailbox.log` — восстановимо, но с последствиями для dsync и репликации.
- Сами письма: файлы в `cur/` и `new/`. Их не трогает ни одна «чистка индексов».
Почему из-за нового UID клиент выкачивает ящик заново
Механика простая, но её надо проговорить, иначе непонятно, откуда берутся десятки гигабайт трафика на ровном месте. IMAP не идентифицирует письмо по содержимому. Он идентифицирует его парой «UIDVALIDITY папки + UID сообщения». Клиент кэширует эту пару локально: в OST у Outlook, в .msf-индексе у Thunderbird, в базе у мобильного клиента. При подключении он спрашивает у сервера UIDVALIDITY. Совпал — можно догружать только новое, по дельте. Не совпал — весь локальный кэш этой папки объявляется недействительным.
RFC 9051 (IMAP4rev2, август 2021) в разделе 2.3.1.1 формулирует это со стороны сервера: если уникальные идентификаторы не сохранились между сессиями, значение UIDVALIDITY обязано стать больше предыдущего. Явного «клиент ДОЛЖЕН всё перекачать» в тексте нет, но следствие однозначное: изменившийся UIDVALIDITY означает, что старые UID больше не указывают на те же самые письма, и любой offline-клиент, который на них опирался, обязан ресинхронизироваться с нуля. На практике все нормальные клиенты именно это и делают.
Теперь что происходит, когда вы удалили dovecot-uidlist. Dovecot открывает папку, файла нет — он создаёт его заново. Присваивает UID всем найденным письмам заново, обычно начиная с единицы, в порядке сортировки имён файлов. И генерирует новый UIDVALIDITY, потому что старый он взять неоткуда. Всё. С точки зрения любого клиента это уже другая папка. Не «изменившаяся» — другая. Тридцать тысяч писем в ящике главного врача приезжают к нему по IMAP повторно, все до одного.
Дальше начинается арифметика, которая и роняет сервер. Один ящик на 20 ГБ — это 20 ГБ дискового чтения и столько же исходящего трафика. Два десятка ящиков, которые проснулись одновременно в 8:50 на сервере, собранном под 22 рабочих места, — это уже не про канал, это про то, что дисковая подсистема встала колом, а imap-процессов стало столько, что вы упёрлись в лимит процессов сервиса (в 2.3 по умолчанию default_process_limit = 100) и живые пользователи получают отказ в подключении. Документация Dovecot прямо предупреждает: если сделать это для всех пользователей, получите «huge disk I/O bursts», и это не преувеличение, а описание вашего утра.
Отдельная неприятность — Outlook. Он в этой ситуации перестраивает OST-файл целиком. У врача с перепиской и снимками за восемь лет это 10–40 ГБ и несколько часов, в течение которых он не работает вообще, а вы объясняете руководителю, почему «ничего не удаляли, но всё качается заново». Веб-клиенты вроде Roundcube переживают проще — у них нет offline-кэша, — но и у них открытое окно со списком писем после смены UIDVALIDITY приходится перезагружать, а поиск и сортировка первые минуты тормозят, пока сервер заново строит кэш заголовков.
- UIDVALIDITY сменился → клиент считает папку новой → полная перезакачка.
- Перезакачка × число ящиков × средний размер = ваш ночной трафик и iowait.
- Метки: буквы в именах файлов остались, названия ушли вместе с `dovecot-keywords`.
- Серверные правила Sieve не страдают: они срабатывают при доставке и на UID не опираются. Страдают локальные кэши клиентов — OST, `.msf`, база мобильного приложения.
- Флаги Seen/Answered/Flagged переживают удаление uidlist — они закодированы в самих именах файлов (`:2,S`, `:2,RS`).
Разбор: клиника «Дентал-Дом», 24 ящика и одна команда find
Стенд, о котором речь. Стоматологическая клиника «Дентал-Дом» на 22 рабочих места: регистратура, врачи, зуботехническая лаборатория, бухгалтерия. Свой почтовый сервер на виртуальной машине: Debian 12, Dovecot 2.3.19.1 из штатного репозитория, Postfix спереди, 4 vCPU, хранение — Maildir на отдельном LVM-томе /var/vmail, 240 ГБ занято из 260. Двадцать четыре ящика — 22 сотрудника плюс общие info@ и registratura@; самый крупный — 46 ГБ у главного врача (сканы договоров, снимки КТ во вложениях и переписка с лабораториями за девять лет, чистить отказывается принципиально). Бэкап — файловый, каждую ночь в 03:10, ретеншен 14 дней.
Что произошло. Мониторинг в 01:40 прислал алерт «свободно менее 8 % на /var/vmail». Дежурный решил освободить место и, отталкиваясь от статьи в интернете про «безопасное удаление индексов Dovecot», выполнил под root:
find /var/vmail -name 'dovecot*' -deleteМаска dovecot* захватила и dovecot.index*, и dovecot-uidlist, и dovecot-keywords. Освободилось 3,4 ГБ — задача формально выполнена, алерт погас, человек ушёл спать. Первые пользователи начали подключаться в 07:30.
Что мы увидели в 09:20, когда меня позвали. Load average 14 при 4 ядрах, iowait 64 %, iostat показывает очередь к диску под 60. Процессов imap — 96 при default_process_limit = 100, то есть регистратура уже ловила отказы в подключении. По iftop исходящий трафик держался на 60–90 Мбит/с при офисном канале в 100, суммарно за утро наружу ушло 17 ГБ. Характерной строки UIDVALIDITY changed в логе не было — и это тоже диагноз: такое сообщение появляется, когда uidlist расходится с уцелевшим dovecot.index, а здесь снесли и то и другое. Диагноз поставили иначе: doveadm mailbox status показывал у папок uidvalidity, равный unix-времени сегодняшнего утра, и uidnext ровно на единицу больше числа писем, а mtime у dovecot-uidlist был свежее 01:40.
Первое, что сделали, — остановили Dovecot. Это контринтуитивно, когда «почта не работает», но абсолютно необходимо: пока сервис жив, он для каждой открытой папки (в том числе при доставке через LMTP) дописывает новый dovecot-uidlist с новыми UID, и число пострадавших ящиков растёт каждую минуту. Дальше сняли слепок текущего состояния (мало ли), составили список пользователей, которые уже успели войти по IMAP после аварии, — их клиенты уже перекачали папки под новые UID, и второй откат устроил бы им повторную перекачку. Остальным из ночного бэкапа за 03:10 восстановили точечно только управляющие файлы:
systemctl stop dovecot
# кто уже логинился по IMAP после 01:40 — их каталоги не трогаем
journalctl -u dovecot --since '01:40' \
| grep -o 'Login: user=<[^>]*>' \
| sed 's#.*<\(.*\)@\(.*\)>#/\2/\1/#' | sort -u > /root/synced-users.txt
# из смонтированного снапшота бэкапа — ТОЛЬКО control files
rsync -a --exclude-from=/root/synced-users.txt \
--include='*/' \
--include='dovecot-uidlist' \
--include='dovecot-keywords' \
--include='subscriptions' \
--exclude='*' \
/mnt/backup/var/vmail/ /var/vmail/
chown -R vmail:vmail /var/vmail
systemctl start dovecotПочему нельзя было просто откатить /var/vmail целиком из бэкапа: между 03:10 и 09:20 люди читали и удаляли почту, приходили новые письма. Полный откат вернул бы удалённое и потерял бы доставленное за шесть часов. Точечный rsync только по трём именам файлов оставляет письма как есть и возвращает ровно ту метаинформацию, которую снесли. Ключевой момент — порядок фильтров: исключения пользователей идут первыми, затем --include='*/', без которого rsync не зайдёт в подкаталоги и молча ничего не скопирует. Побочный эффект того же */: папки, которые пользователь удалил после 03:10, вернутся пустыми каталогами — у нас таких оказалось две, их убрали руками. Свежесозданные ночью dovecot.index* в восстановленных папках трогать не нужно: при первом открытии Dovecot увидит расхождение UIDVALIDITY между индексом и вернувшимся uidlist, запишет в лог то самое UIDVALIDITY changed и перестроит индекс.
Итог. 15 ящиков из 24 вернулись к старым UID полностью — эти люди даже не заметили ночного происшествия, кроме подтормаживания утром. 9 ящиков — регистратура, два администратора и врачи утренней смены — успели до нашего вмешательства подключиться и начать перекачку под новые UID; их мы сознательно исключили из отката, и они честно пересинхронизировались — в сумме ещё около 12 ГБ трафика, растянутых на полтора дня, потому что мы порезали полосу для IMAP на шейпере. У двух ящиков, где dovecot-keywords в бэкапе оказался старше последних изменений, потерялись две пользовательские метки — их пересоздали руками. Общее время активного ремонта — пять часов, полное затухание последствий — двое суток.
И честный вывод, который я всегда проговариваю клиенту: спасло нас не мастерство, а то, что бэкап делался файловым уровнем и захватывал служебные файлы. Если бы почта бэкапилась только через doveadm backup или выгрузкой писем, вернуть исходные UID было бы нечем — все 24 ящика ушли бы в полный пересинк.
- 01:40 — алерт по месту, `find ... -delete`, освобождено 3,4 ГБ.
- 07:30–09:20 — 96 imap-процессов при лимите 100, iowait 64 %, 17 ГБ исходящего трафика.
- 09:25 — `systemctl stop dovecot`: остановить рост числа пострадавших ящиков.
- 09:40–11:00 — список уже подключившихся пользователей и точечный откат `dovecot-uidlist`/`dovecot-keywords`/`subscriptions` из бэкапа 03:10 для остальных.
- Результат: 15 ящиков спасены, 9 пересинкались, 2 метки восстановлены руками.
Что реально можно вернуть, а что уже нет
Разложу по честности, без обещаний. dovecot-uidlist возвращается только из бэкапа и только до того, как Dovecot успел создать новый. Восстановить старые UID «вычислением» нельзя: соответствие UID↔файл нигде больше не дублируется. Если у ящика уже появился свежий uidlist и клиент к нему подключился — всё, вы в новой реальности, откат назад только добавит путаницы. Не пытайтесь героически «вернуть как было» на таких ящиках: смиритесь и дайте им пересинкаться.
dovecot-keywords иногда спасается сам. Названия меток дублируются в индексе — в dovecot.index, — и если снесли только keywords, а индекс уцелел, названия часто ещё живы. Проверяется одной командой:
doveadm fetch -u i.ivanova@dental-dom.example flags mailbox Laboratoriya all | sort -u | head -30Если в выводе видны нормальные названия — не удаляйте индекс: пока он жив, Dovecot знает названия и при очередном изменении меток запишет dovecot-keywords заново. На всякий случай сохраните этот вывод в файл — он пригодится, если индекс всё же придётся пересобирать. Если видите пустоту или служебные $Junk/$NonJunk вместо ваших рабочих меток — названия ушли, восстанавливать вручную по списку от пользователя.
Буквы-то в именах файлов остались. То есть если в бэкапе есть старый dovecot-keywords, вы возвращаете его, и все метки на всех письмах оживают одномоментно — потому что привязка «письмо ↔ буква» никуда не девалась, потерялась только расшифровка буквы. Это единственный сценарий в нашей теме, где восстановление даёт почти стопроцентный результат — оговорка одна: если после аварии пользователи успели создать новые метки, Dovecot мог занять под них те же буквы, и такие метки придётся перепроверить. Поэтому dovecot-keywords из бэкапа тащим всегда, даже если по uidlist уже поздно.
subscriptions восстанавливается из бэкапа тривиально, а если бэкапа нет — придётся просить пользователей подписаться на папки заново. Мелочь, но звонков генерирует много, потому что человек видит «пропали папки» и думает, что пропала почта. Предупредите рассылкой заранее — сэкономите себе полдня телефонных разговоров.
И последнее по приоритету, но важное по нервам: сообщите людям, что происходит, до того как они спросят. Формулировка, которая работает: «письма все на месте, ни одно не потеряно; почтовые программы сегодня перекачивают архив заново, это займёт до конца дня, работать можно через веб-почту». Это чистая правда и она снимает 80 % паники. Врать про «плановые работы» не надо — во-первых, узнают, во-вторых, к вам потом не придут вовремя с настоящей проблемой.
- Есть файловый бэкап и Dovecot остановлен вовремя → возвращается почти всё.
- Dovecot уже переписал uidlist → UID не вернуть, только пересинк.
- Индекс `dovecot.index` уцелел → названия меток, скорее всего, живы.
- Ни бэкапа, ни индекса → метки пересоздаются руками по списку от пользователя.
- Письма не теряются никогда — это первое, что надо сказать пользователям.
Как чистить Dovecot, когда действительно кончилось место
Начну с главного: если вы удаляете файлы Dovecot, чтобы освободить место, вы почти наверняка чистите не то. Индексы редко занимают больше нескольких процентов от объёма почты. В том же «Дентал-Доме» удаление всех индексов дало 3,4 ГБ при 240 ГБ хранилища — полтора процента и двое суток последствий. Место занимают письма и вложения, а не метаданные.
Если индексы всё-таки распухли аномально — а это бывает, когда dovecot.index.cache разрастается на огромных папках, — чистить их надо инструментом Dovecot, а не find. Для одного ящика:
# пересобрать индексы одной папки, control files не трогаются
doveadm force-resync -u i.ivanova@dental-dom.example Laboratoriya
# все папки одного ящика
doveadm force-resync -u i.ivanova@dental-dom.example '*'
# посмотреть, что вообще с папкой: UID, UIDVALIDITY, GUID, размер
doveadm mailbox status -u i.ivanova@dental-dom.example \
messages uidnext uidvalidity guid vsize LaboratoriyaПо man-странице force-resync «пытается исправить все проблемы» ящика, которые Dovecot не смог решить автоматически. Для Maildir это пересканирование cur/ и new/ и перестройка индекса поверх существующего dovecot-uidlist — сам uidlist команда не удаляет, и UID сохраняются. Это ровно та операция, которую хотел сделать наш дежурный.
Ключ -A (по всем пользователям) — вещь, которой я пользуюсь очень аккуратно и никогда в рабочее время. doveadm force-resync -A '*' даже на сервере с 24 ящиками и 240 ГБ — это час-другой плотной дисковой нагрузки. И учтите оговорку из man: с userdb на базе passwd ключ -A захватывает и системных пользователей ниже first_valid_uid; в такой конфигурации надёжнее -F со списком ящиков. Запускать под nice/ionice, ночью, и лучше циклом по пользователям с паузами, чтобы не положить дисковую подсистему одним залпом.
Реальные способы освободить место на почтовом сервере, в порядке отдачи: вычистить корзины и спам старше N дней, поджать или вынести архивные ящики уволенных, включить квоты (их обычно нет — и зря), проверить, не растёт ли /var/log или снапшоты LVM. Массовое удаление старой почты:
# сколько занимает и сколько писем — сначала посчитать
doveadm quota get -A
doveadm search -A mailbox Trash savedbefore 30d | wc -l
# и только потом удалять
doveadm expunge -A mailbox Trash savedbefore 30d
doveadm expunge -A mailbox Junk savedbefore 30dЭто честные гигабайты, и никаких UID при этом не меняется. Две оговорки: doveadm quota get работает только при включённом плагине quota, а doveadm expunge по документации требует и имя папки, и ограничение по сообщениям — поэтому без savedbefore или другого условия он просто откажется выполняться, и это хорошо.
И про порядок действий, когда алерт по месту прилетел ночью. Не надо ничего удалять в 01:40 в полусне. Расширьте том, если есть чем; если нечем — почистите Trash и Junk по команде выше, это безопасно и почти всегда даёт достаточно. Всё остальное — утром, на свежую голову, с бэкапом под рукой. Ни один почтовый сервер не умирает от 92 % занятого места за одну ночь, а вот от неаккуратного find — умирает за одну минуту.
- `doveadm force-resync -u user '*'` вместо `rm dovecot*`.
- `doveadm expunge -A mailbox Trash savedbefore 30d` — безопасные гигабайты.
- `doveadm quota get -A` — понять, кто на самом деле съел место.
- `-A` только ночью, под `ionice -c3`, желательно порциями.
- Перед любой массовой операцией — `doveadm search ... | wc -l`, чтобы увидеть масштаб.
Профилактика: развести control и index по разным каталогам
Самая надёжная защита — сделать так, чтобы индексы и управляющие файлы физически лежали в разных местах. Тогда любая «чистка индексов» по маске в каталоге индексов не сможет задеть данные, даже если её делает человек, который не читал эту статью. Dovecot это умеет с давних версий. В ветке 2.3 параметры задаются прямо в mail_location:
# Dovecot 2.3
mail_location = maildir:~/Maildir:INDEX=/var/indexes/%u:CONTROL=/var/control/%uВ ветке 2.4 настройка mail_location разобрана на отдельные параметры — это одно из заметных изменений при переходе, и его надо учитывать при апгрейде:
# Dovecot 2.4
mail_driver = maildir
mail_path = ~/Maildir
mail_index_path = /var/indexes/%{user}
mail_control_path = /var/control/%{user}Обратите внимание и на смену синтаксиса переменных: %u уступил место форме %{user}. Если вы переезжаете с 2.3 на 2.4, это ровно то место, где конфиг тихо перестаёт делать то, что вы думали.
Второй повод развести control-файлы отдельно — квоты файловой системы. Dovecot не умеет корректно обрабатывать ошибку записи в control-файл, а именно это и происходит, когда у пользователя кончилась квота на разделе. Поэтому официальная рекомендация — держать control вне квотируемого раздела. Побочно это даёт нам ту самую защиту от find, ради которой всё и затевалось.
Но есть подвох, о котором молчат все инструкции, и он ровно про нашу тему. Если вы просто поменяете конфиг на живом сервере и перезапустите Dovecot, он не найдёт dovecot-uidlist по новому пути — и создаст новые. То есть вы получите ту же самую аварию, только по собственной воле и сразу на всех ящиках. Управляющие файлы надо физически перенести на новое место при остановленном сервисе, сохранив структуру каталогов, и только потом стартовать. Проверять — на одном тестовом ящике, сравнивая uidvalidity до и после через doveadm mailbox status.
Третий контур — бэкап. Он должен захватывать служебные файлы, а не только письма. Файловая копия каталога /var/vmail со всеми dovecot-* внутри — это то, что спасло «Дентал-Дом». Логическая выгрузка через doveadm backup полезна как второй контур (она переживает смену формата хранения и умеет в инкремент), но исходные UID из неё вернуть не получится. Держите оба, если объёмы позволяют; если приходится выбирать — файловый.
И мониторинг. Два простых датчика закрывают 90 % риска: алерт на аномальный рост исходящего трафика с почтового сервера (норма ×3 держится дольше 30 минут) и алерт на появление в mail.log строк UIDVALIDITY changed. Второе срабатывает на первом же пострадавшем ящике, но только если индекс уцелел — при удалении «всего сразу» строки не будет, поэтому добавьте третий датчик: раз в ночь сохранять doveadm mailbox status -A uidvalidity '*' и сравнивать с прошлой выгрузкой. У нас это обычный grep в связке с отправкой в Telegram — примитивно, но за полтора года дважды поймало проблему до того, как её заметили пользователи.
- Развести `INDEX=` и `CONTROL=` (2.3) или `mail_index_path`/`mail_control_path` (2.4).
- Control-файлы — вне квотируемого раздела: Dovecot не переживает отказ записи в них.
- Переносить существующие файлы руками при остановленном сервисе, иначе получите аварию сами.
- Бэкап — файловый, с захватом `dovecot-uidlist` и `dovecot-keywords`.
- Алерты: рост исходящего трафика, `UIDVALIDITY changed` в логе и ночная сверка `uidvalidity` по всем папкам через `doveadm mailbox status`.
Частые вопросы
Письма точно не пропали?
Точно. Удаление файлов по маске `dovecot*` не трогает сами сообщения — они лежат отдельными файлами в каталогах `cur/` и `new/`. Посчитайте их: `find /var/vmail/domain/user/Maildir/.Folder/{cur,new} -type f | wc -l`. Число должно совпадать с тем, что было. Потеряны только метаданные: соответствие UID↔файл и названия пользовательских меток.
Можно ли вернуть старые UID, если бэкапа нет?
Нет. Соответствие UID↔имя файла хранилось только в `dovecot-uidlist` и нигде не дублируется. Если файл удалён, а Dovecot успел создать новый, старые UID утрачены безвозвратно. Единственный сценарий — вернуть `dovecot-uidlist` из файлового бэкапа до того, как Dovecot откроет папку и перезапишет его.
Флаги «прочитано» и «отвечено» тоже теряются?
Нет, стандартные флаги IMAP закодированы прямо в имени файла письма — суффикс вида `:2,S` (Seen), `:2,RS` (Replied+Seen), `:2,T` (Trashed). Их удаление служебных файлов не задевает. Теряются только пользовательские метки, потому что в имени файла они обозначены буквами a–z, а расшифровка букв лежала в `dovecot-keywords`.
Как безопасно пересобрать индексы Dovecot?
Командой `doveadm force-resync -u user@domain '*'` для одного ящика. Она пересканирует папку и перестраивает индекс поверх существующих `dovecot-uidlist` и `dovecot-keywords`, не удаляя их, так что UID сохраняются. Ключ `-A` (по всем пользователям) применяйте только ночью и под `ionice -c3`: на большом хранилище это часы дисковой нагрузки.
Сколько места реально освобождает удаление индексов?
Обычно единицы процентов от объёма хранилища. На нашем стенде с 240 ГБ почты это дало 3,4 ГБ — полтора процента и двое суток последствий. Место занимают письма и вложения. Чистить надо корзины и спам (`doveadm expunge -A mailbox Trash savedbefore 30d`), архивы уволенных и включать квоты.
Как понять по логам, что случилось именно это?
Ищите в логе Dovecot строки с `UIDVALIDITY changed` — они означают, что папка получила новый UIDVALIDITY и клиенты пойдут в полный пересинк. Но такая строка появляется, только если старый `dovecot.index` пережил удаление uidlist; если по маске снесли всё сразу, лог молчит. Тогда смотрите `doveadm mailbox status -u user uidvalidity uidnext messages '*'`: `uidvalidity`, совпадающий с unix-временем минувшей ночи, и `uidnext`, равный числу писем плюс один, при большом ящике означают, что нумерация началась заново. Время создания `dovecot-uidlist` (`stat`) подтвердит момент.
Стоит ли переносить control-файлы в отдельный каталог на живом сервере?
Стоит, но строго при остановленном Dovecot и с физическим переносом существующих файлов на новое место. Если просто поменять конфиг и перезапустить сервис, он не найдёт `dovecot-uidlist` по новому пути и создаст новые — вы получите ту же аварию, только сразу по всем ящикам и по собственной инициативе.
Источники
- Dovecot CE 2.4 — Maildir Mailbox Format — Разделы «Control files» и «Index files», формат dovecot-uidlist (V/N/G) и dovecot-keywords, настройки mail_control_path / mail_index_path. https://doc.dovecot.org/2.4.0/core/config/mailbox/formats/maildir.html
- Dovecot 2.3 — Maildir Configuration — Предупреждение о control files: «if you delete control files you'll cause messages to get new UIDs and possibly lose keyword names», параметры INDEX= и CONTROL= в mail_location. https://doc.dovecot.org/2.3/configuration_manual/mail_location/Maildir/
- Dovecot CE — Upgrading from 2.3 to 2.4 — Раздел про mail_location: «Split into multiple mail_* settings»; таблица замены переменных %u → %{user}; предупреждение, что старая конфигурация без правок не заработает. https://doc.dovecot.org/2.4.0/installation/upgrade/2.3-to-2.4.html
- Dovecot CE 2.4 — Mail Location Settings — Настройки mail_driver, mail_path, mail_index_path, mail_control_path, mail_inbox_path, заменившие mail_location. https://doc.dovecot.org/2.4.0/core/config/mailbox/mail_location.html
- RFC 9051 — IMAP4rev2, август 2021 — Раздел 2.3.1.1 «Unique Identifier (UID) Message Attribute»: при несохранении UID между сессиями значение UIDVALIDITY обязано быть больше предыдущего; следствие — недействительность клиентского кэша. https://www.rfc-editor.org/rfc/rfc9051.html
- Dovecot CE — doveadm-force-resync(1) — Описание команды force-resync, ключи -A, -u, -F и предупреждение про -A с passwd-userdb. https://doc.dovecot.org/main/core/man/doveadm-force-resync.1.html
- Dovecot CE — doveadm-expunge(1) и doveadm-search-query(7) — Обязательные mailbox и ограничитель выборки для expunge, ключ savedbefore, пример очистки Spam. https://doc.dovecot.org/main/core/man/doveadm-expunge.1.html и https://doc.dovecot.org/main/core/man/doveadm-search-query.7.html
- OSSO — Dovecot / roundcube / mail read error (2018) — Практический случай: удаление `rm /var/mail/DOMAIN/USER/dovecot*` как временное решение и потеря пользовательских IMAP-меток как побочный эффект. https://www.osso.nl/blog/2018/dovecot-roundcube-mail-read-error/
- Dovecot mailing list — Resend whole inbox to user (апрель 2022) — Обсуждение способов заставить IMAP-клиент перекачать ящик целиком и роли UIDVALIDITY в этом. https://dovecot.org/mailman3/archives/list/dovecot%40dovecot.org/thread/FKWNIQICM5TXSBEUIYVVU3Y3ZODAYWUV/
