Перейти к содержанию

Runbook: Exchange sync по пользователю (ошибки папки/доступа/почтового ящика)

1. Когда использовать

Признаки инцидента:

  1. Ошибки ErrorFolderNotFound/ErrorInvalidFolderId/ErrorNonExistentMailbox.
  2. Синхронизация пользователя «самопроизвольно» отключилась.
  3. Растёт счётчик неудачных попыток Exchange.
  4. Встречи не появляются/не обновляются у конкретного пользователя.

2. Как это работает (по коду)

2.1 Режимы пользователя

Режим определяется парой флагов пользователя:

  1. Disabled -> DoSyncWithExchange = false и CalendarEwsDirectSync = false.
  2. Sync -> DoSyncWithExchange = true и CalendarEwsDirectSync = false.
  3. Direct -> DoSyncWithExchange = false и CalendarEwsDirectSync = true.

Admin API для управления:

  • POST /api/admin/users/{userId}/exchange/reset-fails
  • POST /api/admin/users/{userId}/exchange/update-sync-mode
  • GET /api/admin/users/{userId}/service/settings (для контроля текущего режима/счётчиков)

2.2 Fail-counter и автоотключение

  1. Ошибки Exchange увеличивают SyncWithExchangeFailedAttempts.
  2. Лимит берётся из CalendarExchangeRetryLimit.
  3. Если лимит != 0 и попытки > limit, режим пользователя переводится в Disabled.
  4. reset-fails сбрасывает счётчик в 0, но сам режим при необходимости надо выставить отдельно.

2.3 Обработка типовых ошибок Exchange

ExchangeExceptionsProcessor обрабатывает два класса ошибок:

  1. AccessDenied:
  2. очищаются EwsFolders/EwsFolderPermissions для текущего пользователя,
  3. инкрементируется fail-counter.
  4. Ошибки «плохой пользователь/ящик»:
  5. ErrorNonExistentMailbox, ErrorFolderNotFound, ErrorInvalidFolderId, ErrorInvalidSmtpAddress, ErrorMailRecipientNotFound, ErrorItemNotFound,
  6. при наличии SmtpAddress в деталях могут отключаться пользователи с этим email,
  7. иначе инкремент fail-counter по user.

2.4 Дополнительный восстановительный контур

Есть job EnableDoSyncWithExchangeJob, который может повторно включить sync пользователям, отключённым именно «по ошибке Exchange» (через служебный флаг Sync.Exchange.DisabledByError).

3. Что смотреть при разборе (чек-лист)

  1. Зафиксировать userId, время ошибки, текст ServiceError.
  2. Проверить сервисные настройки пользователя (service/settings):
  3. ewsMode,
  4. syncWithExchangeFailedAttempts,
  5. calendarExchangeRetryLimit,
  6. sid.
  7. Если попытки выше лимита:
  8. сбросить fails,
  9. вернуть нужный режим sync вручную.
  10. Проверить, какой класс ошибки:
  11. AccessDenied -> проверка доступа/impersonation/кеша папок,
  12. mailbox/folder ошибки -> проверка email/папки/calendar mailbox.
  13. Для AccessDenied убедиться, что после очистки кэша папок и повторной попытки состояние изменилось.
  14. Если проблема массовая (несколько пользователей) — проверять Exchange service settings и инфраструктурную доступность EWS.

4. Симптом -> вероятная причина -> проверка

Симптом Вероятная причина Что проверить первым
У пользователя ewsMode = Disabled после серии ошибок превышен retry limit syncWithExchangeFailedAttempts vs calendarExchangeRetryLimit
ErrorAccessDenied права/impersonation/доступ к папке записи EwsFolders/EwsFolderPermissions, параметры impersonation
ErrorNonExistentMailbox/ErrorInvalidSmtpAddress некорректный email/mailbox Users.Email, детали SmtpAddress в логе
ErrorFolderNotFound/ErrorInvalidFolderId невалидный folder link/изменение папки в Exchange повторная инициализация папок, проверки кэша папок
После reset-fails ситуация не изменилась сбросили только счётчик, но не режим текущий ewsMode и update-sync-mode
Событие создано в Outlook, комментарий в задачу не приходит, нет записей в AutomationScriptsLog (Type=10) Settings.DefaultEwsServiceId = NULL и нет AD-профилей, привязанных к EWS → GetAllExchangeServices() вернул пустой список → подписки не стартовали (тихий отказ без лога) SELECT DefaultEwsServiceId FROM Settings; проверить EwsServiceSettings; подробнее — provider-ews.md раздел «Инициализация»

5. SQL для быстрой диагностики

declare @user_id int = 0;

-- Состояние пользователя по Exchange
select
        u.UserID,
        u.Email,
        u.DoSyncWithExchange,
        u.CalendarEwsDirectSync,
        u.SyncWithExchangeFailedAttempts,
        u.SID
from dbo.Users u
where u.UserID = @user_id;

-- Служебные флаги Exchange по пользователю
select
        uiev.UserID,
        uie.SysName,
        uiev.Value,
        uiev.IntValue
from dbo.UserInfoExtValues uiev
join dbo.UserInfoExt uie
        on uie.ID = uiev.InfoExtID
where uiev.UserID = @user_id and
      lower(uie.SysName) in (
            'sync.exchange.disablederror',
            'sync.exchange.itemsstate'
      )
order by
        uie.SysName;

-- Кеш папок Exchange для пользователя
select top (200)
        ef.UserID,
        ef.ServiceId,
        ef.ChangeDate,
        ef.CalendarFolderValue,
        ef.InboxFolderValue
from dbo.EwsFolders ef
where ef.UserID = @user_id
order by
        ef.ChangeDate desc;

-- Кеш прав на папки Exchange
select top (200)
        ep.UserID,
        ep.FolderOwnerId,
        ep.ServiceId,
        ep.ChangeDate,
        ep.FolderValue
from dbo.EwsFolderPermissions ep
where ep.UserID = @user_id
order by
        ep.ChangeDate desc;

-- Настройки сервиса Exchange (без пароля)
select
        ess.ServiceId,
        ess.EwsUrl,
        ess.EwsLogin,
        ess.EwsDomain,
        ess.UseImpersonalization,
        ess.UseSIDForImpersonalization
from dbo.EwsServiceSettings ess
order by
        ess.ServiceId;

6. Что приложить в задачу разработчикам

  1. userId, время ошибки, точный ServiceError.
  2. Снимок GET /api/admin/users/{userId}/service/settings.
  3. Результаты SQL из блока выше.
  4. Что уже делали: reset-fails, смена режима (update-sync-mode), повтор синка.
  5. Масштаб: один пользователь или массово.

Связанные документы

  • docs/domains/calendar/data-flow.md
  • docs/domains/calendar/backend.md
  • docs/domains/integrations/exchange-integration-overview.md
  • docs/domains/integrations/backend.md
  • admin.md