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.
- 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,,. - 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,,.
- 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 consultasSELECTpersonalizadas, "empurrando" as conversões e tratamentos (CAST,NULLIF,HEX) diretamente para o motor do banco de dados antes da transferência pela rede,,. - 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,,,. - 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áusulaNULLIF(campo_data, '0000-00-00'),. Dessa forma, o próprio MySQL converteu os dados inválidos emNULLverdadeiro 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 aowait_timeout(300s) ounet_read_timeout(30s),,. Além disso, tentar ler chaves primárias inteiras de uma só vez colocava o servidor em um estado de congelamentowriting 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=3600etcp_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 comoVARCHAR(16)no banco legado que utilizava a codificaçãoLatin1. Ao tentar extrair e converter esses dados para um banco de destino moderno com suporte aUTF-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çãoUNHEX()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
BITouTINYINT(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 comopl.Nullou 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 viaschema_overridesdo metadata doINFORMATION_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.