В предыдущей статье серии я рассказывал о вариантах включения в пакеты SSIS — или в планы обслуживания SQL Server (которые, в сущности, представляют собой специализированные пакеты SSIS) — проверок на выполнение условия if-checks. По завершении этого процесса, когда дело дойдет до управления пакетами SSIS в сочетании с группами доступности AlwaysOn, вам предстоит выполнить кое-какие дополнительные действия. А точнее, осуществить процесс синхронизации пакетов SSIS на различных экземплярах SQL Server, где размещаются группы доступности, а также обеспечить дальнейшую синхронизацию этих пакетов после развертывания (чтобы избежать неприятных сюрпризов в ходе обновления или при внесении тех или иных изменений).

, размещенных на разных серверах

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

С примером того, как осуществляется эта операция, вы можете ознакомиться в статье, разъясняющей, как переносить, скажем, планы обслуживания с одного сервера на другой (mssqltrek.com/2011/08/27/how-to-transfercopy-maintenance-plans-from-one-server-to-other/).

Еще один вариант, который можно использовать при работе со значительным числом пакетов, состоит в том, чтобы задействовать dtutil (msdn.microsoft.com/en-us/library/ms162820.aspx?f=255&MSPPError=-2147217396). Документацию по этой утилите вы сможете найти, если введете в строку поиска ее название и/или фразу типа transfer SSIS packages between servers.

Разумеется, нужно иметь в виду, что приведенные выше варианты всего лишь показывают, как именно осуществляется синхронизация или копирование пакетов с одного сервера на другой. Если же эти пакеты выполняются в рамках запланированных заданий — или в качестве заданий агентов SQL Server, вам придется синхронизировать и упомянутые задания. Это сделать довольно просто: нужно записать задания в виде сценария на исходном сервере, а затем развернуть упомянутые сценарии на целевых серверах, как указано в предыдущей статье серии. При этом важно, что для выполнения заданий на базе SSIS требуется, чтобы задание указывало цель как для агента SQL Server, так и для базового пакета; это означает, что вам придется синхронизировать обе части всякий раз, когда вы будете настраивать пакеты SSIS с базами данных групп доступности, или всякий раз, когда в задания, в базовые пакеты или как в задания, так и в пакеты будут вноситься какие-либо изменения.

Сохранение согласованности пакетов

Разумеется, цель синхронизации прежде всего в том и состоит, чтобы в случаях, когда администратор использует пакеты SSIS для управления резервными копиями или когда эти пакеты выполняются в качестве пакетных заданий (см. статью «Определение пакетных заданий»), обеспечивать контроль не только за тем, чтобы задания были идентичны на всех серверах, где размещаются группы доступности (то есть чтобы при их выполнении выполнялись идентичные логика, последствия или операции), но и за тем, чтобы упомянутые задания или операции выполнялись только на целевой или предпочтительной реплике. В предыдущей статье речь шла о том, как можно с легкостью дополнять пакеты SSIS логическими конструкциями, позволяющими осуществлять проверки на выполнение условий, чтобы обеспечивать выполнение тех или иных операций только на соответствующем сервере. Кроме того, выше я привел несколько ссылок и информацию о том, как осуществлять первоначальную синхронизацию пакетов или синхронизацию в ручном режиме при внесении изменений в тот или иной пакет.

Но в случае, когда основная логическая конструкция или операции размещаются на нескольких серверах, возникает проблема сохранения согласованности этих пакетов или заданий. Для иллюстрации представим такую ситуацию. Предположим, вы настроили группы доступности для обеспечения высокой доступности или восстановления после сбоя, но в то же время у вас имеется группа хранилищ данных, в которых регулярно выполняются процессы извлечения, преобразования и загрузки данных с использованием контента ключевых баз данных. В этом случае необходимо не только позаботиться о том, чтобы извлечение данных осуществлялось из идеальных или предпочтительных реплик, то есть перенос этого процесса на реплики, предназначенные только для чтения, на первом этапе, возможно, представляется вполне логичным — если не считать проблем лицензирования и логики «поддержания актуальности» операций записи, которые, возможно, в конце концов будут использоваться. Вдобавок к этому вам нужно обеспечить нормальное функционирование системы в случае, когда, скажем, младший разработчик ETL вносит некоторые изменения в пакет и выкладывает эти изменения только на сервер, где размещается, допустим, главная реплика одной из ваших баз данных, входящих в состав группы доступности. Если такая ситуация действительно возникнет, то в тот момент, когда произойдет аварийное переключение на другой хост, случится одно из двух. Либо вновь добавленное задание, выложенное разработчиком, не будет выполняться на новом сервере, либо — если внесенное в пакет SSIS соответствующее «изменение» представляет собой модификацию существующего пакета — во время исполнения к новой реплике будут применены старый пакет или прежняя логика.

