SOGo No child available: чинит ActiveSync на телефонах
АйТи Фреш
Linux, Docker и DevOps

SOGo пишет No child available, когда сотрудники подключают телефоны через ActiveSync: в чём причина

Автор: , директор ООО «АйТи-Фреш» · · ~13 мин чтения
SOGo не может обработать запрос веб-почты, потому что все worker-процессы заняты push-соединениями ActiveSync
Каждый телефон с push держит свой процесс SOGo занятым часами — веб-почте просто не хватает свободных мест.

Если веб-почта SOGo в mailcow виснет после того, как сотрудники подключили почту на телефонах через ActiveSync, а в логах — «No child available» от WOWatchDog, дело не в CPU, а в том, что все workers SOGo заняты push-соединениями EAS. Разбираю, как считать WOWorkersCount по числу устройств, а не пользователей.

Симптом: веб-почта виснет именно после подключения телефонов

Садовый центр «СадГавань» — 36 рабочих мест, почта на mailcow, которую мы ведём на аутсорсе корпоративной почты с прошлой весны. Летом директор попросил подключить корпоративную почту менеджерам на личные телефоны через встроенный почтовый клиент — самый удобный способ для них — это ActiveSync (EAS), который в mailcow обслуживает SOGo. Подключили 14 телефонов за неделю — плюс ещё шесть руководителей к тому моменту уже работали в Outlook на ноутбуках через тот же ActiveSync, — и почти сразу веб-почта в браузере на компьютерах начала регулярно зависать на несколько минут, особенно в середине рабочего дня.

В логах docker compose logs sogo-mailcow --tail 100 — повторяющаяся строка от вотчдога процессов: [ERROR] <0x...[WOWatchDog]> No child available to handle incoming request!, а между ними — длинные запросы POST /SOGo/Microsoft-Server-ActiveSync?...&Cmd=Ping и Cmd=Sync от телефонов. При этом docker stats не показывал перегрузки CPU или памяти — контейнер SOGo просто не мог обслужить новый входящий запрос, потому что все worker-процессы были заняты долгими EAS-запросами.

Первым делом заподозрили саму миграцию почты — незадолго до этого мы переносили ящики «СадГавани» на mailcow с их прежнего сервиса через миграцию на mailcow без потери писем, и таймлайн совпадал почти день в день. Но при более внимательном сравнении логов оказалось, что имапсинк тут ни при чём: зависания начались не в момент миграции, а через два дня после неё — ровно тогда, когда менеджеры массово подключили почту на телефонах.

Что такое WOWorkersCount и почему это не про производительность CPU

WOWorkersCount — это не про многопоточность в привычном смысле, а буквально количество независимых процессов-экземпляров SOGo, которые параллельно принимают HTTP-запросы: веб-интерфейс, CalDAV/CardDAV, и ActiveSync. Согласно официальному руководству по установке SOGo, значение 3 — разумный дефолт для низкой нагрузки, а верхнюю границу диктуют CPU и I/O конкретной машины: завышенное значение само по себе снижает производительность под нагрузкой, а не наоборот.

Ключевая деталь именно для ActiveSync: устройство с включённым push держит HTTP-соединение открытым в командах Ping и Sync, и всё это время один worker занят им — не секунды обработки запроса, а до часа, если интервалы выставлены под настоящий push. Руководство SOGo прямо пишет, что по умолчанию SOGo не даёт EAS-клиентам долго держать соединения — именно чтобы телефоны не монополизировали все child-процессы и не оставили без ответа веб-интерфейс и DAV. Как только интервалы увеличивают ради мгновенных уведомлений, каждое такое устройство фактически закрепляет за собой worker, и свободных для браузера и CalDAV остаётся всё меньше.

Похожая логика встречается за пределами SOGo — в статье про банк-клиент в телефоне бухгалтера я разбирал другую сторону той же тенденции: как только корпоративные сервисы переезжают на личные телефоны сотрудников, у администратора появляется новый класс нагрузки и новый класс рисков, которые редко закладывают в план ещё на этапе «давайте просто подключим почту на телефон» — там про безопасность, здесь про ёмкость сервера, но исходная причина одна: мобильные клиенты ведут себя не так, как десктопные.

Сколько workers реально нужно — формула не по пользователям, а по устройствам

Официальное руководство SOGo формулирует правило прямо: на каждое EAS-устройство с push нужен минимум один child-процесс, и children должно быть больше, чем таких устройств, — чтобы оставалось чем обслуживать веб-интерфейс и DAV. Примеры из документации (во всех таймаут Apache — 3600 секунд, WOWatchDogRequestTimeout = 60 минут): для 100 пользователей и 10 EAS-устройств — WOWorkersCount = 15, SOGoMaximumPingInterval = 3540, SOGoMaximumSyncInterval = 3540, SOGoInternalSyncInterval = 30; для 1000 пользователей и 100 устройств — WOWorkersCount = 120 при тех же интервалах и SOGoInternalSyncInterval = 60. Это примеры конфигурации, а не норматив производительности, но пропорция показательна: workers растут вслед за числом push-устройств почти один к одному, с запасом 20–50 %.

