Redis MISCONF в mailcow: почему stop-writes-on-bgsave-error=no не чинит сохранение данных
Если mailcow вдруг перестаёт сохранять действия в панели, а в логах контейнера redis-mailcow видна ошибка MISCONF Redis is configured to save RDB snapshots, самый быстрый совет из интернета — выполнить CONFIG SET stop-writes-on-bgsave-error no — действительно убирает ошибку за секунду. Но это не исправление, а снятие защитного механизма: Redis специально блокирует запись, когда не может сохранить снимок данных на диск, чтобы не копить изменения, которые негде будет восстановить после сбоя. Разбираю, что реально стоит за MISCONF, на что это влияет в mailcow и как искать настоящую причину отказа BGSAVE.
Симптом: панель mailcow перестаёт откликаться на действия с MISCONF в логе
Типичная картина: администратор заходит в панель корпоративной почты на mailcow, чтобы, например, добавить домен или изменить настройки ящика, а интерфейс отвечает ошибкой, либо действие просто не сохраняется. В логах контейнера redis-mailcow при этом обнаруживается сообщение: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Сообщение длинное и на первый взгляд пугающее, но по сути оно само себя объясняет: Redis сообщает, что запись данных отключена, потому что последняя попытка сохранить снимок на диск (BGSAVE) провалилась.
Проблема реальна и зафиксирована в трекере mailcow-dockerized — issue #5963 описывает ровно этот сценарий на свежей установке mailcow версии 2024-06c, где сразу после входа в веб-панель возникает исключение Redis. Трассировка в этом отчёте указывает конкретное место сбоя: вызов Redis->lPush('API_LOG', ...) в файле /web/json_api.php — то есть панель mailcow пытается записать очередную запись в свой внутренний журнал API-вызовов через Redis, и именно эта запись блокируется механизмом MISCONF.
Важная деталь для диагностики: то, что ошибка проявляется именно на записи в API_LOG, не значит, что проблема связана с этим конкретным журналом или с объёмом логов в нём. Redis блокирует вообще все команды, изменяющие данные, а не только конкретный ключ или структуру — API_LOG просто оказывается первой операцией записи, которая происходит после того, как BGSAVE уже провалился, и поэтому первой попадает под блокировку в логах. Rspamd тоже пишет в этот Redis (статистику, лимиты, историю), поэтому та же MISCONF обычно видна и в логе rspamd-mailcow — так было, например, в старом обсуждении #1454 в том же трекере.
Что означает MISCONF на самом деле: не ошибка Redis, а его защита
Официальный FAQ Redis прямо описывает это поведение как задуманное: параметр stop-writes-on-bgsave-error заставляет сервер отклонять все команды, изменяющие данные, если последняя фоновая попытка сохранения (BGSAVE) завершилась ошибкой. Смысл в том, чтобы не позволить накопиться изменениям, которые физически негде будет восстановить, если Redis (или весь хост) внезапно упадёт — без этой защиты сервер продолжал бы принимать записи в память, создавая растущий разрыв между тем, что реально в памяти, и тем, что сохранено на диске.
Отсюда ключевой вывод: сама ошибка MISCONF — это симптом, а не причина. Причина всегда одна и та же по сути: Redis не смог успешно выполнить BGSAVE (сохранение RDB-снимка) на диск. Причин у этого может быть несколько — нехватка места на диске под том с данными Redis, проблемы с правами на директорию, в которую пишется дамп, или, что особенно характерно для контейнеризированных инсталляций вроде mailcow, неудачный fork дочернего процесса для сохранения из-за нехватки памяти на хосте (ядро Linux может консервативно отказывать в fork, даже если формально памяти достаточно, если не настроен overcommit). В отличие от полноценного Redis-кластера под высокую нагрузку, в mailcow по умолчанию это единственный инстанс Redis без реплики — при отказе BGSAVE переключиться попросту не на что.
Ещё один вариант, который стоит держать в уме на серверах с ограниченным контролем ресурсов, — жёсткие лимиты памяти на сам контейнер redis-mailcow, заданные через Docker (например, если администратор вручную добавлял memory limits в docker-compose.override.yml). Если лимит контейнера выставлен впритык к объёму данных, которым оперирует Redis, попытка BGSAVE может упереться именно в лимит контейнера, а не в память хоста целиком — и тогда free -h на хосте покажет свободную память, которая по факту недоступна процессу Redis внутри контейнера.
Почему CONFIG SET stop-writes-on-bgsave-error no — не решение, а маскировка
Быстрая команда, которую часто советуют в первую очередь:
source mailcow.conf
docker compose exec redis-mailcow redis-cli -a "$REDISPASS" CONFIG SET stop-writes-on-bgsave-error no(в актуальных версиях Redis в mailcow закрыт паролем REDISPASS из mailcow.conf — его прописывает requirepass в data/conf/redis/redis-conf.sh; в старых установках без этой переменной ключ -a не нужен.)
Действительно снимает блокировку записи мгновенно — панель mailcow снова начинает откликаться на действия. Но именно поэтому она опасна как единственное решение: она не чинит причину, по которой BGSAVE не смог сохранить данные на диск, а лишь отключает защитный механизм, который об этом предупреждал.
После такой команды Redis продолжает принимать запись изменений в оперативную память, но состояние на диске остаётся тем же, что было на момент последнего успешного сохранения — то есть устаревшим. Если в этот момент произойдёт перезапуск контейнера redis-mailcow, аварийное завершение процесса или перезагрузка хоста, все изменения, накопленные после последнего успешного BGSAVE, будут потеряны безвозвратно — и панель mailcow, и кэши, зависящие от Redis, откатятся к устаревшему состоянию, а администратор узнает об этом постфактум, когда пропадут недавние изменения в настройках.
Как искать настоящую причину отказа BGSAVE
Первым делом стоит проверить место на диске под тем данных Redis — именно нехватка места самая частая причина отказа сохранения. В mailcow данные Redis хранятся в отдельном Docker-томе, и команда df -h на хосте покажет, не заполнен ли раздел, на котором лежат тома Docker целиком, а не только том Redis — переполнение общего раздела /var/lib/docker встречается едва ли не чаще, чем переполнение выделенного тома.
Если с диском всё в порядке, следующий кандидат — память и настройка vm.overcommit_memory на хосте. Redis для BGSAVE форкает дочерний процесс, который в теории требует столько же адресного пространства, сколько занимает родительский процесс (хотя реально благодаря copy-on-write расходует значительно меньше), и ядро Linux при консервативных настройках overcommit может отказать в этом форке, даже если физической памяти формально достаточно. Проверить и исправить это можно так:
cat /proc/sys/vm/overcommit_memory
echo 'vm.overcommit_memory = 1' > /etc/sysctl.d/99-redis.conf
sysctl --systemЗначение 1 рекомендует сама документация Redis, а в трекере mailcow (issue #1454) именно vm.overcommit_memory=1 на хосте снял плавающую MISCONF у автора. Меняется параметр на хосте, не внутри контейнера. Права на том проверяются одной командой: docker compose exec --user redis redis-mailcow touch /data/test — если она падает, дело в томе redis-vol-1, а не в памяти. Отдельно стоит свериться с логом самого контейнера docker compose logs redis-mailcow за момент возникновения MISCONF — там обычно есть более конкретное сообщение об ошибке сохранения (например, явное No space left on device или ошибка fork), которое сразу указывает на нужное направление, не заставляя перебирать все гипотезы подряд.
RDB и AOF: почему после отключения защиты часть данных всё равно может потеряться
В контексте этой ошибки полезно понимать, что RDB-снимок — не единственный, но в конфигурации mailcow по умолчанию основной механизм сохранения данных Redis на диск. Согласно официальному разбору персистентности Redis, данные, которые существуют только в оперативной памяти между двумя снимками RDB, могут быть потеряны при аварийном завершении процесса или сервера — журналирование через AOF (Append Only File) даёт более гранулярную защиту, записывая каждую операцию, но в стандартной поставке mailcow полагается именно на периодические RDB-снимки, а не на AOF.
И здесь частое заблуждение: «Redis в mailcow — просто кэш, потеряем — не страшно». Это не так. В Redis mailcow хранит закрытые DKIM-ключи доменов (хэш DKIM_PRIV_KEYS), данные Rspamd — байесовскую статистику, которую вы месяцами обучали, лимиты отправки, — а также баны netfilter и журналы панели. Потеря свежих изменений в Redis — это, например, DKIM-ключ, сгенерированный уже после последнего успешного снимка и уже опубликованный в DNS: после рестарта его не будет, и подписи писем сломаются. Поэтому redis — отдельный компонент в штатном скрипте резервного копирования mailcow. И это тем более повод не относиться к ошибке MISCONF легкомысленно — регулярные отказы BGSAVE говорят о системной проблеме с диском или памятью хоста, которая рано или поздно проявится и в других сервисах, а не только в Redis.
На практике я советую клиентам не пытаться включать AOF для redis-mailcow вручную, редактируя конфигурацию контейнера в обход штатной поставки mailcow — это нестандартная модификация, которая может быть потеряна или конфликтовать при следующем обновлении mailcow, а сам проект такую конфигурацию не документирует и не тестирует. Штатный redis-conf.sh задаёт только пароль и служебного пользователя, так что работают значения Redis по умолчанию: RDB-снимки по расписанию save и выключенный AOF. Разумнее принять эту модель как есть и вместо борьбы с архитектурой Redis сосредоточиться на том, чтобы BGSAVE стабильно успешно завершался — тогда и стандартной защиты RDB-снимков достаточно для той роли, которую Redis играет в mailcow.
Кейс «ВелоГрад»: диск под Docker забился логами, а не письмами
Клиент — магазин велосипедов «ВелоГрад», 46 рабочих мест, у которого mailcow был поставлен по нашему пошаговому регламенту и с тех пор крутился на выделенном VPS почти без мониторинга свободного места. Администратор клиента заметил проблему, когда сотрудники пожаловались, что в панели mailcow перестали сохраняться изменения фильтров и алиасов — при этом сама почта продолжала приходить и уходить без видимых сбоев, что на первый взгляд выглядело странно: сервис вроде работает, а настройки не сохраняются.
В логе redis-mailcow сразу нашлась ошибка MISCONF. Проверка диска через df -h показала, что раздел, на котором лежат тома Docker, заполнен на 100 % — но занимали место не письма и не сам Redis, а разросшиеся логи контейнеров, которые никогда не ротировались за полтора года эксплуатации. Убрали часть старых логов, настроили ротацию через docker-compose.override.yml с ограничением max-size и max-file для логов контейнеров, освободили около 8 ГБ. После этого Redis смог самостоятельно выполнить очередной BGSAVE, ошибка MISCONF исчезла сама, без единой команды CONFIG SET — то есть без временного отключения защиты, которое администратор изначально хотел применить по совету из форума.
Дополнительно на этом же сервере настроили простой мониторинг свободного места — раз в сутки cron-задача проверяет процент занятости диска и отправляет уведомление на почту администратора, если свободного места остаётся меньше 15 %. Стоило это буквально десяти строк bash-скрипта, но именно такого элементарного контроля не хватало полтора года, пока ситуация не дошла до отказа записи в панели mailcow — для компании с 46 рабочими местами это уже не мелкая неполадка одного сотрудника, а риск для всей переписки, если бы диск заполнился полностью и до Redis.
Чек-лист: диагностика MISCONF в mailcow
При появлении MISCONF в логе redis-mailcow первым делом проверить df -h — не только выделенный том Redis, но и весь раздел, на котором лежат тома Docker: переполнение общего раздела встречается чаще, чем переполнение конкретного тома. Если с диском всё в порядке, проверить vm.overcommit_memory на хосте и посмотреть docker compose logs redis-mailcow на предмет конкретного сообщения об ошибке — оно почти всегда точнее подсказывает направление, чем догадки по общему тексту MISCONF.
CONFIG SET stop-writes-on-bgsave-error no стоит применять только как временную меру, чтобы панель mailcow снова заработала прямо сейчас, пока идёт поиск причины — не как финальное решение. После устранения настоящей причины (места на диске, памяти или прав на директорию) стоит явно инициировать BGSAVE вручную (redis-cli -a "$REDISPASS" BGSAVE) и через INFO persistence убедиться, что rdb_last_bgsave_status стал ok, а затем при желании вернуть stop-writes-on-bgsave-error обратно в yes, чтобы защита продолжала работать при следующем сбое.
Частые вопросы
Что означает ошибка MISCONF Redis is configured to save RDB snapshots в mailcow?
Redis сообщает, что запись данных отключена, потому что последняя попытка сохранить RDB-снимок на диск (BGSAVE) не удалась, а параметр stop-writes-on-bgsave-error включён (значение по умолчанию — yes). Это защитный механизм, не сбой сервиса как таковой.
Почему CONFIG SET stop-writes-on-bgsave-error no не считается решением проблемы?
Эта команда только отключает защиту и убирает симптом, но не устраняет причину, по которой BGSAVE не смог сохранить данные на диск. После неё Redis продолжает принимать запись в память, но данные на диске остаются устаревшими до следующего успешного сохранения.
Какие основные причины отказа BGSAVE в Redis?
Чаще всего — нехватка места на диске (в контейнеризированных инсталляциях типа mailcow это может быть переполнение всего раздела Docker, а не только тома Redis), проблемы с правами на директорию данных, и отказ ядра в форке дочернего процесса из-за консервативных настроек vm.overcommit_memory.
Как проверить, что именно вызвало ошибку MISCONF в mailcow?
Посмотреть df -h на хосте (весь раздел под Docker, не только том Redis), проверить vm.overcommit_memory через cat /proc/sys/vm/overcommit_memory, и изучить docker compose logs redis-mailcow — там обычно есть конкретное сообщение об ошибке сохранения.
Может ли mailcow потерять данные из-за ошибки MISCONF?
Данные, накопленные в Redis между последним успешным BGSAVE и моментом сбоя, могут быть потеряны при перезапуске контейнера или хоста — это касается данных в Redis — в mailcow там лежат в том числе DKIM-ключи доменов, статистика и лимиты Rspamd, журналы панели. Сами письма в Redis не хранятся, они в томе vmail.
Нужно ли возвращать stop-writes-on-bgsave-error обратно в yes после устранения причины?
Да, это рекомендуется — значение no было временной мерой, чтобы панель mailcow продолжала работать, пока искали причину. После устранения проблемы и успешного ручного BGSAVE стоит вернуть защиту в исходное состояние yes.
Источники
- GitHub mailcow/mailcow-dockerized — Issue #5963 — Симптом MISCONF на свежей установке mailcow 2024-06c (Ubuntu 24, Docker 27.0.3, Compose 2.28.1), точка сбоя Redis->lPush('API_LOG', ...) в /web/json_api.php, статус closed as not planned. https://github.com/mailcow/mailcow-dockerized/issues/5963
- Redis FAQ — How to fix error MISCONF Redis is configured to save RDB snapshots — Официальное объяснение назначения stop-writes-on-bgsave-error, причин отказа BGSAVE (память, диск, права), рекомендация искать и устранять первопричину, а не только снимать блокировку. https://redis.io/faq/doc/296s7bo3im/how-to-fix-error-error-misconf-redis-is-configured-to-save-rdb-snapshots
- Redis Blog — The Importance of Database Persistence and Backups — Разница между RDB-снимками и AOF-журналированием, риск потери данных, существующих только в памяти между снимками RDB. https://redis.io/blog/importance-of-database-persistence-and-backups/
- GitHub mailcow-dockerized — redis-conf.sh, functions.dkim.inc.php, Issue #1454 — redis-conf.sh задаёт только requirepass $REDISPASS и пользователя quota_notify (persistence по умолчанию); DKIM-ключи пишутся в Redis hSet('DKIM_PRIV_KEYS', …); в #1454 проверка записи в том touch /data от пользователя redis и решение vm.overcommit_memory=1. https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/redis/redis-conf.sh , https://github.com/mailcow/mailcow-dockerized/issues/1454