Разумеется, ни один из перечисленных вариантов не дает результата, даже отдаленно напоминающего тот, который вам нужен. Конечно, вы можете создать политику или контрольный список мер, которые следует применять всякий раз, когда запускаются пакеты SSIS, но… Надеюсь, вы не сочтете меня безнадежным скептиком, если я выражу сомнение в том, что такой процесс будет выполняться безупречно всякий раз. Поэтому я предлагаю вашему вниманию следующий сценарий; можете регулярно использовать его для опросов (или проверок) пакетов SSIS, независимо от того, идет ли речь об обычных пакетных заданиях SSIS или о пакетах SSIS, используемых в работе с планами обслуживания, и для отчетов по любым проблемам синхронизации (см. листинг).

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

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

Что же касается прочих аспектов проблемы, прошу вас иметь в виду, что, говоря здесь о задачах синхронизации SSIS, я демонстрирую всего лишь верхушку айсберга. Если угодно, вам предлагается описание подхода к решению вопроса в самом общем виде. Используя этот подход в рабочих средах, я добивался значительных успехов. Но при этом мне приходилось сталкиваться и с затруднениями, и со сбоями. Во многих случаях пакеты SSIS могут быть до абсурдного сложными (а также неустойчивыми и «ломкими»). Таким образом, даже если представленные на разных серверах экземпляры пакета идентичны, из этого отнюдь не следует, что пакет SSIS всегда будет выполняться на сервере, где он не был протестирован (просто потому, что детали подключения, пути к файлам либо папкам (или же правила безопасности, управляющие доступом к этим путям) могут быть абсолютно иными или не на 100% синхронизированными). В целом, если вы используете пакеты SSIS в сочетании с базами данных групп доступности, обязательно проследите за тем, чтобы при подключении к базам данных групп доступности использовались прослушиватели групп доступности, а также удостоверьтесь в том, что в пакетах применяется соответствующая логика if/else (или что вы просто активируете и деактивируете задания по мере необходимости). И еще одно замечание: единственный способ обеспечить выполнение перечисленных выше пунктов — с помощью тестирования. Но эта процедура наверняка уже стала для вас привычной, после того как в вашей среде появились группы доступности.

Далее мы обсудим некоторые соображения, касающиеся структур, не укладывающихся в схему «простых двухузловых» топологий групп доступности, и рассмотрим другие подходы к темам и задачам, о которых я писал в ранее опубликованных статьях.

Заключительные соображения относительно резервных копий

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

Рекомендации по работе с резервными копиями групп доступности AlwaysOn

Как вы, возможно, догадались, рекомендации по управлению резервными копиями в средах с использованием групп доступности принципиально не отличаются от рекомендаций для работы с обычными базами данных. Единственное отличие состоит в том, что в первом случае администратору приходится иметь дело с рядом дополнительных опасностей и ловушек.

