Replique os backups de sua VM SQL Server do Azure para Amazon

Olá pessoal, tudo certo?

No post de hoje vou compartilhar com vocês um caso que atendi em um de nossos clientes.

O nosso cliente tem uma VM com o SQL Server no Azure e gostaria de ter um site de DR (disaster recovery) na Amazon. Até ai tudo bem, você poderia falar “Tiago isso é fácil, o SQL Server te dá tantas possibilidades” ou até mesmo “Poxa ele pode usar a replicação no próprio Azure”. Concordo com tudo isso, mas cliente é cliente e a solicitação era “Quero uma cópia do meu SQL Server na Amazon”. Então a replicação no próprio Azure está descartada.

Pensei em utilizar o AlwaysOn AG, porém, a licença que ele utiliza é a edição Web que não tem suporte. Então AG descartado.

Passamos para próxima possibilidade que é configurar uma Replicação. Só que o custo (esforço) para configurar seria grande, mas seria uma alternativa válida também.

Próximo cenário seria configurar um Database Mirror, porém como os servidores estão em clouds diferentes que não estão integradas, teríamos que configurar o mirror utilizando certificado, mesmo assim não seria possível seguir com esse cenário, pois a edição Web libera você para configurar o servidor apenas como testemunha (witness).

Então chegamos no cenário que melhor atendeu a situação que é configurar um Log Shipping. Contudo, teríamos um esforço grande pelo mesmo motivo do mirror… “clouds diferentes não integradas…”, mas aí que entra uma solução de contorno que é criar um Log Shipping manual, que nada mais é que pegar os backups de um servidor e restaurar no outro.

Mas Tiago como que você copia os backups de um servidor para o outro? Utilizou alguma ferramenta? FTP?

Resposta: Não.

Poderíamos utilizar ferramentas para copiar os backups ou transferir via FTP, só que o nosso serviço foi facilitado pelo cliente já que ele utiliza o serviço de backup direto em um Blob Storage, se você quiser conhecer esse serviço veja o post (Fazendo um backup database On-Premises no Azure). Portanto, no servidor da Amazon eu precisaria apenas fazer o restore sem precisa de copiar os arquivos.

Como que você recuperou os caminhos dos backups?

Novamente aqui o trabalho foi facilitado pelo cliente que fez um form .Net que executa a query abaixo no MSDB e retorna as informações dos backups da instância principal que está no Azure e faz a carga dessas informações na Amazon, ou seja, um backup é realizado no Azure, ele é gravado na MSDB, de 30 em 30 segundos a aplicação valida se tem um backup novo, se tiver ele registra a informação na Amazon. Também daria para fazer um script powershell para recuperar as informações no Blob Storage, porém foi mais simples utilizar a app que o cliente fez.

SELECT A.database_name,
       A.backup_start_date,
       A.backup_finish_date,
       b.physical_device_name AS Diretorio,
       A.type,
       ROW_NUMBER() OVER (PARTITION BY A.database_name ORDER BY A.backup_start_date DESC) AS Ranking
FROM msdb..backupset A
    JOIN msdb..backupmediafamily b
        ON b.media_set_id = A.media_set_id
GROUP BY A.database_name,
         A.backup_start_date,
         b.physical_device_name,
         A.backup_finish_date,
         A.type;

E para fazer o restore como que você fez o controle do que já havia sido restaurado?

Para controlar a sequência lógica dos backups que precisam ser restaurados e até mesmo para controlar a cadeia de log, eu criei uma tabela que chamo de “”, que extrai informações da tabela de histórico de backups que foram realizados no Azure.

CREATE TABLE [dbo].[Controle_BKP](
	[ID] [int] IDENTITY(1,1) NOT NULL,
	[Nm_Database] [varchar](50) NULL,
	[Dt_Backup] [datetime] NULL,
	[Ds_Diretorio] [varchar](800) NULL,
	[Tipo_BKP] [char](1) NULL,
	[Fl_Restore] [bit] NULL DEFAULT ((0))
) ON [PRIMARY]

Para extrair as informações da tabela de histórico para tabela de controle, fiz uma procedure que pega o último backup full, o último diferencial (se utilizar) depois do full e os últimos backups de log depois do backup full ou do backup diferencial.

