Como migrar bases SQL gigantes entre Azure e GCP

Marcos Tavares
Único
Published in
6 min readAug 19, 2020
Migração de aves é igual migração de dados?
Fonte: https://unsplash.com/photos/p72K-AvJrbQ

Migrar um sistema de nuvem nunca é uma tarefa simples, mas normalmente a parte mais complexa vai envolver o banco de dados, já que a aplicação pode ter que ficar fora do ar durante algum tempo.

O grande problema é que muitas vezes não podemos parar o sistema ou devemos fazer isso com o menor downtime possível.

Quando uma base é pequena, por volta de 10GB a 50GB, isso pode acontecer em períodos menores. Mas, quando uma base tem mais de 100GB, acelerar este processo é algo bem relevante.

A ideia deste texto é explicar como proceder ao migrar uma base do serviço Azure SQL para o serviço do GCP Cloud SQL.

Antes de detalhar um processo para bases maiores, vamos ver o que é necessário para migrar uma base SQL do Azure para o GCP de uma forma simples.

1 — Parar o serviço de banco de dados

Isto é importante para evitar novas escritas e evitar a geração de uma base inconsistente.

2 — Gerar um bacpac

Hoje o Azure não disponibiliza arquivo .bak diretamente fornecendo apenas arquivos no formato bacpac.

3 — Importar este bacpac

Isso pode ser feito em uma versão do SQL superior a 17 em qualquer servidor, até mesmo em um PC local.

Também é importante rodar um comando na base principal (Master):

sp_configure 'contained database authentication', 1;  
GO
RECONFIGURE;
GO

4 — Gerar um backup

Antes de fazer o backup remova os usuários criados e execute o comando na base restaurada:

ALTER DATABASE base SET CONTAINMENT = NONE WITH NO_WAIT

Após isso, volte a flag na base principal:

sp_configure 'contained database authentication', 0;  
GO
RECONFIGURE;
GO

5 — Fazer o upload para o GCP

Através do painel do GCP, você deverá fazer o upload deste .bak gerado para algum bucket.

6 — Importar o backup no GCP

No painel do GCP, é importate criar um banco e importar a base para o projeto a partir do bucket que foi salvo o backup.

Isto deve funcionar para bases pequenas. Este processo pode levar de uma a três horas, dependendo do tamanho do banco e provavelmente será o downtime que o sistema terá.

Mas se eu precisar migrar uma base com mais de 100 GB?

Bom, aí o ideal é utilizar algumas ferramentas mais avançadas como bcp, gsutil e sqlpackage. Vou detalhar aqui em que pontos elas podem ser utilizadas.

Diferente de uma base menor, não precisamos parar a base no início do processo, pois pode levar um tempo muito longo para gerar o bacpac.

1 — Gerar uma cópia da base

Dentro do próprio Azure, você deverá criar uma cópia da base. A partir dela que será a geração do bacpac, assim evitamos dados inconsistentes.

2 — Gerar um bacpac

Como é uma cópia, apenas mandamos gerar e aguardamos várias horas. Se for possível, aumente os DTUs. Isso acelera o processo.

3 — Importar bacpac server local

Recomenda-se importar utilizando o sqlpackage e não a interface do SSMS pois não é possível, de forma simples, alterar alguns parâmetros.

sqlpackage.exe /Action:Import /tsn:server-sql-local /tdn:base /sf:”F:\database-yyyy-mm-dd-hh-mm.bacpac” /p:CommandTimeout=1800

A geração de um bacpac de uma base ativa, sem pará-la, pode levar a um erro de importação do bacpac por causa de dados inconsistentes.

*** Error importing database: Could not import package

Não se preocupe, pois este erro será consertado ao importar os dados faltantes através do BCP.

Não esqueça de executar os comandos descritos anteriormente na migração de bases menores.

4 — Gerar o backup

Você deverá extrair um backup full, não esqueça de executar os comandos explicados anteriormente também.

5 — Importar para GCP

Se tentarmos importar pelo painel web do GCP um arquivo de 100GB teremos um tempo de upload muito alto. O ideal é utilizar a ferramenta do próprio Google, chamada gsutil.

gsutil -o GSUtil:parallel_composite_upload_threshold=150M cp base.bak gs://project-temp-bucket

6 — Criar base no GCP

Após a importação do backup para algum bucket, utilize-o para geração da base.

Estes foram os passos possíveis antes de termos downtime. Recomenda-se preparar os scripts antes de desligar o sistema, pois você terá que sincronizar as bases. Tenha em mente que este processo pode ser mais complexo dependendo da modelagem dos dados.

7 — Desligar o sistema conectado ao Banco do Azure

Desta forma você evitará novas escritas no banco.

8 — Realizar o BCP Out do Azure

Para auxiliar neste processo, criei um script para export de todas as tabelas a partir do último Identity de cada tabela. Você deverá rodar na base restaurada no GCP.