Перечислим, что нужно сделать и за чем проследить в процессе управления резервными копиями для групп доступности AlwaysOn.

  • Обеспечьте регулярное тестирование резервных копий. В одних сетях достаточно будет делать это раз в неделю или раз в месяц, в других даже интервал в сутки может оказаться слишком большим — только вы можете судить о том, насколько важно для ваших данных находиться под постоянной защитой; и не забывайте, что данные высокой доступности не относятся к той же категории, что и данные, защищаемые от аварийных отказов, таких как определенные формы повреждений, программные сбои, ошибки пользователей и т. д.
  • Обязательно документируйте все процессы и процедуры, необходимые для восстановления данных в чрезвычайной ситуации. Подготовкой такой документации должен заниматься сотрудник, который может дать разъяснения по максимальному числу чрезвычайных ситуаций и задокументировать способы выхода из таких ситуаций или пояснения к ним, но написана она должна быть на уровне, понятном для техников младшего звена, поскольку, скорее всего, именно такие специалисты прибудут по вызову или будут находиться на дежурстве в момент сбоя базы данных.
  • Позаботьтесь о том, чтобы у вас под рукой были соглашения об уровне обслуживания или документы, определяющие допустимый объем возможных потерь данных, а также приемлемое время простоя в случае сбоя; они помогут вам определить показатели эксплуатационной готовности. Если эти показатели не будут четко определены и доступны, вы просто не сможете добиться успеха. Возможно, вы восстановите данные после катастрофического сбоя, однако может статься, что никто из руководства не имеет ни малейшего понятия о том, что система SQL Server может простаивать в течение какого-то времени, и никто не будет ожидать «настоящего» простоя, потому что, согласно представлениям руководства, вы заплатили за оборудование и лицензии SQL Server, а значит, получили гарантию того, что ваши базы данных всегда доступны.
  • Обязательно проследите за тем, чтобы ваша документация содержала сведения о том, как осуществляется диагностика максимально широкого круга неисправностей (отказы кластеров, повреждения баз данных, поврежденные или взломанные базы данных, потенциально поврежденные базы данных и т. п.).
  • Проследите за тем, чтобы в ваших документах по восстановлению после аварийного сбоя содержалась информация о том, какой более высокой инстанции следует передавать вопросы (включая актуальную контактную информацию), если события будут разворачиваться не так, как вы ожидали.
  • Обязательно проследите за тем, чтобы точные сведения о месте размещения резервных файлов и о том, как вы построили архитектуру резервных копий групп доступности AlwaysOn, были задокументированы (то есть речь идет о том, как была определена предпочтительная реплика, куда пересылаются файлы и т. д.).
  • Обеспечьте регулярное обновление документации.

Выполняя перечисленные базовые рекомендации по управлению резервными копиями, вы должны в то же время держать в поле зрения некоторые обстоятельства (или «подводные камни»).

  • Если вы исправно проверяете работу групп доступности перед их развертыванием в рабочих сетях, то знаете, что процедура восстановления данных с резервных копий — дело нелегкое, поскольку восстановление не может осуществляться «через голову» базы данных, входящей в состав группы доступности (в том смысле, что эту базу нужно будет извлекать из группы доступности или вообще полностью расформировывать последнюю до начала восстановления, а после восстановления вам, разумеется, придется повторно инициализировать синхронизацию базы данных, вновь включая ее в состав группы доступности).
  • С учетом упомянутых сложностей (а также высокой вероятности простоев) у вас возникает реальная потребность в приобретении агента чтения журнала от стороннего поставщика. Подобный агент поможет вам справиться с ситуациями, связанными с «логическим повреждением» данных, или пригодится в случаях, когда целостность вашей базы данных нарушена. Так вот, агент чтения журнала от стороннего поставщика позволит вам восстановить данные после сбоя (если вы располагаете добротными резервными копиями), не требуя при этом начинать все с чистого листа и не вынуждая вас переводить системы в автономный режим или восстанавливать все данные на момент времени, предшествующий их повреждению.

Кроме того, вам нужно будет обращать особое внимание на «фрагментацию» резервных копий (рассмотренную в одной из статей серии), а также обеспечивать корректную «синхронизацию» заданий резервного копирования и логических схем на всех хостах для реплик (как описано в предыдущих статьях).

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

Листинг. Сценарий опросов пакетов SSIS
IF OBJECT_ID('dbo.dba_SyncCheckMaintenancePlans','P') IS NOT NULL
        DROP PROC dbo.dba_SyncCheckMaintenancePlans