/****** Object:  StoredProcedure [dbo].[stpCarga_Historico_Backup]    Script Date: 16/07/2018 20:39:09 ******/

Create PROCEDURE [dbo].[stpCarga_Historico_Backup] @dbname sysname
AS
BEGIN

---BKP FULL
	INSERT INTO Controle_BKP
    (
        Nm_Database,
        Dt_Backup,
        Ds_Diretorio,
        Tipo_BKP,
        Fl_Restore
    )
    SELECT A.database_name,
           A.backup_start_date,
           A.Diretorio,
           A.type,
           0
	FROM
    (
        SELECT A.database_name,
               A.backup_start_date,
               A.diretorio ,
               A.type,
               ROW_NUMBER() OVER (PARTITION BY A.database_name ORDER BY A.backup_start_date DESC) AS Ranking
        FROM historico_backup A
        WHERE A.type = ('D')
             AND A.database_name = @dbname
        GROUP BY A.database_name,
                 A.backup_start_date,
				 A.Diretorio,
                 A.type
    ) A
        LEFT JOIN dbo.Controle_BKP cbd
          ON cbd.Ds_Diretorio = A.Diretorio
    WHERE A.Ranking = 1
       AND cbd.Ds_Diretorio IS NULL;

-- BKP DIF

   INSERT INTO Controle_BKP
    (
        Nm_Database,
        Dt_Backup,
        Ds_Diretorio,
        Tipo_BKP,
        Fl_Restore
    )
    SELECT A.database_name,
           A.backup_start_date,
           Diretorio,
           A.type,
           0
    FROM
    (
        SELECT A.database_name,
               A.backup_start_date,
               A.Diretorio,
               A.type,
               ROW_NUMBER() OVER (PARTITION BY A.database_name ORDER BY A.backup_start_date DESC) AS Ranking
		FROM historico_backup A
        WHERE A.type = ('I')
              AND A.database_name = @dbname
			     AND a.backup_start_date >=
          (
              SELECT MAX(a.backup_finish_date)
              FROM historico_backup A
              WHERE a.type = ('D')
                    AND a.database_name = @dbname
              GROUP BY a.database_name,
                       a.type)
        GROUP BY A.database_name,
                 A.backup_start_date,
				 A.Diretorio,
                 A.type
    ) A
        LEFT JOIN dbo.Controle_BKP cb
            ON cb.Ds_Diretorio = A.Diretorio
    WHERE A.Ranking = 1
          AND cb.Ds_Diretorio IS NULL;

 -- BKP LOG

    INSERT INTO Controle_BKP
    (
        Nm_Database,
        Dt_Backup,
        Ds_Diretorio,
        Tipo_BKP,
        Fl_Restore
    )
    SELECT a.database_name,
           a.backup_start_date,
           A.Diretorio,
           a.type,
           0
     FROM historico_backup A
        LEFT JOIN dbo.Controle_BKP cb
            ON cb.Nm_Database = a.database_name
               AND cb.Dt_Backup = a.backup_start_date
    WHERE a.type = ('L')
          AND a.database_name = @dbname
          AND a.backup_start_date >=
          (
              SELECT MAX(a.backup_finish_date)
              FROM historico_backup A
              WHERE a.type = ('D')
                    AND a.database_name = @dbname
              GROUP BY a.database_name,
                       a.type
          )
          AND cb.Dt_Backup IS NULL
    ORDER BY a.backup_start_date;

end

E agora, as tabelas de controle estão criadas. Como fazer o restore?

O script abaixo é uma procedure que faz o restore. Para facilitar a criação dos bancos eu criei as mesmas estruturas de diretórios do servidor de origem.

Ao final de cada restore, a rotina faz um update na tabela de controle informando que aquele arquivo foi restaurado com sucesso.

Create PROCEDURE [dbo].[stpRestauraBancos]
    @dbname sysname

