Всем привет, меня зовут Александр, я работаю в команде СЭД компании ДОМ.РФ.

Сначала коротко про СЭД ДОМ.РФ - это система электронного документооборота «СДУ Приоритет» от компании Digital Design, на основе платформы DocsVision одноименного разработчика. Количество пользователей – более 5000 из числа сотрудников ДОМ.РФ и дочерних организаций. Система тяжелая, предельно кастомизированная под наши потребности, продуктивная среда размещена на 27 серверах. СЭД интегрирован с пятью системами не считая BI, еще 3 интеграции находятся в процессе разработки/тестирования.

Изменения в продуктивную среду приходят с двух источников:

  • Вендор – обновления платформы, доработки под наши потребности, исправления ошибок, новые модули;

  • Внутренняя команда разработки – разработка на уровне БД, а также интеграционные сервисы.

Историй в джире за год набирается несколько сотен, и, хотя большинство из них – мелкие ошибки, которые исправляются в рамках одного-двух релизов, объем изменений достаточно большой. В результате мы подходим к тому, что безотносительно качества разработки и тестирования, сам процесс выкатывания изменений в продуктивную среду имеет вероятность что-либо сломать. Соответственно, план отката изменений – неотъемлемая часть процесса. От плана отката зависит требуемое технологическое окно и, как следствие, способны ли мы проводить работы в ночное время, а может быть в выходные дни, или вообще стоит перенести работы на новогодние праздники (реальная история).

В этой статье я расскажу, как Always On Availability Groups помогает значительно сократить требуемое технологическое окно за счёт оптимальной процедуры отката со стороны БД, также как подружить СЭД с группами доступности. Во второй части статьи речь пойдет о том, как мы провели дедубликацию файлов в СЭД на уровне БД и сократили объем БД на 8Тб без потери информации, и как нам помогли в этом группы доступности.

Rollback – наше все!

Рассмотрим откат со стороны БД. Вариантов отката немало, перечислю основные:

  • Снапшоты на уровне MSSQL. Хороший вариант, когда не предполагается интенсивная запись/обновление данных, иначе потребуется дополнительное дисковое пространства, а кроме того – просядет скорость записи на диск. Если возникнет нужда отменить тяжелую транзакцию, то придется ждать rollback.

  • Снапшоты сервера БД на уровне ВМ. Рабочий вариант, если ожидаются минимальные изменения в БД. Условным минусом является необходимость привлечения специалиста по виртуальной инфраструктуре. Условно, потому что специалист скорее всего и так понадобится для снятия снапшотов серверов приложений.

  • Снапшоты LUN на уровне СХД. Зависит от реализации на самой СХД. Главный минус – требуется привлечение специалиста по СХД.

  • Классические резервные копии. Хороший вариант, но не тогда, когда объем БД 19Тб.

  • Откат через группы доступности.

План отката через группы доступности:

  • Останавливаем репликацию нашей БД (Suspend Data Movement) перед основными работами.

  • Заглядываем в графану, в которую через заббикс поступает информация о времени последнего коммита на репликах. При остановленной репликации выглядит примерно так:

Как забрать данные о коммитах через View?

View может быть и проще, не обязательно подавать время в формате unixtimestamp на заббикс.

CREATE view [dbo].[anik_Monitoring_CommitTime]

as

SELECT

         DATEDIFF(s, '1970-01-01 03:00:00', HADRRS.last_commit_time) as last_commit_time

       ,AVR.replica_server_name

         ,CASE

         when HADRRS.is_primary_replica = 1 then 'Primary'

         when HADRRS.is_primary_replica = 0 then 'Secondary'

         end as IsPrimary

  FROM [master].[sys].[dm_hadr_database_replica_states] as HADRRS

  JOIN [master].[sys].[availability_replicas] as AVR

    on HADRRS.replica_id = AVR.replica_id

       join master.sys.databases as DBS

       on HADRRS.database_id = DBS.database_id

       where DBS.name = 'Database_Name'

GO

Также на заббиксе установлен триггер, который при значительным превышении разницы между значениями первой и второй реплики поднимает в ружье ГДА (группа дежурных администраторов) и команду СЭД.

  • В случае успешного завершения включаем репликацию обратно (Resume Data Movement).

  • Откат. Что-то пошло не так. Мы удаляем из группы доступности нашу БД, переключаем Primary на вторую реплику и проверяем работоспособность. Процедура отката завершена.

  • Удаляем некорректную БД, добавляем БД в группу доступности с запуском automatic seeding. Данный пункт можно выполнить как сразу, так и в удобное время. Влияние на производительность БД некритичное, но требует широкого канала между репликами (мы утилизировали 10 гигабит на 40-45%).

Узнать статус seeding и перезапустить при необходимости.

Узнать статус seeding.

На primary:

SELECT * FROM [master].[sys].[dm_hadr_automatic_seeding]; 
SELECT * FROM [master].[sys].[dm_hadr_physical_seeding_stats];

Удобный запрос для seсondary:

SELECT 
r.session_id, r.status, r.command, r.wait_type , 
r.percent_complete, r.estimated_completion_time 
FROM sys.dm_exec_requests r JOIN sys.dm_exec_sessions s 
ON r.session_id = s.session_id 
WHERE r.session_id <> @@SPID 
AND s.is_user_process = 0 
AND r.command like 'VDI%' 
AND wait_type ='BACKUPTHREAD'

Перезапустить seeding если процесс не начался 

ALTER AVAILABILITY GROUP [sed-ag] 
MODIFY REPLICA ON '[db-node2] ' 
WITH (SEEDING_MODE = MANUAL);  

ALTER AVAILABILITY GROUP [sed-ag] 
MODIFY REPLICA ON 'db-node2' 
WITH (SEEDING_MODE = AUTOMATIC);

Итого, мы закладываем в план по 15 минут на подготовку и 15 минут на сам откат, при этом нам не принципиален объем планируемых изменений в БД, и мы уверены, что процедура займет именно столько времени, сколько на нее заложено. И самое главное - за счет отсутствия необходимости привлечения дополнительных специалистов к работе в выходные мы экономим время на обсуждениях и согласованиях.

Дружим СЭД с группами доступности

Главной проблемой является то, что СЭД использует объекты за пределами основной БД, которые нельзя синхронизировать через группы доступности, а именно:

  • Метаданные в базе Temp. При отработке failover мы получаем ошибки отображения карточек в папках веб-клиента, отображается меньше карточек, чем должно быть. Подозреваю, что мы могли бы решить проблему настройками платформы и перенести все необходимое в основную БД, но оценить последствия такого достаточно сложно.

  • Jobs MSSQL агента. Мы очень любим эти джобы, в которых крутятся более 100 шагов, реализующих бизнес-логику.

Вторичная реплика имеет 3 режима доступа на чтение (Readable Secondary): Yes, No, Read-intent only.

Yes – разрешены все соединения, но только для операций чтения, Read-intent only – разрешены соединения только на чтение. Нам нужна возможность читать secondary с различных BI, но Read-Only Routing не нужен, поэтому у нас стоит просто Yes. И тут появляется проблема! Бог с ними, с замусоренными логами, а вот некоторые джобы занимаются рассылками отчетности, которую мы не успели перетащить в BI, и они вполне себе сработают на вторичной реплике, что плохо.

Сам факт изменения джобов не является проблемой, несколько минут, которые уйдут на актуализацию, особой роли не сыграют. Автоматизировать этот процесс мы не стали, т.к. он и так предельно простой.

Как быстро перенести задания агента

Object Explorer Details (F7) в SQL Management Studio позволяет проводить групповые Script To над джобами, единственный минус – необходимо проверить триггеры, т.к. они ссылаются на джобы по id.

Для решения указанных проблем сначала нам надо определить факт смены статуса реплик. Используем для этого триггеры:

USE [msdb]

GO

EXEC msdb.dbo.sp_add_alert @name=N'On change state to primary',

             @message_id=0,

             @severity=10,

             @enabled=1,

             @delay_between_responses=0,

             @include_event_description_in=0,

             @event_description_keyword=N'"Database_Name" is changing roles from "RESOLVING" to "PRIMARY"',

             @category_name=N'[Uncategorized]',

             @job_id=N'248bb05a-93a1-4f39-a574-354a30c97897'

GO

EXEC msdb.dbo.sp_add_alert @name=N'On change state to secondary',

             @message_id=0,

             @severity=10,

             @enabled=1,

             @delay_between_responses=0,

             @include_event_description_in=0,

             @event_description_keyword=N'to "SECONDARY" because the mirroring session or availability group failed over due to role synchron',

             @category_name=N'[Uncategorized]',

             @job_id=N'248bb05a-93a1-4f39-a574-354a30c97897'

GO

Ключевой параметр – @severity=10, именно такую серьезность имеют события смены состояния реплики.  Job_id - айди джоба который необходимо запустить. И вот мы узнали о событии смены состояния реплики. Теперь пришла очередь нашего универсального джоба. Для контроля ситуаций, когда определенные выключенные джобы не должны быть включены и наоборот, мы используем слова DontTouch в названии. Для пересоздания таблиц метаданных в Temp мы используем платформенную процедуру dvsys_metadata_validate_all_objects.

Универсальный джоб:

if (SELECT sys.fn_hadr_is_primary_replica ('Database_Name')) = 1

begin

       exec [Database_Name].[dbo].[dvsys_metadata_validate_all_objects] @RecreateJob = 0, @WithDropExisting = 0;

end

declare @State int

       set @State = (SELECT sys.fn_hadr_is_primary_replica ('Database_Name'))

declare @JName as varchar(max)

DECLARE j_cur CURSOR FOR

   select

             j.Name

       from [msdb].[dbo].sysjobs j

       where

             j.Name not like 'DontTouch%'

OPEN j_cur

FETCH NEXT FROM j_cur INTO @JName

WHILE @@FETCH_STATUS = 0

BEGIN

       exec [msdb].[dbo].sp_update_job @job_name = @JName, @enabled = @State

 

FETCH NEXT FROM j_cur

