Pular para conteúdo

Fase 2: Extração de Dados do Sistema Legado (MySQL 5.1)

A fase de extração foi um dos estágios mais críticos do projeto. O desafio consistia em ler cerca de 100GB de dados históricos acumulados ao longo de quase 30 anos em um banco de dados MySQL 5.1 obsoleto,,, garantindo a integridade da informação e evitando a sobrecarga tanto do servidor de origem quanto da máquina cliente.

🛠️ Técnicas e Tecnologias Utilizadas

Para garantir alta performance e baixo consumo de memória, a arquitetura de extração foi modernizada, abandonando abordagens tradicionais (como Pandas e SQLAlchemy puro) em favor de um pipeline de dados de alto desempenho.

  1. ConnectorX, Apache Arrow e Polars: O ConnectorX (escrito em Rust) foi utilizado como o motor principal de extração, lendo os dados do MySQL e alocando-os diretamente em memória no formato colunar Apache Arrow,,. Esses lotes (RecordBatches) eram então consumidos via Polars, garantindo altíssima velocidade e processamento com uso mínimo de memória RAM,,.
  2. Processamento em Chunks (Streaming/Paginação): Em vez de carregar tabelas gigantes inteiras na memória, a extração foi configurada para operar em modo de streaming, dividindo a leitura em lotes (chunks) de 100.000 linhas utilizando cursores ou paginação por chave primária,,.
  3. Pushdown Transformations (Transformação na Fonte): A lógica de limpeza não foi feita no Python. O script de extração foi programado para inspecionar dinamicamente o DDL da origem (via INFORMATION_SCHEMA) e gerar consultas SELECT personalizadas, "empurrando" as conversões e tratamentos (CAST, NULLIF, HEX) diretamente para o motor do banco de dados antes da transferência pela rede,,.
  4. Paralelismo Assíncrono: Utilizando ThreadPoolExecutor, a extração de diferentes tabelas (ou fatias da mesma tabela) foi orquestrada para rodar em múltiplas threads simultâneas, maximizando o uso da rede e reduzindo o tempo de extração de horas para minutos,,,.
  5. Persistência em Parquet: Os dados extraídos em memória (via Arrow) foram salvos diretamente em arquivos Parquet na camada Bronze do Data Lake (Google Cloud Storage), preservando a tipagem rica dos dados e oferecendo alta taxa de compressão,,.

🚧 Problemas Enfrentados e 💡 Soluções Aplicadas

A idade do sistema legado e a permissividade do MySQL 5.1 trouxeram à tona uma série de inconsistências silenciosas nos dados.

Problema 1: Datas Inválidas ("0000-00-00") e Pânicos no Rust

  • O Problema: O MySQL 5.1 permitia o armazenamento de datas vazias no formato '0000-00-00',. Como as bibliotecas modernas (Arrow e Polars) seguem padrões rigorosos e não reconhecem essa "data zero" no calendário Gregoriano, as threads do ConnectorX encontravam esses valores e geravam uma falha fatal silenciosa (Rust Panic: Could not retrieve chrono::naive::datetime::NaiveDateTime), o que causava a interrupção da ingestão e perda de dados em chunks inteiros,,.
  • A Solução: Foi implementada uma regra de "Pushdown Transformation" no script gerador da query. Ao invés de um simples SELECT *, o script substituiu a seleção dos campos de data por uma cláusula NULLIF(campo_data, '0000-00-00'),. Dessa forma, o próprio MySQL converteu os dados inválidos em NULL verdadeiro antes de enviá-los ao ConnectorX, eliminando o erro de leitura sem corromper a linhagem dos dados na camada Bronze,.

Problema 2: Gargalos de I/O, Exaustão de Rede e Timeouts

  • O Problema: Durante a extração de tabelas massivas, o servidor de origem frequentemente abortava a conexão, gerando erros como Lost connection to MySQL server during query. A investigação revelou que o MySQL 5.1 matava a conexão devido ao wait_timeout (300s) ou net_read_timeout (30s),,. Além disso, tentar ler chaves primárias inteiras de uma só vez colocava o servidor em um estado de congelamento writing to net, exaurindo a rede e a memória do cliente,.
  • A Solução: Adotou-se o processamento estrito em lotes (chunks). Para resolver os timeouts de rede, foram injetadas opções de configuração no driver de conexão (read_timeout=3600 e tcp_keepalive=True), forçando o envio de pacotes de baixo nível ("TCP Keepalive") que sinalizavam ao servidor legado que o cliente ainda estava ativo e processando dados,,.

Problema 3: Expansão de Encoding e Corrupção de Dados Binários

  • O Problema: Alguns campos como senhas criptografadas (PesSenhaCripto) estavam armazenados como VARCHAR(16) no banco legado que utilizava a codificação Latin1. Ao tentar extrair e converter esses dados para um banco de destino moderno com suporte a UTF-8 (utf8mb4), os bytes sofriam expansão (caracteres com acentos passavam de 1 byte para 2 bytes). A string resultante ficava maior que o limite da coluna, causando truncamento (Data truncated for column) ou corrompendo a string original,,,,.
  • A Solução: Desenvolveu-se uma lógica onde qualquer tipo de dado identificável como texto contendo lixo binário, ou explicitamente binário, fosse extraído utilizando a função HEX() nativa do MySQL. O dado viajava pelo pipeline (Arrow/Parquet) como uma string hexadecimal segura e previsível. No momento da carga no novo ambiente, a função UNHEX() era aplicada para recriar o byte exato original, garantindo a integridade de 100% dos registros criptografados,,.

Problema 4: Inferência Incorreta de Booleanos (TINYINT(1) / BIT)

  • O Problema: Tipos como BIT ou TINYINT(1), comumente usados como booleanos, eram frequentemente lidos pelo conector Python em sua forma de bytes puros (ex: b'\x00'),. Quando esses dados eram passados para o Polars em fatias onde o primeiro chunk continha apenas valores nulos, o Polars inferia o schema incorretamente como pl.Null ou falhava ao interpretar a conversão para arquivo de texto/TSV no momento da carga (datatype binary cannot be written to CSV),,.
  • A Solução: A função automática de inspeção DDL passou a adicionar conversores explícitos nas cláusulas de extração, forçando CAST(coluna AS UNSIGNED) no lado do MySQL,. Isso fez com que o dado chegasse ao ConnectorX e Polars como um inteiro numérico limpo (0 ou 1) ou mapeando diretamente o schema explicito no Polars via schema_overrides do metadata do INFORMATION_SCHEMA,.

🎯 Resumo da Fase

A transição de tentativas ingênuas de extração (tudo em memória com Pandas) para um motor robusto orquestrado em Python, impulsionado por ConnectorX (Rust) e Apache Arrow, foi a virada de chave do projeto,,,.

A lição primordial desta fase foi que não se pode confiar na consistência dos dados de um sistema legado, nem depender da inferência automática de bibliotecas modernas. O sucesso da extração exigiu uma postura de "programação defensiva", utilizando a inspeção de metadados para construir instruções SELECT customizadas por tabela, higienizando as informações diretamente na origem antes mesmo que os bytes trafegassem pela rede.