GO

CREATE PROC dbo.dba_SyncCheckMaintenancePlans

        @MailProfileName        sysname = NULL, -- напр., 'General'
        @OperatorName           sysname = NULL, -- напр., 'Alerts',
        @PrimaryAGAfinity       sysname = NULL, -- см. комментарии выше.
        @IgnoredPlans           nvarchar(MAX) = ‘’,  -- имена любых планов
        обслуживания, которые следует игнорировать
        @ConsoleOnly            bit     = 0             -- вывод только на консоль
        - не пересылать предупреждения по электронной почте
        (для отладки, выполнения в ручном режиме и т.д.).
AS
        SET NOCOUNT ON;
        IF @PrimaryAGAfinity IS NOT NULL BEGIN
                IF (SELECT dbo.fn_hadr_group_is_primary
                (@PrimaryAGAfinity)) = 0 BEGIN
                        PRINT 'Server is Not Primary.'
                        RETURN 0;
                END
        END

        -- если мы не выполняем этот код вручную, удостоверьтесь,
        что данный сервер основной:
        IF @ConsoleOnly = 0 BEGIN - если мы не осуществляем
        выполнение в «ручном» режиме - удостоверьтесь, что мы имеем
        все параметры:
                IF ISNULL(@MailProfileName, '') = '' BEGIN
                        RAISERROR('Procedure expects parameter
                        ''@MailProfileName'' which was not provided.', 16, 4);
                        RETURN -2;
                END

                IF ISNULL(@OperatorName, '') = '' BEGIN
                        RAISERROR('Procedure expects parameter
                        ''@OperatorName'' which was not provided.', 16, 4);
                        RETURN -3;
                END
        END;

        CREATE TABLE #IgnoredPlans (
                plan_name sysname
        );
        -- однострочная/встроенная «разделенная» функция:
        DECLARE @DeserializedPlans nvarchar(MAX) = N'SELECT '
        + REPLACE(REPLACE(REPLACE(N'''''','{0}',@IgnoredPlans), ',', ''','''),
        ',', ' UNION SELECT ');

        INSERT INTO #IgnoredPlans
        EXEC(@DeserializedPlans);

        DECLARE @LocalServerName sysname = @@SERVERNAME;
        DECLARE @RemoteServerName sysname;
        SET @RemoteServerName = (SELECT TOP 1 name
        FROM PARTNER.master.sys.servers WHERE server_id = 0);
        -------------------------------------------------------------------------
        -- начинаем с формирования списка всех планов, вызываемых
        любым заданием на данном сервере:
        DECLARE @MaintenancePlanJobs TABLE (
                job_name sysname NOT NULL,
                plan_name sysname NOT NULL
        );
        -- находим задания по планам обслуживания по следующему:
        DECLARE @MaintPlanStartPattern nvarchar(100) = N'/Server
        "$(ESCAPE_NONE(SRVR))" /SQL "Maintenance Plans\';
        DECLARE @MaintPlanEndPattern nvarchar(100) = N'" /set
        "\Package\Full Backups.Disable;false"';
        WITH core AS (
                SELECT
                        j.name [job_name],
                        REPLACE(js.command, @MaintPlanStartPattern, '')
                        [name_plus]
                FROM msdb.dbo.sysjobsteps js
                INNER JOIN msdb.dbo.sysjobs j ON j.job_id = js.job_id
                WHERE command LIKE @MaintPlanStartPattern + '%'
        )
        INSERT INTO @MaintenancePlanJobs
        SELECT
                job_name,
                LEFT(name_plus, CHARINDEX('»', name_plus) - 1) [plan_name]
        FROM
                core;
        -- Теперь извлекаем локальную/удаленную информацию
        о пакетах/планах:
        CREATE TABLE #LocalPlans (
                id uniqueidentifier,
                name sysname,
                createdate datetime,
                ownersid nvarchar(128),
                vermajor int,
                verminor int,
                verbuild int,
                verid uniqueidentifier
        );
        CREATE TABLE #RemotePlans (
                id uniqueidentifier,
                name sysname,
                createdate datetime,
                ownersid varbinary(128),
                vermajor int,
                verminor int,
                verbuild int,
                verid uniqueidentifier
        );
        INSERT INTO #LocalPlans
        SELECT
                id,
                name,
                createdate,
                ownersid,
                vermajor,
                verminor,
                verbuild,
                verid
        FROM
                msdb.dbo.sysssispackages
        WHERE
                packagetype = 6; -- план обслуживания через дизайнер/мастер
        INSERT INTO #RemotePlans
        SELECT
                id,
                name,
                createdate,
                ownersid,
                vermajor,
                verminor,
                verbuild,
                verid
        FROM
                PARTNER.msdb.dbo.sysssispackages
        WHERE
                packagetype = 6; -- план обслуживания через дизайнер/мастер
        -- Запустить проверки сравнением (???comparison checks):
        CREATE TABLE #Divergence (
                name sysname,
                diff_type sysname
        );
        INSERT INTO #Divergence
        SELECT
                name,
                'Maintenance Plan Exists on ' + @LocalServerName + ' Only.'
        FROM
                #LocalPlans
        WHERE
                name NOT IN (SELECT name FROM #RemotePlans);
        INSERT INTO #Divergence
        SELECT
                name,
                'Maintenance Plan Exists on ' + @RemoteServerName + ' Only.'
        FROM
                #RemotePlans
        WHERE
                name NOT IN (SELECT name FROM #LocalPlans);
        INSERT INTO #Divergence
        SELECT
                lp.name,
                'Maintenance Plan Differences (owner, create date, version,
                SID, etc) between servers.'
        FROM
                #LocalPlans lp
                INNER JOIN #RemotePlans rp ON rp.name = lp.name
        WHERE
                lp.createdate != rp.createdate
                OR lp.ownersid != rp.ownersid
                -- ИЛИ lp.verid != rp.verid -- MKC: На разных серверах они
                всегда будут иметь разные значения
                OR lp.vermajor != rp.vermajor
                OR lp.verminor != rp.verminor
                OR lp.verbuild != rp.verbuild;
        -- Сообщаем о любых ошибках:
        IF (SELECT COUNT(*) FROM #Divergence WHERE name NOT IN
        (SELECT name FROM #IgnoredPlans)) > 0 BEGIN
                DECLARE @subject sysname = 'SQL Server Maintenance Plan
                Synchronization Problems';
                DECLARE @crlf char(2) = CHAR(13) + CHAR(10);
                DECLARE @tab char(1) = CHAR(9);
                DECLARE @message nvarchar(MAX) = 'Problems detected with
                the following Maintenance Plans: '
                + @crlf;
                SELECT
                        @message = @message + @tab + name + ': ' + @crlf +
                        @tab + @tab + diff_type + @crlf
                FROM
                        #Divergence
                WHERE
                        name NOT IN (SELECT plan_name FROM #IgnoredPlans
                        WHERE plan_name != '')
                ORDER BY
                        NAME;
                SELECT @message += @crlf + @crlf + 'Maintenance Plans must
                be synchronized by Exporting them from SSIS as File System
                packages and then re-importing them to the target server.'
                        + @crlf + @tab + 'Once Maintenance Plans have been
                        synchronized, you should synchronize corresponding Jobs
                        derived from the plans as well.'
                        + @crlf + @tab + 'Please consult documentation for
                        additional help with synchronizing Maintenance Plans.';
                IF @ConsoleOnly = 1 BEGIN
                        -- Распечатываем детали:
                        PRINT 'SUBJECT: ' + @subject;
                        PRINT 'BODY: ' + @crlf + @message;
                  END
                ELSE BEGIN
                        -- направляем сообщение:
                        EXEC msdb..sp_notify_operator
                                @profile_name =
@MailProfileName,
                                @name = @OperatorName,
                                @subject = @subject,
                                @body = @message;
                END;
        END
        DROP TABLE #LocalPlans;
        DROP TABLE #RemotePlans;
        DROP TABLE #Divergence;
        DROP TABLE #IgnoredPlans;
        RETURN 0;