/*start bcp select*/declare @servername varchar(64)  = 'serverzure'
declare @path varchar(64) = 'F:\bcp\data\'
declare @usr varchar(64) = 'usr'
declare @pwd varchar(64) = 'pwd'
declare @database varchar(64) = 'basename'
SELECT
('Select (''bcp "SELECT * FROM '+TABLE_NAME+' WHERE '+COLUMN_NAME+' > ''+ CAST((SELECT MAX('+COLUMN_NAME+') FROM '+TABLE_NAME+') AS varchar(100)) +''" QUERYOUT ' + @path + TABLE_NAME + '.bcp -w -d'+@database+' -S"'+@servername+'" -U '+@usr+' -P '+@pwd +''') as z union') AS x
from INFORMATION_SCHEMA.COLUMNS
where COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1
order by TABLE_NAME
/*end bcp select*/

Isso vai gerar um Select para rodarmos em uma nova query do SQL. Após a nova execução, é só copiar o resultado na ferramenta BCP e exportar todos os dados do Azure para o caminho configurado.

9 — Realizar BCP IN no GCP

Agora vamos enviar todos os dados extraídos para a base no Google. Em destaque, um script para auxiliar este processo que pode ser executado em qualquer uma das bases (GCP ou Azure), já que o importante é a estrutura dos dados.

select ('bcp '+TABLE_NAME+' IN F:\bcp\data\'+ TABLE_NAME +'.bcp -w -e F:\bcp\logs\errorfile -E -dbase -S11.111.111.111 -Usqlserver -Ppwd -m1000000 -b1000 -hTABLOCK') z from INFORMATION_SCHEMA.COLUMNS
where COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1
order by TABLE_NAME

Cole este script no bcp e execute.

Resolvemos todos os dados novos que geraram novas linhas desde que restauramos, mas como atualizamos o histórico até o ponto de paralisação do serviço?

É bem importante conversar com alguém que conheça o negócio, pois você terá que gerar estes updates na mão, um a um. Se as tabelas não tiverem uma coluna de data de atualização pode ficar mais complexo de fazer isso.

Uma coisa importante: utilize o BCP para isto também. Tentar atualizar via linked server ou outra ferramenta pode ser muito demorado.

10 — Realizar BCP Out do Azure para Update

Se o negócio permitir, podemos definir um intervalo de tempo dos dados que serão atualizados, senão seremos obrigados a atualizar toda a tabela.

bcp "select * from table where createddate between DateAdd(month, -1, Convert(date, GetDate())) and getdate()" QUERYOUT F:\bcp\data\tableUpdate.bcp -w -d"base" -S"azureserver" -U usr -P pwd

11 — Realizar BCP In no GCP para Update

A tabela deve ter um sufixo de Update (ou outro nome) para facilitar os joins.

bcp TB_TableUpdate IN F:\bcp\data\signatureUpdate.bcp -w -e F:\bcp\logs\errorfile -E -d"base" -S"gcpServer" -U sqlserver -P pwd -m1000000 -b1000 -hTABLOCK

12 — Updates no GCP

Você deverá executar os updates na base. A query pode ser mais complexa para acelerar o processo se tiver alguma coluna que indique que houve alteração.

UPDATE Table
SET
[coluna1] = az.[coluna1],
[success] = az.[success],
[date] = az.[date]
FROM
TableUpdate az INNER JOIN
Table gcp ON az.[id] = gcp.[id]
WHERE
az.[id] = gcp.[id]
go

Com os dados atualizados, vamos recriar os índices e constraints. Estes processos podem demorar caso o banco seja muito grande.

13 — Recriar Constraint

Em destaque, um um script genérico para as bases.

/*recriate constraints*/SELECT 'ALTER TABLE [' + o.name + '] WITH CHECK CHECK CONSTRAINT [' + i.name + ']' AS keyname from sys.foreign_keys i
INNER JOIN sys.objects o ON i.parent_object_id = o.object_id
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0;
GO

14 — Recriar Índices

Outro ponto que pode demorar, mas é um passo importante.

/*recriate index*/SELECT DISTINCT 'ALTER INDEX ALL ON [dbo].[' + t.name + '] REBUILD' AS keyname 
FROM sys.indexes ind
INNER JOIN
sys.tables t ON ind.object_id = t.object_id

15 — Apontar aplicação para o banco do GCP

São muitos passos e talvez nem todos sejam necessários, mas é uma boa base para quem for encarar esta missão de migrar bases gigantes entre nuvens.

Bebam água e pratiquem exercícios.

Até uma próxima!

Abraço.

Marcos Tavares é desenvolvedor na Acesso Digital.

--

--

Marcos Tavares
Único
Writer for

Software Developer at unico, technophile and amateur Tennis and TCG player.