У «СадГавани» 36 сотрудников, но считать нужно не их, а устройства: 14 телефонов плюс 6 Outlook-клиентов на ActiveSync — 20 постоянных EAS-подключений. Дефолт самого SOGo без явной настройки — один worker, но в mailcow в data/conf/sogo/sogo.conf из коробки стоит WOWorkersCount = "20"; и уже выставлены длинные интервалы: SOGoMaximumPingInterval = 3540, SOGoMaximumSyncInterval = 3540. Арифметика простая: 20 workers на 20 push-устройств — ноль свободных для браузера и CalDAV в тот момент, когда все устройства держат соединения. Пока телефонов было шесть, запас был; после волны подключений он кончился.

Отдельно уточню: документация SOGo не даёт единой формулы вида «workers = устройства × коэффициент» — это именно опорные точки для калибровки, а не таблица подстановки. Я использую эти два примера как верхнюю и нижнюю границу и интерполирую между ними по числу реальных EAS-устройств с push, добавляя отдельный запас под сами веб-сессии в браузере и синхронизацию календарей через CalDAV, которые тоже расходуют workers, просто на порядок короче по времени.

Сравнение примеров расчёта WOWorkersCount для SOGo при разном числе пользователей и EAS-устройств
Число EAS-устройств с push определяет нужное количество workers почти линейно, а не число ящиков.

Похожие случаи в трекерах: не только мы наступили на эти грабли

В обсуждении на форуме сообщества mailcow алерт Watchdog ALERT: sogo-mailcow тоже связывают с нехваткой workers под нагрузкой. Показательнее issue #5655 репозитория mailcow-dockerized («EAS starves SOGo threads after a while», январь 2024): у автора три личных ящика, один iPhone и дефолтные 20 workers, а через 12–24 часа EAS-запросы съедали все процессы, лог заполнялся тем же No child available, и помогал только перезапуск контейнера — до следующего раза. Мейнтейнер ответил стандартно: не хватает workers, увеличьте WOWorkersCount в data/conf/sogo/sogo.conf. А автор закрыл тикет после того, как перезагрузил сам iPhone — запросов к серверу стало заметно меньше. Вывод: иногда worker-ы съедает не парк устройств, а одно устройство, которое засыпает сервер Ping/Sync, и это видно по DeviceId в логе.

В более старом issue #2266 с той же ошибкой — 280 почтовых ящиков и WOWorkersCount = "50", SOGoMaximumPingInterval = 3540, SOGoInternalSyncInterval = 45, SOGoMaximumSyncInterval = 60. Ответ разработчика: число workers надо увеличивать, начать со 100 и смотреть логи, всё зависит от интенсивности EAS. Там же он оценил цену: 100–120 EAS-workers требуют порядка 16–20 ГБ RAM, и на сервере с 4 ГБ и двумя CPU при 100 workers нагрузка ушла под 100. Главный вывод: единого «правильного» числа нет, WOWorkersCount пересчитывается при каждом заметном росте парка мобильных клиентов, и вместе с ним — память сервера.

Как менять WOWorkersCount конкретно в mailcow

В mailcow параметры SOGo лежат в файле data/conf/sogo/sogo.conf внутри установки — это не переменная в mailcow.conf, а прямая правка конфига SOGo в plist-подобном формате. Обратите внимание на кавычки: в штатном файле значение записано как WOWorkersCount = "20";. Смотрю текущее значение и заодно интервалы:

grep -nE 'WOWorkersCount|PingInterval|SyncInterval|WatchDogRequestTimeout|SxVMemLimit' /opt/mailcow-dockerized/data/conf/sogo/sogo.conf

Меняю на посчитанное под реальный парк устройств: для «СадГавани» — 20 EAS-устройств с push плюс запас на веб-интерфейс и CalDAV для остальных сотрудников и на рост штата. Взял 32. sed учитывает кавычки — без них шаблон просто не совпадёт, и вы решите, что правка применилась:

sed -i 's/WOWorkersCount = "[0-9]*";/WOWorkersCount = "32";/' /opt/mailcow-dockerized/data/conf/sogo/sogo.conf
grep -n WOWorkersCount /opt/mailcow-dockerized/data/conf/sogo/sogo.conf

После правки перезапускаю не только SOGo, но и Memcached — документация mailcow для изменений в конфигурации SOGo везде даёт одну и ту же пару контейнеров:

docker compose restart memcached-mailcow sogo-mailcow

После этого шага «No child available» у «СадГавани» не повторялась две недели подряд под наблюдением — веб-почта в браузере перестала виснуть даже в пиковые часы, когда все 20 EAS-устройств держат push-сессии одновременно. Память контейнера SOGo по docker stats выросла с примерно 0,9 до 1,5 ГБ — на VPS с 8 ГБ это укладывается с запасом. Учтите: sogo.conf — файл из репозитория mailcow. update.sh перед обновлением коммитит локальные правки, а потом сливает апстрим с приоритетом его версии (git merge -Xtheirs), так что при конфликте в этих строках ваше значение может быть перезаписано. После каждого апдейта проверяю WOWorkersCount grep-ом.