INTO @JName

END

CLOSE j_cur

DEALLOCATE j_cur

Ну и завершающая неожиданная проблема - Invalid Urn filter on server level: filter must be empty or server attribute must be equal with the true server. Ошибка появляется при попытке сервера приложений создать объекты в БД и влияет на корректность отображения папок в web-клиенте. Культурное решение найти не удалось. Обходное магическое решение – подключение сервера приложений к прослушивателю под учетной записью, от которой работает SQL Server.

О MULTISUBNETFAILOVER замолвите слово

Наша стратегия аварийного восстановления основана на том, что сервера СЭД равномерно распределены по двум ЦОД. Мы используем «родные» для ЦОД подсети, избегая «растянутых», т.е. реплики находятся в разных подсетях. Значение параметра RegisterAllProvidersIP установлено в 1, соответственно DNS запись прослушивателя выглядит так:

Практические выводы:

  • Multisubnetfailover позволяет избежать манипуляций с DNS и сопутствующих задержек.

  • SQL-client из .NET Framework версии 4.6.1 и выше по умолчанию использует multisubnetfailover=1 и прописывать в строке подключения данный параметр не нужно.

  • СЭД на актуальной версии платформы использует .NET Framework версии 4.6.1 и выше, соответственно все работает без проблем.

  • СЭД на платформе старых версий, например, DocsVision5 невозможно подружить с подобной конфигурацией, т.к. версия SQL-клиента требует явного указания параметра, а возможность его указания в строке подключения сервера приложений к БД не предусмотрена.

  • Для работы Zabbix потребуется драйвер msodbcsql, а также явное указание параметра в строке подключения.

  • Если потребитель не может использовать для подключения свежий .Net Framework или msodbcsql с параметром, то придется использовать прямое подключение к одной из реплик.

Кворумные боли

Кворум в нашем случае – самое узкое место для автоматического фэйловера. Мы используем file share witness. Сама сетевая папка размещена в том же ЦОД2, где размещена вторичная реплика. При отказе ЦОД1 с первичной репликой на борту вторичная реплика не утратит кворума и фэйловер отработает как надо. Но при отказе ЦОД2 (Secondary и witness), Primary останется в одиночестве и утратит кворум, а БД будет отключена и ее придется поднимать вручную. Вопрос, «и так сойдёт» или размещаем witness в ЦОД3?

FAILOVER на практике

Как быстро происходит переключение?

Мгновенно. Но сами сервера приложений будут ожидать истечения таймаутов перед повторным запросом.

Как выглядит момент переключения реплик для пользователя СЭД?

Как 20-секундная задумчивость сервера. Все действия, совершенные пользователем в момент переключения завершаются без ошибок.

Применялся ли фэйловер на практике?

Да, в ручном режиме. После большого обновления СЭД мы столкнулись с ростом нагрузки на ЦПУ вплоть до 100%, что привело к катастрофическому падению производительности. Manual failover позволил «на лету» добавить ядра ЦПУ и снизить остроту проблемы на время поиска источника повышенной нагрузки. Можно сказать, совместили приятное с полезным, хе-хе.

Заключение

Выводы о том, действительно ли так хороши расхваливаемые мной группы доступности  и  курсоры оставлю на откуп читателям. В следующей части этой статьи расскажу о том, как дедубликация файлов и группы доступности позволили нам сократить объем БД на 8Тб без потери информации.

Комментарии (2)


  1. VitalKoshalew
    14.09.2021 21:48

    Ну и завершающая неожиданная проблема — Invalid Urn filter on server level: filter must be empty or server attribute must be equal with the true server. Ошибка появляется при попытке сервера приложений создать объекты в БД и влияет на корректность отображения папок в web-клиенте. Культурное решение найти не удалось. Обходное магическое решение – подключение сервера приложений к прослушивателю под учетной записью, от которой работает SQL Server.

    Что поддержка Microsoft говорит? «Решение» очень спорное.

    N.B. Очень рекомендую всегда и всё в IT настраивать по-английски. Другие языки — для конечных пользователей. Очень сложно потом разбираться с ошибками всяких «прослушивателей».

    Отдельный вопрос: каков план отката в случае обнаружения критической проблемы после начала рабочего дня? Все эти подмены серверов подразумевают отсутствие Staging серверов, иначе вероятность поломать систему обновлением достаточно мала, а больше они ни от чего не защищают. По-моему лучше иметь Staging на бесплатной Developer лицензии — ресурсов он будет требовать относительно немного.


    1. Elen_Dor Автор
      20.09.2021 10:50

      Решение спорное и временное. Настолько временное, что даже постоянное.

      N.B. 'Прослушиватель' – listener – вольная трактовка, т.к. термин не перегружен смыслами.

      Обычно при значительных изменениях в БД вторичная реплика не синхронизируется до вечера, но не для отката в одну кнопку, а для возможности вытащить и восстановить какие-то определённые данные, т.к. откат по истечению нескольких часов будет катастрофой похуже, чем неработоспособность за то же время. А вообще, преимущества отката видны во второй части статьи.