AS
BEGIN

    DECLARE @caminho VARCHAR(MAX),
            @mdf VARCHAR(MAX),
            @mdf_name sysname,
            @ldf VARCHAR(MAX),
            @ldf_name sysname;

    SELECT @mdf = physical_name,
           @mdf_name = mf.name
    FROM master.sys.master_files mf
        INNER JOIN master.sys.databases db
            ON mf.database_id = db.database_id
    WHERE mf.type = 0 
          AND db.name = @dbname;
 
   SELECT @ldf = physical_name,
           @ldf_name = mf.name
    FROM master.sys.master_files mf
        INNER JOIN master.sys.databases db
            ON mf.database_id = db.database_id
    WHERE mf.type = 1 -- 1 = Log 
          AND db.name = @dbname;

        --Restaurar backup full.
        SET @caminho =
        (
            SELECT Ds_Diretorio
            FROM restore_log.dbo.Controle_BKP
            WHERE Tipo_BKP = 'D'
                  AND Fl_Restore = 0
                  AND Nm_Database = @dbname
        );

        IF @caminho IS NOT NULL     
        BEGIN
            RESTORE DATABASE @dbname
            FROM URL = @caminho
            WITH FILE = 1,
                 MOVE @mdf_name
                 TO @mdf,
                 MOVE @ldf_name
                 TO @ldf,
                 NORECOVERY,
                 NOUNLOAD,
				 STATS = 10,
                 REPLACE;

            IF @@ERROR = 0
            BEGIN
                UPDATE restore_log.dbo.Controle_BKP
                SET Fl_Restore = 1
                WHERE Ds_Diretorio = @caminho;
            END;

            PRINT '>>>Backup Full restaurado: ' + @caminho;

        END;

        --Restarurar backup diferencial.

		DECLARE cursor_BackupDiferencial CURSOR FOR
        SELECT Ds_Diretorio
        FROM restore_log.dbo.Controle_BKP
        WHERE Tipo_BKP = 'I'
              AND Fl_Restore = 0
			  AND Dt_Backup <= (SELECT MIN(Dt_Backup) FROM restore_log.dbo.Controle_BKP WHERE Tipo_BKP = 'I'
																					  AND Nm_Database = @dbname)
              AND Nm_Database = @dbname;

        OPEN cursor_BackupDiferencial;

        FETCH NEXT FROM cursor_BackupDiferencial
        INTO @caminho;

        WHILE @@FETCH_STATUS = 0
        BEGIN

            RESTORE DATABASE @dbname
            FROM URL = @caminho
            WITH FILE = 1,
                 NORECOVERY,
				 NOUNLOAD,
                 STATS = 10,
                 REPLACE;

            IF @@ERROR = 0
            BEGIN
                UPDATE restore_log.dbo.Controle_BKP
                SET Fl_Restore = 1
                WHERE Ds_Diretorio = @caminho;
            END;

            PRINT '>>>Backup Diferencial restaurado: ' + @caminho;

            FETCH NEXT FROM cursor_BackupDiferencial
            INTO @caminho;
        END;

        CLOSE cursor_BackupDiferencial;
        DEALLOCATE cursor_BackupDiferencial;
		end
		
        --Restaurar backup de logs.
        DECLARE cursor_backup_files CURSOR FOR
        SELECT Ds_Diretorio
        FROM restore_log.dbo.Controle_BKP
        WHERE Tipo_BKP = 'L'
              AND Fl_Restore = 0
              AND Nm_Database = @dbname
			  ORDER BY Dt_Backup;

        OPEN cursor_backup_files;

        FETCH NEXT FROM cursor_backup_files
        INTO @caminho;

        WHILE @@FETCH_STATUS = 0
        BEGIN

            RESTORE LOG @dbname
            FROM URL = @caminho
            WITH FILE = 1,
                 NORECOVERY,
				 STATS = 10;

            IF @@ERROR = 0
            BEGIN
                UPDATE restore_log.dbo.Controle_BKP
                SET Fl_Restore = 1
                WHERE Ds_Diretorio = @caminho;
            END;

            PRINT '>>>Backup de Log restaurado: ' + @caminho;

            FETCH NEXT FROM cursor_backup_files
            INTO @caminho;

        END

        CLOSE cursor_backup_files;
        DEALLOCATE cursor_backup_files;

END

Após a criação da procedure, eu criei um job que executa a cada 30 minutos. Isso ficou alinhado com o cliente que assumiu que poderia perder até 30 minutos de dados.

E assim o cliente ficou feliz por que agora tem uma réplica do seu banco de dados SQL Server do Azure na Amazon.

Bom pessoal por hoje é isso.

Um grande abraço,

Tiago Neves

Deixe uma resposta