Чек-лист из пяти пунктов подготовки SOGo и mailcow перед массовым подключением телефонов через ActiveSync
Пять шагов перед подключением новой партии телефонов — дешевле, чем разбор зависшей веб-почты постфактум.

На что ещё смотрю, если увеличение WOWorkersCount не помогло до конца

Если после увеличения WOWorkersCount алерты стали реже, но не исчезли — смотрю на интервалы. В mailcow SOGoMaximumPingInterval и SOGoMaximumSyncInterval уже стоят на 3540 секундах, как в примерах SOGo, — это настоящий push, но и максимальное время, на которое устройство удерживает worker. Уменьшив их, вы освобождаете процессы быстрее ценой менее мгновенных уведомлений: это рычаг на случай, когда добавить RAM под workers нельзя. Отдельно сверяю WOWatchDogRequestTimeout — он задаётся в минутах; в штатном конфиге mailcow стоит 30, а в примерах SOGo для push — 60. Если в логе много WOWatchDogChild ... has been hanging in the same request, а телефоны жалуются на обрывы push, поднимаю его до 60 — но осознанно, понимая, что так worker держится дольше.

Второе — прокси перед SOGo. Руководство SOGo требует, чтобы таймаут веб-сервера перед ним был не меньше интервала EAS (в примерах — 3600 секунд для Apache). В mailcow эту роль играет nginx-mailcow, и для location ^~ /Microsoft-Server-ActiveSync в штатном шаблоне уже стоят proxy_read_timeout 3600 и proxy_send_timeout 3600 — трогать их не нужно. А вот если перед mailcow есть ещё один обратный прокси или балансировщик с таймаутом 60 секунд, он будет рвать push-соединения, телефоны начнут переподключаться, и нагрузка на workers вырастет. Такой внешний прокси проверяю первым.

Третье — память. Каждый worker — отдельный процесс, и SxVMemLimit (в mailcow — 384 МБ, это же дефолт SOGo) задаёт потолок, после которого child перезапускается. На практике worker редко упирается в лимит, но умножать число workers на порядок, не посчитав RAM, опасно: вместо нехватки workers получите OOM Killer и падение SOGo целиком. Для 32 workers у «СадГавани» хватило штатной памяти VPS, а для масштабов из issue #2266 я ориентируюсь на оценку разработчика — 16–20 ГБ на 100–120 EAS-workers — и считаю память на этапе планирования, а не по факту первого падения.

Чек-лист: считаю workers перед подключением новых телефонов

Этот чек-лист я теперь прохожу перед каждым массовым подключением мобильных клиентов к почте на mailcow, а не только когда уже начались жалобы на зависшую веб-почту. Дешевле выставить workers с запасом заранее, чем разбирать логи вотчдога в разгар рабочего дня, пока бухгалтерия не может открыть письмо от банка.

И отдельно фиксирую дату и число подключённых устройств в заметке к серверу — при следующем росте штата и новой волне подключений телефонов сразу видно, от какой базы считать новое значение WOWorkersCount, а не запускать расчёт с нуля каждый раз заново.

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

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

Почему ошибка появилась не сразу, а через несколько дней после подключения телефонов?

Push-соединения ActiveSync живут до часа, и worker-процессы занимаются постепенно, по мере того как устройства устанавливают долгие Ping/Sync-сессии. В issue #5655 mailcow-dockerized автор описывает, что workers заканчивались через 12–24 часа после старта, — поэтому проблема проявляется не в первый час.

Достаточно ли одного большого WOWorkersCount, чтобы забыть про проблему навсегда?

Нет. Даже конфигурация с WOWorkersCount = 50 на 280 ящиков (issue #2266) не гарантирует, что проблема не вернётся при дальнейшем росте числа EAS-устройств с push. Значение нужно пересматривать при каждом заметном росте парка мобильных клиентов.

Правка sogo.conf в mailcow делается через переменную окружения в mailcow.conf?

Нет, WOWorkersCount и связанные параметры правятся напрямую в файле data/conf/sogo/sogo.conf внутри установки mailcow, а не через переменные в mailcow.conf — это отдельный конфиг самого SOGo.

Нужно ли перезапускать весь mailcow целиком после правки WOWorkersCount?

Нет, достаточно перезапустить SOGo и Memcached: docker compose restart memcached-mailcow sogo-mailcow — так документация mailcow рекомендует применять изменения конфигурации SOGo. Postfix, Dovecot и Rspamd эта правка не затрагивает.

Что делать, если увеличение WOWorkersCount не помогает совсем?

Посмотреть в логе sogo-mailcow, не засыпает ли сервер одно устройство (по DeviceId) — в issue #5655 помогла перезагрузка iPhone. Затем проверить WOWatchDogRequestTimeout, интервалы Ping/Sync и таймаут внешнего прокси перед mailcow, если он есть.

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

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

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

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

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

Источники

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