Como fazer uma migração de dados

Imagine que você tem uma estante com muitos livros. Em um determinado momento, você precisa fazer a mudança desses livros para uma prateleira. A estante e a prateleira têm estruturas diferentes e você precisa organizar os livros para que eles caibam no novo espaço.

Então, diante desse cenário, você terá que selecionar o que irá de fato para a nova prateleira, respeitando os limites dela e a organização final dos livros de acordo com a nova estrutura.

Esse exemplo é uma forma de ilustrar os passos necessários para migrar dados entre bancos de dados distintos.

Os tópicos aqui apresentados são resultado de um trabalho de migração de dados entre bancos de dados de tecnologias diferentes, com estruturas diferentes, com regras de negócio distintas e um prazo curto para desenvolvimento e teste. Não considero os passos apresentados como a melhor estratégia, e sim aquilo que podia fazer com o que tinha disponível.

Considerações

Itens a serem observados antes de começar a executar a migração:

Entender as limitações do SGBD (Origem e Destino)

Entender como funciona o SGBD e as ferramentas que ele possui ajudará a prever os problemas que podem acontecer durante a execução da migração. Alguns scripts, a depender de como o banco e o negócio estão estruturados, podem conter mais de 1 milhão de registros e nem toda ferramenta ou SGBD conseguirá executá-los. Alguns clients tem desempenho melhor que outros e alguns podem apresentar um desempenho melhor a depender do SGBD de destino. Outros clients podem apresentar problemas com a configuração do locale do script, podendo pular algumas instruções.

Além das ferramentas, o SGBD pode ser um fator limitador na migração. Um dos problemas pode ser a forma como ele processa os scripts, chegando ao ponto de travar o servidor.

Identificar as particularidades nas estruturas dos bancos

Cada tabela pode possuir uma dependência. Essas dependências influenciam na ordem como os dados serão importados. Tabelas contendo enums e code data deverão ser listadas e tratadas antes dos dados principais. Será necessário observar se há dados duplicados ou semelhantes nessas tabelas para evitar redundâncias.

Evitar sub-selects com argumentos inline

Em alguns momentos os subselects serão necessários. A questão é ter de usar alguma linha da coluna para realizar novas pesquisas. Isso aumenta o tempo de execução consideravelmente. A solução é transformar essa pesquisa em um conjunto de dados a ser usado como argumento em um IN. Por exemplo:

SELECT *
FROM TABELA T
WHERE id <> (SELECT id FROM TABELA2 T2 WHERE T.valor = T2.valor)

Converter para:

SELECT *
FROM TABELA T
WHERE id IN (SELECT id FROM TABELA2 T2 WHERE T2.valor = argumento)

Será melhor ainda se for:

SELECT *
FROM TABELA T
WHERE EXISTS (SELECT id FROM TABELA2 T2 WHERE T2.valor = id)

Processo

Criar um arquivo “Composer”

Normalmente um script SQL possui a seguinte estrutura:

SELECT coluna1, coluna2
FROM tabela
WHERE coluna3 = argumento

Resultando em:

coluna1 coluna2
Valor1 Valor2
Valor3 Valor4

O Composer é uma estrutura de script que permite a extração dos dados do banco de origem, formatando a saída em um padrão esperado para o banco. Por exemplo:

SELECT 'INSERT INTO tabela (coluna1,coluna2) VALUES (''' + coluna_origem1 +''', ''' + coluna_origem2 + ''');' as "-- TABELA"
FROM tabela
WHERE colunaX = argumentoY

O resultado será:

— TABELA
INSERT INTO tabela (coluna1,coluna2) VALUES (‘Valor1’, ‘Valor2’);
INSERT INTO tabela (coluna1,coluna2) VALUES (‘Valor3’, ‘Valor4’);

Usar funções/procedures para dados críticos

Alguns dados críticos podem ter repetições, ter dados incompletos ou ter a necessidade de atualização por conta de alguma regra de negócio. A partir de funções/procedures é possível controlar esses dados desde que uma estrutura específica para o controle desses dados seja criada. Por exemplo:

Para registrar dados salvos com sucesso, será usada a tabela TABELA_SUCESSO.

Para identificar os registros inconsistentes ou duplicados será criada a tabela TABELA_INSUCESSO

A partir disso, a linha do composer ficará:

SELECT 'SELECT funcao(''' + coluna_origem1 +''', ''' + coluna_origem2 + ''');' as "-- FUNCAO_TABELA"
FROM tabela
WHERE colunaX = argumentoY

O resultado será:

— FUNCAO_TABELA
SELECT funcao(‘Valor1’, ‘Valor2’);
SELECT funcao(‘Valor3’, ‘Valor4’);

Separar os resultados em arquivos distintos

Para evitar um arquivo único contendo todos os comandos de INSERT, sendo difícil de manipular ou atualizar, recomenda-se separar os registros por tabela ou pela regra de negócio. Alguns arquivos, ainda separados, chegaram a ter 300MB de tamanho.

Verificar se todos os registros foram lidos

Houve episódios onde alguns registros não foram inseridos por conta de conflitos de codificação (encoding) da ferramenta e do script executado, fazendo com que alguns registros não fossem atualizados (O problema é que a ferramenta não parou ao identificar o erro na codificação). Verificar a quantidade de registros importados após a execução do script evitará surpresas indesejadas.