Skip to main content
  1. Posts/

Como fazer uma migração "simples" de dados

·5 minutos

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. A intenção é mostrar um fluxo “simples”, sem depender de alguma tecnologia específica além do próprio SQL. Não considero os passos apresentados como a melhor estratégia, e sim aquilo que eu pude 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 têm 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 ou mais dependências (chaves-estrangeiras ou FK). Essas dependências influenciam na ordem como os dados serão importados. Além disso, 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 entre as diversas origens para evitar redundâncias.

Índices nos bancos de origem podem acelerar a leitura e criação dos arquivos intermediários. Já nos bancos de destino, os índices poderão limitar a quantidade de registros inseridos/alterados por segundo. Em ambos os casos, será necessário avaliar a necessidade e o impacto de criação ou remoção deles.

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:

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

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

-- Melhor ainda:
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

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 de destino. Por exemplo:

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

O resultado será um arquivo pronto para execução no banco de destino:

-- 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, criando tabelas auxiliares:

  • TABELA_SUCESSO: registra dados salvos com sucesso.
  • TABELA_INSUCESSO: identifica registros inconsistentes ou duplicados.
SELECT 'SELECT funcao(''' + coluna_origem1 + ''', ''' + coluna_origem2 + ''');' AS "-- FUNCAO_TABELA"
FROM tabela
WHERE colunaX = argumentoY

Separar os resultados em arquivos distintos #

Para evitar um arquivo único contendo todos os comandos de INSERT, 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. Verificar a quantidade de registros importados após a execução do script evitará surpresas indesejadas.

Considerações finais #

Extrações pontuais, com poucos registros, de origens de dados não-convencionais (CSV, Excel, etc.), com modelos de negócio simples, podem se beneficiar com o fluxo apresentado.

Algumas situações podem permitir o uso de ferramentas como Pentaho PDI ou Airflow. Em outras, algumas regras corporativas limitarão o acesso aos destinos, exigindo que as alterações sejam feitas por scripts e que os mesmos sejam executados por pessoas credenciadas.