<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Luan Andryl</title>
    <description>The latest articles on Forem by Luan Andryl (@andryl_).</description>
    <link>https://forem.com/andryl_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F223488%2F39542882-d297-4f4d-807c-71f8e480ac0e.jpg</url>
      <title>Forem: Luan Andryl</title>
      <link>https://forem.com/andryl_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/andryl_"/>
    <language>en</language>
    <item>
      <title>Quando a arquitetura fala mais alto que o código</title>
      <dc:creator>Luan Andryl</dc:creator>
      <pubDate>Mon, 03 Nov 2025 13:05:39 +0000</pubDate>
      <link>https://forem.com/andryl_/quando-a-arquitetura-fala-mais-alto-que-o-codigo-4nmp</link>
      <guid>https://forem.com/andryl_/quando-a-arquitetura-fala-mais-alto-que-o-codigo-4nmp</guid>
      <description>&lt;h3&gt;
  
  
  Crônica de uma madrugada em que a beleza do design não salvou o sistema
&lt;/h3&gt;

&lt;p&gt;Estava eu vivendo minha vida tranquilamente até que um alerta me tirou da inércia: &lt;strong&gt;“Failover automático disparado: primário caiu, standby promovido.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O sistema era herdado, cheio de camadas históricas, e minha primeira reação foi quase otimista. “Perfeito, o Multi-AZ está funcionando. Tudo volta sozinho.”&lt;/p&gt;

&lt;p&gt;Mas, alguns minutos depois, o otimismo evaporou. As exceções começaram a pipocar no monitoramento, os consumidores de Kafka acumularam lag e a fila de processamento travou por completo.&lt;/p&gt;

&lt;p&gt;Abri os logs e lá estava a enxurrada: &lt;code&gt;read_only_sql_transaction&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Quem já viu esse erro sabe o que ele significa — a aplicação está tentando escrever em uma réplica de leitura. O failover aconteceu, o banco se recuperou, mas o sistema continuou falando com o nó antigo, acreditando que ele ainda era o primário.&lt;/p&gt;

&lt;p&gt;Reiniciei os pods e, como num passe de mágica, tudo voltou ao normal.&lt;br&gt;&lt;br&gt;
Kafka fluindo, jobs executando, alertas desaparecendo.  &lt;/p&gt;

&lt;p&gt;Mas o alívio durou pouco. Assim que o sistema estabilizou, veio a pergunta inevitável: e as mensagens perdidas durante o downtime?  &lt;/p&gt;

&lt;p&gt;A princípio, confiávamos que havia um mecanismo de resiliência cuidando disso. E havia — mas, para minha surpresa, ele estava apoiado no próprio banco de dados.  &lt;/p&gt;

&lt;p&gt;Foi nesse instante que percebi o real problema. O incidente não expôs apenas o failover, mas a fragilidade arquitetural de um processo essencial diante de falhas de infraestrutura.&lt;br&gt;&lt;br&gt;
Em um cenário ideal, talvez usar o banco como fallback para questões de negócio até funcione.&lt;br&gt;&lt;br&gt;
Mas acreditar que “nunca vai haver problema de rede ou infraestrutura” é o tipo de premissa que destrói sistemas em produção.  &lt;/p&gt;

&lt;p&gt;A verdade é que &lt;strong&gt;rede não é confiável&lt;/strong&gt; e &lt;strong&gt;mensageria deve ser tratada com mensageria&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
O que nos faltava era um &lt;strong&gt;Dead Letter Topic (DLT)&lt;/strong&gt; um canal seguro para mensagens que não puderam ser processadas.&lt;br&gt;&lt;br&gt;
Sem isso, cada falha virava esquecimento, e a arquitetura, que deveria proteger o sistema, acabou se tornando parte do problema.  &lt;/p&gt;

&lt;p&gt;Aquela madrugada deixou claro que os incidentes mais caros não nascem de uma grande falha, mas de uma sequência de pequenas negligências: um DNS cacheado, um pool de conexões teimoso, um DLT inexistente e a falta de idempotência.  &lt;/p&gt;




&lt;h2&gt;
  
  
  O desastre invisível: quando o DNS dita o destino
&lt;/h2&gt;

&lt;p&gt;Quando o primário de um banco cai, o RDS faz o que promete:&lt;br&gt;&lt;br&gt;
promove a réplica standby e atualiza o endpoint DNS. Tecnicamente, o sistema “voltou”. Mas, na prática, a aplicação não sabe disso.  &lt;/p&gt;

&lt;p&gt;O DNS — aquele detalhe que raramente recebe atenção — é o elo mais frágil entre o banco e o cliente. E sim, vale lembrar: sua aplicação fala com o banco &lt;strong&gt;pela rede&lt;/strong&gt;. Não há mágica aqui.  &lt;/p&gt;

&lt;p&gt;Se o IP resolvido pelo sistema operacional está cacheado, ou se o pool mantém conexões antigas, sua aplicação vai continuar escrevendo no lugar errado. É como mudar de casa, mas continuar indo ao endereço antigo. O nome (endpoint) está certo, mas o caminho ficou preso na memória.&lt;/p&gt;

&lt;p&gt;O RDS troca o IP em segundos, mas o sistema pode levar minutos, às vezes horas, para entender que o mundo mudou. Enquanto isso, o Kafka empilha mensagens, o banco rejeita gravações e o time corre atrás de fantasmas.&lt;/p&gt;




&lt;h2&gt;
  
  
  O cenário Elixir/Ecto: o falso conforto do “restart resolve”
&lt;/h2&gt;

&lt;p&gt;Quem trabalha com Elixir conhece bem o padrão: Ecto e Postgrex mantêm conexões persistentes no pool. Quando o failover ocorre e o primário vira réplica, essas conexões continuam vivas, apontando para o nó errado. Resultado: &lt;code&gt;read_only_sql_transaction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A correção existe e é elegante ... basta configurar &lt;code&gt;:disconnect_on_error_codes&lt;/code&gt; no Repo, incluindo &lt;code&gt;:read_only_sql_transaction&lt;/code&gt;. Assim, quando o erro ocorre, o pool descarta a conexão e força uma reconexão limpa.&lt;/p&gt;

&lt;p&gt;Mas isso só resolve metade do problema. Se o cache DNS do sistema ainda aponta para o IP antigo, o Ecto vai reconectar… no mesmo lugar errado. Em ambientes Kubernetes, esse cache pode durar o TTL configurado ... ou eternamente, dependendo da base da imagem.&lt;/p&gt;

&lt;p&gt;Reiniciar o pod resolve, sim, mas é uma solução de sorte. É como consertar um vazamento fechando o registro geral ... funciona, mas não é engenharia, é improviso.&lt;/p&gt;




&lt;h2&gt;
  
  
  O mesmo problema, outro runtime: a JVM e o DNS eterno
&lt;/h2&gt;

&lt;p&gt;Nos últimos meses venho explorando mais a JVM, e ao encontrar o mesmo comportamento em produção, fui investigar como ela lida com esse tipo de falha.&lt;/p&gt;

&lt;p&gt;E a descoberta é, no mínimo, curiosa: por padrão, a JVM cacheia resoluções DNS &lt;strong&gt;para sempre&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
O parâmetro &lt;code&gt;networkaddress.cache.ttl&lt;/code&gt; vem indefinido, e o comportamento padrão é infinito. Uma vez resolvido o endpoint, ele nunca mais é atualizado ... mesmo que o RDS já tenha mudado de IP várias vezes.&lt;/p&gt;

&lt;p&gt;Por isso, aplicações Spring Boot que dependem de failover precisam definir explicitamente &lt;code&gt;-Dsun.net.inetaddr.ttl=60&lt;/code&gt; (ou menos)&lt;br&gt;&lt;br&gt;
e garantir que o pool (geralmente HikariCP) valide conexões antes de reutilizá-las. Além disso, é fundamental tratar o erro SQLState &lt;code&gt;25006&lt;/code&gt; como gatilho para encerrar e recriar conexões.&lt;br&gt;&lt;br&gt;
É o equivalente JVM do &lt;code&gt;disconnect_on_error_codes&lt;/code&gt; do Ecto.&lt;/p&gt;

&lt;p&gt;Foi aí que ficou claro: o problema não era o banco.&lt;br&gt;&lt;br&gt;
Era a confiança cega na infraestrutura sem entender o comportamento do runtime que a consome.&lt;/p&gt;




&lt;h2&gt;
  
  
  O elo perdido: quando o DLT não existe
&lt;/h2&gt;

&lt;p&gt;O que me tirou o sono naquela madrugada não foi o failover em si, mas o que veio depois: as mensagens que não puderam ser gravadas simplesmente sumiram.&lt;/p&gt;

&lt;p&gt;Sem um &lt;strong&gt;Dead Letter Topic (DLT)&lt;/strong&gt;, não havia para onde elas irem.&lt;br&gt;&lt;br&gt;
O processo de reprocessamento dependia do Oban que, ironicamente, também persistia no mesmo banco afetado. Quando o banco cai, a fila de recuperação cai junto.  &lt;/p&gt;

&lt;p&gt;E o Kafka? Avançou o offset. Pronto. Dados no limbo.&lt;/p&gt;

&lt;p&gt;O DLT é a fronteira entre um incidente e uma tragédia.&lt;br&gt;&lt;br&gt;
Ele é o arquivo de quarentena das mensagens que falharam, mas ainda têm valor. Sem ele, o sistema não apenas perde dados .. perde memória.  &lt;/p&gt;

&lt;p&gt;O DLT não é um detalhe técnico; é uma escolha ética. É o “não esqueci o que deu errado”.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idempotência: o antídoto contra o caos
&lt;/h2&gt;

&lt;p&gt;DLT sozinho não basta. Ele te avisa o que falhou, mas não repara o dano. É aí que entra a idempotência.  &lt;/p&gt;

&lt;p&gt;Durante a recuperação, reprocessar as mensagens foi um exercício de paciência: algumas já haviam sido aplicadas parcialmente, outras não.&lt;br&gt;&lt;br&gt;
Sem uma chave única, não havia forma segura de distinguir uma duplicata de uma nova execução.&lt;/p&gt;

&lt;p&gt;Esse é o tipo mais caro de incidente evitável ... horas de trabalho manual, avaliando cada registro e decidindo o que reaplicar.  &lt;/p&gt;

&lt;p&gt;Essa experiência reforçou algo que considero inegociável: &lt;br&gt;
&lt;strong&gt;idempotência é um princípio de arquitetura, não uma conveniência de código.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Em Elixir, isso significa projetar cada operação para ser reconhecida de forma única ... por uma chave natural ou por estados determinísticos no banco.  &lt;/p&gt;

&lt;p&gt;Em Kotlin, passa por deduplicação lógica, seja via banco, Redis ou hashing, garantindo que um mesmo evento não produza efeitos colaterais duas vezes.  &lt;/p&gt;

&lt;p&gt;Sem idempotência, o DLT deixa de ser ferramenta de resiliência e vira apenas um cemitério de dúvidas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Para fechar: arquitetura é o que te salva às três da manhã
&lt;/h2&gt;

&lt;p&gt;Depois daquela madrugada, algo ficou cristalino: de nada adianta o design do seu código ser perfeito se a arquitetura não funciona.&lt;br&gt;&lt;br&gt;
É preferível um módulo feio que cumpre seu papel do que uma obra-prima de desacoplamento que não entrega o básico: &lt;strong&gt;disponibilidade e integridade.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O primeiro caso se resolve com uma PR de refactor. O segundo consome uma sprint inteira e, às vezes, a confiança da equipe.&lt;/p&gt;

&lt;p&gt;Problemas de arquitetura não são detalhes são o que mais consome tempo, esforço e dinheiro. E, paradoxalmente, são também o que mais pode salvar um sistema quando tudo dá errado.&lt;/p&gt;

&lt;p&gt;Naquela noite, percebi que o verdadeiro inimigo não era o RDS, nem o Kafka. Era a ilusão de que alta disponibilidade é algo que “se configura”.&lt;/p&gt;

&lt;p&gt;O failover promove, o DNS muda, mas se sua aplicação não entende o que aconteceu: se o pool não reconecta, se o DNS não se renova, se o DLT não existe ... Então você não tem resiliência, tem sorte.&lt;/p&gt;

&lt;p&gt;Hoje vejo o failover não como um evento de infraestrutura, mas como um &lt;strong&gt;teste de maturidade arquitetural&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
O hardware faz seu papel mas a aplicação é quem precisa entender o que mudou.&lt;/p&gt;

&lt;p&gt;E isso exige fundamentos: *&lt;em&gt;TCP, DNS, cache, mensageria, idempotência.  *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Resiliência não é reiniciar o pod e torcer pra voltar. É projetar pra que, quando o chão se mover, o sistema saiba dançar sozinho.&lt;/p&gt;

</description>
      <category>arquitetura</category>
      <category>elixir</category>
      <category>kotlin</category>
      <category>resiliencia</category>
    </item>
    <item>
      <title>Once Upon a Migration in Engineering</title>
      <dc:creator>Luan Andryl</dc:creator>
      <pubDate>Tue, 17 Jun 2025 18:51:07 +0000</pubDate>
      <link>https://forem.com/andryl_/once-upon-a-migration-in-engineering-3iga</link>
      <guid>https://forem.com/andryl_/once-upon-a-migration-in-engineering-3iga</guid>
      <description>&lt;p&gt;No início de muitas startups, a pressão para entregar rápido leva as equipes de engenharia a construírem MVPs (Produtos Mínimos Viáveis) e lançarem funcionalidades às pressas, visando validar o produto e alcançar product-market fit. Nessa corrida, sistemas e modelos de dados são criados para atender às necessidades imediatas. Com o passar dos anos, porém, é comum que certas decisões iniciais precisem ser reavaliadas à luz do crescimento do produto e de novas demandas (técnicas ou de negócio). É aí que entra em cena uma tarefa frequente em times de engenharia maduros: migrações de sistema e de dados. &lt;/p&gt;

&lt;p&gt;Existem diversos tipos de migrações que podemos enfrentar. Podemos precisar migrar a arquitetura (por exemplo, extrair microservices de um monolito legado), reescrever componentes inteiros, ou, foco deste artigo, realizar grandes migrações de dados. Aqui estamos falando de alterar ou mover grandes volumes de dados de um formato, local ou esquema para outro – às vezes envolvendo múltiplos serviços e bancos de dados diferentes – de maneira segura e eficiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cenário: Migração de Dados em Múltiplos Sistemas
&lt;/h2&gt;

&lt;p&gt;Imagine o seguinte cenário real (simplificado para fins didáticos): sua empresa possui um ecossistema com vários serviços e bases de dados, cada um guardando informações sobre o titular de uma conta de formas redundantes. Por exemplo, um serviço de processamento de PIX e boletos armazena dados do pagador/beneficiário; outro sistema de autorização de cartão mantém referência ao titular do cartão; o serviço de extrato registra quem enviou ou recebeu cada transação, etc. Em resumo, diversos sistemas possuem cópias de referência do mesmo dado de identidade. &lt;/p&gt;

&lt;p&gt;Agora, devido a uma nova exigência regulatória, vocês precisam modificar essa referência de titular em todos esses sistemas ao mesmo tempo (por exemplo, trocar o identificador usado para o cliente por um novo ID unificado, ou atualizar o formato de um documento). Como proceder? Uma mudança desse tipo exige coordenação cuidadosa: ou todos os serviços adotam a nova referência simultaneamente, ou corremos risco de inconsistências e falhas em cascata. &lt;/p&gt;

&lt;p&gt;Neste artigo, vamos explorar as estratégias adotadas em uma migração de dados em larga escala semelhante a essa. Vamos discutir os desafios técnicos envolvidos, as decisões tomadas para minimizar riscos e dores de cabeça, e como utilizamos ferramentas – em especial o Elixir – para processar grandes volumes de dados de forma eficiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desafios em Grandes Migrações de Dados
&lt;/h2&gt;

&lt;p&gt;Realizar migrações de dados volumosas e distribuídas traz uma série of desafios técnicos e operacionais. Antes de entrarmos nas soluções, vale destacar alguns dos principais problemas que precisamos endereçar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consumo de memória e recursos:&lt;/strong&gt; Processar milhões de registros ou comandos de atualização pode facilmente esgotar memória e CPU se feito de forma ingênua. Ler um arquivo enorme inteiro para a RAM ou carregar um dataset massivo de uma só vez não escala. Precisamos de abordagens streaming (fluxo) que utilizem memória constante, mesmo com arquivos gigantes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manter consistência e ordem:&lt;/strong&gt; No cenário descrito, pode ser crucial que as atualizações ocorram em ordem e de forma coordenada. Se um sistema é atualizado antes de outro, podemos ter falhas temporárias. Além disso, se estamos executando comandos SQL, pode haver dependências entre eles que exigem ordem sequencial. Precisamos garantir execução segura e ordenada dos comandos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limitações de ferramentas/banco:&lt;/strong&gt; Algumas ferramentas de acesso a banco de dados têm restrições – por exemplo, o driver Postgrex (usado pelo Ecto no Elixir) não suporta múltiplos comandos SQL na mesma query quando usando o &lt;a href="https://groups.google.com/g/elixir-ecto/c/HjO2guV1yDw/m/Glr6THhwga8J?pli=1" rel="noopener noreferrer"&gt;protocolo padrão extendido&lt;/a&gt;. Isso significa que não dá pra simplesmente juntar &lt;code&gt;UPDATE X; UPDATE Y; UPDATE Z;&lt;/code&gt; em uma única chamada – temos que executar um por vez.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tempo de execução e desempenho:&lt;/strong&gt; Atualizar dados em larga escala pode levar horas se feito de forma ineficiente. Precisamos pensar em formas de otimizar o throughput – seja agrupando operações em lote, paralelizando onde seguro, ou outras otimizações – sem comprometer a segurança.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Robustez e recuperação de erros:&lt;/strong&gt; Em um mundo ideal, todas as atualizações ocorreriam sem nenhum erro. No mundo real, porém, é possível que alguns comandos falhem – seja por dados inesperados, conflitos (ex: violação de chave única), deadlocks, ou erros transientes de conexão. Uma migração robusta deve lidar graciosamente com erros: registrar o problema e seguir em frente, ao invés de abortar tudo. Afinal, se 5 registros falharem em 1 milhão, talvez seja aceitável tratar esses 5 manualmente depois, mas queremos que os outros 999.995 sejam migrados com sucesso.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log e monitoramento:&lt;/strong&gt; Executar 100 mil ou 1 milhão de operações gera muito log. Se não tivermos cuidado, podemos inundar nosso sistema de logs, dificultando achar informação útil. Queremos uma forma de consolidar logs – por exemplo, registrar todos os erros num único arquivo ou estrutura – e ter algum feedback de progresso, sem imprimir uma linha de log para cada operação bem-sucedida.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orquestração entre serviços:&lt;/strong&gt; No caso de múltiplos sistemas envolvidos, há ainda o desafio de orquestrar a migração de forma centralizada ou coordenada. Podemos precisar rodar scripts em cada serviço ou banco, sincronizados de alguma maneira. Alternativamente, um serviço orquestrador poderia disparar as migrações individuais e acompanhar seu progresso. De qualquer forma, requer planejamento para que todos cheguem ao estado final consistente.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tendo estes desafios em mente, vamos agora detalhar como abordá-los. Usaremos como espinha dorsal um caso prático de migração de dados realizado em Elixir, mas ressaltando conceitos gerais que podem ser aplicados em outras linguagens e ferramentas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 1: Streaming de Dados para Uso Mínimo de Memória
&lt;/h2&gt;

&lt;p&gt;O primeiro mandamento de trabalhar com grandes volumes de dados é &lt;strong&gt;não carregar tudo de uma vez em memória&lt;/strong&gt;. Isso vale tanto para leitura quanto para escrita.&lt;/p&gt;

&lt;p&gt;Se o seu plano de migração envolve, por exemplo, ler um arquivo de exportação com 500 mil linhas de comandos SQL ou dados, &lt;strong&gt;você deve evitar ler o arquivo inteiro&lt;/strong&gt; com um &lt;code&gt;IO.puts&lt;/code&gt; ou similar. Em vez disso, &lt;strong&gt;processe em streaming&lt;/strong&gt;, ou seja, em pedaços.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lendo arquivos enormes de forma eficiente
&lt;/h3&gt;

&lt;p&gt;Suponha que temos um arquivo &lt;code&gt;.sql&lt;/code&gt; gigantesco armazenado em um bucket S3, contendo centenas de milhares de comandos &lt;code&gt;UPDATE&lt;/code&gt; que precisamos executar.&lt;/p&gt;

&lt;p&gt;Em vez de baixar todo o arquivo e carregá-lo na memória, podemos &lt;strong&gt;processar linha a linha em fluxo&lt;/strong&gt;. No Elixir, a biblioteca &lt;code&gt;ExAws&lt;/code&gt; nos ajuda nisso. Por exemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;O método &lt;code&gt;ExAws.S3.download_file/4&lt;/code&gt; pode baixar um objeto do S3 em múltiplas partes (concorrentes), ao invés de uma tacada só. Por padrão ele usa &lt;a href="https://hexdocs.pm/ex_aws_s3/ExAws.S3.html#download_file/4" rel="noopener noreferrer"&gt;8 partes paralelas de 1MB cada&lt;/a&gt;, o que maximiza throughput sem sobrecarregar memória (8 MB no total por padrão, podendo configurar menos). Também permite baixar direto para arquivo local ou para a memória.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Se usamos &lt;code&gt;:memory&lt;/code&gt; como destino no download_file, podemos então encadear com &lt;code&gt;ExAws.stream!/2&lt;/code&gt; para obter um &lt;a href="https://hexdocs.pm/ex_aws_s3/ExAws.S3.html" rel="noopener noreferrer"&gt;Stream lazy em Elixir&lt;/a&gt;. Esse stream vai nos entregar pedaços (chunks) de bytes conforme vamos consumindo, sem armazenar tudo antecipadamente. Importante: &lt;a href="https://hexdocs.pm/ex_aws_s3/ExAws.S3.html" rel="noopener noreferrer"&gt;nenhum dado é efetivamente buscado do S3 até começarmos a consumir o stream&lt;/a&gt;, ou seja, só puxamos pequenos blocos na medida da necessidade, mantendo o uso de memória praticamente constante.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Como cada chunk pode ser de até 1MB e não necessariamente termina exatamente no fim de uma linha do arquivo, precisamos juntar as partes e separá-las por linha. Podemos usar &lt;code&gt;Stream.chunk_while/4&lt;/code&gt; para isso: acumulamos bytes até encontrarmos um &lt;code&gt;\n&lt;/code&gt; de fim de linha, entregamos cada linha completa, e mantemos o resto para o próximo chunk. &lt;a href="https://hexdocs.pm/ex_aws_s3/ExAws.S3.html#download_file/4" rel="noopener noreferrer"&gt;A própria documentação do ExAws traz um exemplo de como implementar isso&lt;/a&gt;. Basicamente, concatenamos o chunk atual com o “resto” da linha anterior (se houver), separamos as linhas completas utilizando algo como :binary.match para encontrar quebras de linha, e guardamos o pedaço final incompleto para juntar com o próximo chunk. Com essa técnica, mesmo que a leitura do S3 seja em blocos de 1MB, o stream resultante nos fornece exatamente uma linha por vez, já devidamente separada.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Em código Elixir, poderia ficar assim (simplificado):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ExAws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;S3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"meu-bucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"caminho/arquivo.sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ExAws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunk_while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
              &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="ss"&gt;:binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_len&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
                  &lt;span class="c1"&gt;# encontramos pelo menos um \n&lt;/span&gt;
                  &lt;span class="c1"&gt;# separa até o último \n completo&lt;/span&gt;
                  &lt;span class="n"&gt;last_newline_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# calcular posição do último \n em data&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;complete_lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;split_at_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_newline_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lines_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;complete_lines&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="ss"&gt;:nomatch&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
                  &lt;span class="c1"&gt;# nenhum \n no chunk inteiro, acumula e continua&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="k"&gt;end&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
              &lt;span class="c1"&gt;# no fim, emite o que sobrou como última linha (se não vazio)&lt;/span&gt;
              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
            &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Obs: O código acima omite detalhes de implementação da busca do último newline por brevidade; consulte a documentação do ExAws para um exemplo completo.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Com isso, conseguimos iterar por cada linha do arquivo S3 sem jamais carregar o arquivo inteiro na memória. Apenas um chunk (1MB por padrão, ajustável) e uma linha por vez ficam em RAM. Essa abordagem nos salvou de estourar os limites do &lt;strong&gt;pod Kubernetes&lt;/strong&gt; onde o job rodava – lembre-se, em ambientes K8s geralmente definimos limites de memória, e um processo que tenta alocar vários GB de uma vez será morto pelo OOM Kill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dica:&lt;/strong&gt; Caso seus arquivos no S3 estejam comprimidos (por exemplo &lt;code&gt;.gz&lt;/code&gt;), você pode incorporar uma etapa de descompressão on-the-fly no stream. Por exemplo, adicionando &lt;code&gt;|&amp;gt; StreamGzip.gunzip()&lt;/code&gt; (da biblioteca &lt;code&gt;:stream_gzip&lt;/code&gt;) logo após o &lt;code&gt;ExAws.stream!&lt;/code&gt;, conseguimos baixar menos dados (comprimidos) e descompactar em fluxo, sem usar espaço em disco. Isso é extremamente útil para arquivos de migração, que muitas vezes são textos repetitivos e comprimem bem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternativa: stream via arquivo local ou HTTP
&lt;/h3&gt;

&lt;p&gt;A solução acima utiliza as capacidades do ExAws para streaming em memória. Em alguns casos, você pode preferir ou precisar de abordagens alternativas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Download para arquivo local + &lt;code&gt;File.stream!&lt;/code&gt;:&lt;/strong&gt; O &lt;code&gt;ExAws.S3.download_file/4&lt;/code&gt; também aceita um path de arquivo local. Podemos baixar o arquivo inteiro diretamente para, digamos, &lt;code&gt;"/tmp/arquivo.sql"&lt;/code&gt;, sem carregá-lo em memória, já que o download é feito em partes e salvo em disco. Depois, usamos &lt;code&gt;File.stream!("/tmp/arquivo.sql", [], 8192)&lt;/code&gt; para ler do disco linha a linha. O lado bom: delega ao sistema de arquivos, que geralmente lida bem com arquivos grandes. O lado ruim: requer espaço em disco (no K8s, talvez um volume temporário), e é uma etapa a mais (download completo antes de processar). Ainda assim, é uma alternativa simples se streaming puro estiver complicado.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streaming via HTTP (presigned URL)&lt;/strong&gt;: Caso você não esteja usando Elixir/ExAws, ou queira uma solução independente, outra opção é gerar uma URL temporária (presigned) para o arquivo no S3 e usar um cliente HTTP que suporte streaming de resposta. Por exemplo, em Elixir poderíamos usar o &lt;code&gt;Mint&lt;/code&gt; ou &lt;code&gt;HTTPoison&lt;/code&gt; para fazer um GET na URL e ir processando o corpo do HTTP em pedaços conforme chega. A própria abordagem discutida acima com HTTPStream no &lt;a href="https://www.poeticoding.com/aws-s3-in-elixir-with-exaws/#:~:text=Unfortunately%20we%20can%E2%80%99t%20use%20,the%20file%20on%20the%20fly" rel="noopener noreferrer"&gt;Poeticoding 1&lt;/a&gt; e &lt;a href="https://www.poeticoding.com/aws-s3-in-elixir-with-exaws/#:~:text=Now%20that%20we%20have%20the,code%20and%20how%20it%20works" rel="noopener noreferrer"&gt;Poeticoding 2&lt;/a&gt; seguem esse caminho – obtém-se a URL assinada e depois usa um stream HTTP para ler e transformar os dados linha a linha, tudo de forma lazy. Essa técnica independe do ExAws e poderia ser usada em qualquer linguagem com HTTP streaming (em Python requests com stream, Node streams, etc). O importante, de novo, &lt;strong&gt;é não montar tudo em memória.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Em suma, seja via biblioteca de cloud (ExAws) ou via HTTP, &lt;strong&gt;prefira streams a cargas completas&lt;/strong&gt;. Assim garantimos uso mínimo de memória e melhoramos até o throughput (pois começamos a processar as primeiras linhas enquanto o restante ainda está baixando, pipelineando as etapas).&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 2: Execução Sequencial e Controle de Transações
&lt;/h2&gt;

&lt;p&gt;Como mencionado, a stack Ecto/Postgrex tem uma restrição importante: &lt;strong&gt;não podemos mandar múltiplas instruções SQL de uma só vez&lt;/strong&gt; (separadas por ponto e vírgula) num mesmo comando via Repo.query. Se tentarmos juntar &lt;code&gt;sql1; sql2; sql3&lt;/code&gt; em uma única string e chamar &lt;code&gt;Repo.query&lt;/code&gt;, receberemos um erro de sintaxe do Postgrex dizendo que não suporta múltiplos comandos no protocolo extendido. Portanto, &lt;strong&gt;devemos alimentar nosso banco com um comando por vez&lt;/strong&gt;. No contexto do Elixir, isso significa que nosso stream de linhas SQL será processado elemento a elemento, cada linha resultando em uma chamada &lt;code&gt;Repo.query(sql)&lt;/code&gt; independente. &lt;/p&gt;

&lt;p&gt;Isso garante também que a ordem é respeitada: lemos a primeira linha do arquivo, executamos no banco; depois a segunda, executa; e assim por diante. Se a ordem das operações importa (e geralmente importa, especialmente em migrações), estaremos seguros. &lt;/p&gt;

&lt;p&gt;Uma implementação simples em Elixir poderia ser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# junta cada linha com seu número (1,2,3,...)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sql_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class="c1"&gt;# Limpando eventuais espaços e ';' do final da linha&lt;/span&gt;
     &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sql_line&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trim_trailing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;log:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_res&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; 
         &lt;span class="ss"&gt;:ok&lt;/span&gt;  &lt;span class="c1"&gt;# sucesso, não faz nada especial&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Postgrex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
         &lt;span class="n"&gt;handle_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# vamos tratar erros adiante&lt;/span&gt;
     &lt;span class="k"&gt;end&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No snippet acima, algumas coisas a notar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Utilizamos &lt;code&gt;Repo.query/3&lt;/code&gt; ao invés de funções como &lt;code&gt;Repo.query!&lt;/code&gt; ou macros de Ecto. O Repo.query nos permite executar SQL “cru” diretamente. Retorna &lt;code&gt;{:ok, result}&lt;/code&gt; ou &lt;code&gt;{:error, error}&lt;/code&gt; em vez de lançar exceção, o que nos dá controle de tratamento de erro sem parar o fluxo. Passamos &lt;code&gt;log: false&lt;/code&gt; para suprimir o log padrão (vamos falar mais sobre logging adiante).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removemos &lt;code&gt;";"&lt;/code&gt; do final da linha, se houver, para evitar mandar um ponto-e-vírgula extra que o driver poderia interpretar como comando vazio após o &lt;code&gt;;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A ordem é garantida pelo &lt;code&gt;Stream.each&lt;/code&gt; – um comando só inicia depois do anterior finalizar, já que estamos dentro do mesmo processo consumindo sequencialmente&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reutilização de conexão: Por padrão, cada chamada &lt;code&gt;Repo.query&lt;/code&gt; pega uma conexão do pool e devolve após uso. No caso de executarmos 100 mil queries sequencialmente, isso significa pegar e soltar a conexão 100 mil vezes. O overhead disso é relativamente pequeno (afinal cada query em si provavelmente é mais custosa que pegar a conexão), mas podemos otimizar mantendo a mesma conexão para todo o fluxo. Em Elixir, uma forma seria envolver esse processamento dentro de um &lt;code&gt;Repo.checkout&lt;/code&gt; (ou &lt;code&gt;DBConnection.run&lt;/code&gt;), que “prende” uma conexão do pool para uso exclusivo dentro do bloco fornecido. Assim, evitamos handshake/checkout repetido. &lt;strong&gt;Cuidado:&lt;/strong&gt; não confunda isso com usar uma transação; no &lt;code&gt;Repo.checkout&lt;/code&gt; podemos executar comandos independentes com autocommit, mas todos usando o mesmo canal de conexão. Isso pode melhorar um tiquinho a performance e garantir que não teremos espera por conexão do pool no meio.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exemplo utilizando &lt;code&gt;Repo.checkout&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;log:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handle_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No fundo, essa abordagem ou a anterior acabam fazendo a mesma coisa (conexões do Ecto geralmente são persistentes e rápidas). Em muitos casos, a simplicidade de usar apenas o stream com &lt;code&gt;Repo.query&lt;/code&gt; é suficiente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tamanho da transação: tudo de uma vez ou aos poucos?
&lt;/h3&gt;

&lt;p&gt;Uma pergunta importante ao executar migrações é: &lt;strong&gt;devemos envolver todas as operações em uma única transação?&lt;/strong&gt;. Se tivermos 100 mil comandos de update, podemos pensar: “coloca tudo dentro de &lt;code&gt;Repo.transaction(fn -&amp;gt; ... end)&lt;/code&gt; e se algo der errado fazemos rollback geral”. &lt;strong&gt;Na prática, isso raramente é uma boa ideia&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por quê? Uma transação enorme mantém &lt;strong&gt;locks e registros no log de transação do banco por muito tempo&lt;/strong&gt;, consumindo recursos. Se algo falhar no meio, todo o progresso até então é perdido ao dar rollback – o que pode desperdiçar horas de trabalho por causa de um único registro problemático. Além disso, muitos bancos (PostgreSQL incluso) podem ter desempenho pior dentro de uma transação longa, comparado a operações com &lt;em&gt;auto-commit&lt;/em&gt;, devido a gerenciamento de MVCC e inchaço de tuplas não confirmadas.&lt;/p&gt;

&lt;p&gt;No caso real que enfrentamos, optamos por não ter uma transação englobando tudo. Cada comando se &lt;em&gt;commitava&lt;/em&gt; individualmente. Assim, mesmo que um comando ou outro falhasse, as mudanças anteriores já estariam persistidas e não seriam desfeitas. Isso se alinha com a filosofia de continuar apesar de erros.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"E se precisarmos de atomicidade total?"&lt;/strong&gt; – pode perguntar alguém. Ou seja, ou migra tudo ou nada. Bem, aí é um requisito de negócio bem diferente: em geral, para grandes migrações, costuma-se planejar de forma que possamos reprocessar ou corrigir depois os poucos erros, em vez de exigir atomcidade absoluta (até porque atingir isso pode ser quase impossível sem causar downtime enorme). Mas caso precisasse, uma alternativa seria agrupar em transações menores: por exemplo, comitar a cada 1000 comandos. Assim, a cada mil operações bem-sucedidas, garantimos aquele lote no banco; se uma falhar dentro do lote, podemos rolar só aquele sub-lote. Essa abordagem de chunking transacional reduz a janela de rollback. Contudo, fica a complexidade: como lidar com um erro no meio de um lote? Repetir só ele isoladamente depois? Pode virar um pesadelo gerenciar isso manualmente.&lt;/p&gt;

&lt;p&gt;No nosso caso, decidimos: &lt;strong&gt;commit por comando = simplicidade e menor risco&lt;/strong&gt;. O preço a pagar pode ser desempenho (por causa de fsync a cada operação no banco), mas vamos tratar de otimizações mais à frente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controlando timeouts e encerramentos
&lt;/h3&gt;

&lt;p&gt;Executar 100k queries sequenciais também exige cuidado com configurações de timeout do Ecto/DB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeout de query&lt;/strong&gt;: Por padrão, cada query no Ecto tem timeout ~15 segundos. 99% das nossas operações eram rápidas (ms ou poucos s), mas e se alguma demorar mais (ex: index pesado, ou atualizando milhões de linhas numa tacada)? Para não abortar prematuramente, podemos ajustar o timeout na chamada &lt;code&gt;Repo.query(sql, [], timeout: 120_000)&lt;/code&gt; por exemplo, para dar 2 minutos naquele comando específico, ou mesmo :infinity se for algo known slow. Use com parcimônia – timeouts existem para evitar conexões travadas eternamente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mantenha vivo o processo&lt;/strong&gt;: Se rodamos isso dentro de um job no K8s, certifique-se que o pod aguenta rodar pelo tempo necessário. Os timeouts do Kubernetes (como &lt;em&gt;activeDeadlineSeconds&lt;/em&gt; num Job) devem ser configurados conforme a expectativa de duração da migração. E dentro da aplicação Elixir, se estiver sob um Supervisor, talvez aumentar temporariamente o &lt;code&gt;:timeout&lt;/code&gt; do GenServer/Task supervisor se necessário, para não ser reiniciado erroneamente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finalização graciosa&lt;/strong&gt;: Se estamos tratando erros e continuando, idealmente o job deveria terminar com sucesso (código 0) mesmo que tenha havido algumas falhas não-críticas, contanto que fizemos tudo que dava. Podemos sinalizar sucesso, mas garantir que os detalhes dessas falhas estejam logados para acompanhamento. Se preferir marcar o job como “falhou se qualquer erro ocorreu”, também é possível, mas aí perde-se a vantagem de seguir em frente – vai ter que reexecutar tudo.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agora que já lidamos com leitura e execução básica, vamos falar de &lt;strong&gt;otimizações&lt;/strong&gt; para desempenho e do &lt;strong&gt;tratamento robusto de erros&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 3: Otimizando Performance com &lt;em&gt;Chunking&lt;/em&gt; e &lt;em&gt;Batching&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Performance não deve vir antes de corretude&lt;/strong&gt; em uma migração, mas uma vez assegurados os pilares acima, podemos sim buscar maneiras de acelerar o processo para que a janela de migração seja a menor possível. Duas ideias relacionadas são: &lt;em&gt;chunking&lt;/em&gt; (processamento em pedaços) e &lt;em&gt;batching&lt;/em&gt; (agrupamento de operações).&lt;/p&gt;

&lt;h3&gt;
  
  
  Leitura em pedaços e paralelismo de download
&lt;/h3&gt;

&lt;p&gt;Já cobrimos que o &lt;code&gt;ExAws&lt;/code&gt; por padrão baixa com 8 chunks simultâneos de 1MB. Isso é bom para throughput, mas se estivermos realmente restritos em memória, poderíamos reduzir a concorrência (exAws permite configurar &lt;code&gt;max_concurrency: 1&lt;/code&gt; e até o tamanho do chunk). Isso deixaria o download totalmente sequencial (1MB por vez) e limitaria o uso de RAM a ~1MB. Em contrapartida, o download total seria um pouco mais lento. É um trade-off: para pods muito limitados talvez seja necessário, mas na maioria dos casos 8 MB de uso (8 threads x 1MB) é aceitável. Monitoramos e vimos que a utilização de memória ficou dentro do orçamento, então mantivemos o padrão que oferecia melhor velocidade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agrupar múltiplos inserts/updates em um só comando
&lt;/h3&gt;

&lt;p&gt;Uma otimização poderosa, quando aplicável, é agrupar várias operações similares em um único comando SQL. Por exemplo, se o nosso arquivo SQL fosse composto majoritariamente de milhares de &lt;code&gt;INSERT&lt;/code&gt; na mesma tabela, poderíamos combinar vários em um único &lt;code&gt;INSERT ... VALUES (...), (...), ...&lt;/code&gt; gigante, reduzindo drasticamente o número de comandos executados. O Ecto oferece a função &lt;code&gt;Repo.insert_all/3&lt;/code&gt; exatamente para inserir múltiplos registros de uma vez.&lt;/p&gt;

&lt;p&gt;Há relatos na comunidade de que &lt;a href="https://elixirforum.com/t/timeout-errors-with-long-running-transactions/20879#:~:text=I%20would%20also%20reach%20for,insert%20than%2010%E2%80%99000%20single%20insert" rel="noopener noreferrer"&gt;é muito mais eficiente executar uma query com 10.000 inserts de uma vez do que 10.000 queries separadas&lt;/a&gt;. O motivo é claro: latência de rede reduzida (uma ida ao banco ao invés de 10 mil), overhead de parsing do SQL uma vez só, etc. Se você consegue juntar N operações num lote, faça-o.&lt;/p&gt;

&lt;p&gt;No nosso caso específico, as operações não eram triviais de agrupar, pois envolviam updates de registros existentes espalhados em tabelas diferentes. Mas se, por exemplo, você está migrando dados de uma tabela antiga para uma nova, poderia ler em chunks de 1000 registros e usar um &lt;code&gt;insert_all&lt;/code&gt; na tabela nova para cada chunk, ao invés de inserir um por um.&lt;/p&gt;

&lt;p&gt;Com Elixir Streams, podemos facilmente fazer algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;stream_data&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunk_every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MySchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_conflict:&lt;/span&gt; &lt;span class="ss"&gt;:replace_all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;conflict_target:&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
   &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No pseudo-código acima, &lt;code&gt;batch&lt;/code&gt; seria uma lista de maps ou structes Ecto a inserir. O &lt;code&gt;on_conflict&lt;/code&gt; só ilustram que poderíamos até lidar com conflitos se precisarmos atualizar existentes. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atenção&lt;/strong&gt;: &lt;em&gt;batching&lt;/em&gt; funciona melhor para inserções ou operações homogêneas. Para updates complexos (diferentes tabelas, diferentes &lt;code&gt;WHERE&lt;/code&gt; para cada linha), não há um agrupamento SQL nativo fácil. Aí, de fato, seguimos com um por um. Nunca tente simplesmente concatenar vários comandos com &lt;code&gt;;&lt;/code&gt; achando que vai burlar o sistema. Cada &lt;em&gt;statement&lt;/em&gt; tem que ser enviado separadamente, a não ser que você opte por usar o protocolo simples do Postgres (não suportado pelo Ecto) ou outra ferramenta externa (por exemplo, rodar um script &lt;code&gt;.sql&lt;/code&gt; direto via client psql). No contexto de nossa aplicação Elixir, mantivemos a granularidade de um a um, abrindo mão de agrupar updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Chunking&lt;/em&gt; de transações e conexões
&lt;/h3&gt;

&lt;p&gt;Mencionamos anteriormente a possibilidade de &lt;em&gt;chunking&lt;/em&gt; transacional para melhorar performance: por exemplo, abrir uma transação a cada 1000 operações para diminuir o overhead de commit a cada linha. Optamos por não fazer isso, em parte por simplicidade e em parte porque nosso bottleneck não foi o commit em si, mas sim o tempo de execução total no banco (que envolvia consultas a índices, etc.). No seu caso, se identificar que o &lt;em&gt;flush&lt;/em&gt; de disco a cada comando está lentificando (pode acontecer se cada update é muito pequeno e o disco é lento), agrupar alguns em transação pode melhorar. Mas lembre-se do risco: se um falha, aquele bloco inteiro volta. É um equilíbrio delicado.&lt;/p&gt;

&lt;p&gt;Um membro da comunidade do Elixir inclusive reforçou que operações em lote &lt;a href="https://elixirforum.com/t/timeout-errors-with-long-running-transactions/20879/5" rel="noopener noreferrer"&gt;não combinam bem com uma transação gigantesca&lt;/a&gt;. Ou seja, de nada adianta você juntar inserts em lotes de 10k se depois envolver tudo num transaçãozão de 1 milhão – você perde os ganhos e adiciona riscos. Melhor aplicar lotes + autocommit, ou transações pequenas.&lt;/p&gt;

&lt;h3&gt;
  
  
  E quanto a paralelizar as operações?
&lt;/h3&gt;

&lt;p&gt;Até agora, assumimos execução estritamente sequencial, o que é normalmente necessário se há dependência de ordem ou se estamos atualizando o mesmo conjunto de dados (para evitar &lt;em&gt;deadlocks&lt;/em&gt; e conflitos). Porém, em algumas migrações, dá para paralelizar desde que em partições independentes. Por exemplo, se você tem 10 bancos de dados diferentes para 10 serviços, poderia rodar 10 jobs simultâneos, um por banco, cada um fazendo sua parte. Dentro de um mesmo banco, se as operações forem em tabelas completamente separadas, também poderia rodar &lt;em&gt;threads&lt;/em&gt; separadas (mas cuidado extremo com carga no banco!).&lt;/p&gt;

&lt;p&gt;De forma geral, preferimos paralelizar as operações dentro de um mesmo banco de dados, mas com controle individual por banco. Cada banco possui uma configuração de máquina específica, com diferentes capacidades de carga, e está vinculado a uma estratégia própria de migração. Todo esse processo foi cuidadosamente controlado e parametrizado de acordo com o cenário.&lt;/p&gt;

&lt;p&gt;Avalie no seu contexto: se paralelismo trouxer ganhos e não violar consistência, Elixir torna fácil utilizar &lt;em&gt;Tasks&lt;/em&gt; assíncronas ou mesmo a biblioteca &lt;strong&gt;Flow/GenStage&lt;/strong&gt; para processamento paralelo com controle de concorrência. Só tenha em mente a capacidade do banco de dados em aguentar múltiplas operações simultâneas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégia 4: Tratamento de Erros e Log Consolidado
&lt;/h2&gt;

&lt;p&gt;Chegamos em um ponto crucial: &lt;strong&gt;como lidar com erros durante a migração?&lt;/strong&gt; E, correlacionado, como organizar a saída de logs de forma útil.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capturando erros sem quebrar o fluxo
&lt;/h3&gt;

&lt;p&gt;A chave para uma migração resiliente é isolar falhas. No pseudo-código que mostramos, usamos &lt;code&gt;Repo.query&lt;/code&gt; (não &lt;code&gt;!&lt;/code&gt;), para obter um &lt;code&gt;{:error, err}&lt;/code&gt; em vez de uma exceção. Assim, nenhum erro de SQL levanta uma exceção não tratada que interromperia todo o processamento. Nós capturamos o erro, logamos, e seguimos. &lt;/p&gt;

&lt;p&gt;Exemplos de erros que podem ocorrer em uma linha SQL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Violação de integridade (ex: &lt;code&gt;FOREIGN KEY&lt;/code&gt;, &lt;code&gt;UNIQUE&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Erro de sintaxe no comando (às vezes scripts gerados podem ter alguma linha malformada).&lt;/li&gt;
&lt;li&gt;Deadlock detectado no banco (código &lt;code&gt;SQLSTATE 40P01&lt;/code&gt; no Postgres).&lt;/li&gt;
&lt;li&gt;Timeout ou perda de conexão momentânea.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para muitos desses, &lt;strong&gt;repetir a operação não vai adiantar&lt;/strong&gt; (ex.: sintaxe errada vai continuar errada). Mas para alguns transientes vale a pena tentar novamente. Implementamos uma lógica simples de &lt;em&gt;retry&lt;/em&gt; para certos casos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Se o erro for de deadlock (&lt;code&gt;err.postgres.code == "40P01"&lt;/code&gt;), podemos aguardar um instante e tentar de novo aquela query, um número limitado de vezes. Deadlocks são meio aleatórios e um retry simples pode resolver.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Se for erro de conexão (por exemplo, &lt;code&gt;DBConnection.ConnectionError&lt;/code&gt; indicando que a conexão caiu), o pool do Ecto geralmente já tentará reconectar na próxima query automaticamente. Então, talvez apenas registrar e tentar novamente seja suficiente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Se for qualquer outro erro persistente, registramos e não repetimos.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A implementação pode ser algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;exec_sql_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;timeout:&lt;/span&gt; &lt;span class="mi"&gt;30_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;log:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Postgrex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;postgres:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;code:&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
         &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"40P01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"40001"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="c1"&gt;# 40001 seria serialization failure (ex.: retry do Postgres)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nv"&gt;@max_retries&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# espera incremental&lt;/span&gt;
        &lt;span class="n"&gt;exec_sql_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;log_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;log_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No nosso caso específico, a incidência de erros foi baixíssima, então não complicamos muito com retries – registramos e pronto. Mas fica a ideia para quem for implementar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logando erros em um só lugar
&lt;/h3&gt;

&lt;p&gt;Em vez de imprimir cada erro na saída padrão (o que iria misturá-los no meio do log geral da aplicação ou do Kubernetes), optamos por &lt;strong&gt;consolidar todos os erros em um arquivo de log separado&lt;/strong&gt;, por arquivo de entrada processado. &lt;/p&gt;

&lt;p&gt;Ou seja, se estamos processando arquivo.sql, criamos &lt;code&gt;arquivo.sql.errors.log&lt;/code&gt; e vamos adicionando entradas lá dentro sempre que ocorre um erro. Cada entrada inclui pelo menos o número da linha e uma mensagem do erro retornado pelo banco, algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linha 12345: erro ao executar "UPDATE ...": ERROR: duplicate key value violates unique constraint "idx_user_email"
Linha 67890: erro ao executar "INSERT ...": ERROR: null value in column "id" violates not-null constraint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso nos permitiu, ao final, ter um resumo de tudo que falhou em um só lugar, fácil de compartilhar com o time ou analisar depois. Enquanto isso, o job principal podia terminar com sucesso sem poluir os logs de execução normais com milhares de linhas (que dificultariam enxergar o progresso).&lt;/p&gt;

&lt;p&gt;Implementação em Elixir usando File:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"logs/arquivo_sql_erro.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:utf8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
       &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trim_trailing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
         &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
         &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
           &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Linha &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;end&lt;/span&gt;
     &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No snippet, abrimos o arquivo e dentro da função passamos o stream. Cada erro faz um &lt;code&gt;IO.puts&lt;/code&gt; no arquivo. Estamos no mesmo processo do stream, então não há condição de corrida no arquivo (se fossem paralelos, teria que sincronizar a escrita). Esse arquivo depois poderia ser anexado como artefato, enviado ao S3, ou lido por alguma rotina de notificação. &lt;/p&gt;

&lt;p&gt;Nota: preferimos acumular erros em memória e depois gravar tudo de uma vez? Poderíamos ter usado um &lt;code&gt;Enum.reduce&lt;/code&gt; no stream para construir uma lista de erros. Mas imagine se 10 mil linhas falham – segurar 10 mil strings de erro em memória também pesa. Gravar em arquivo conforme ocorrem foi uma abordagem mais simples também, e garante que mesmo que o &lt;em&gt;job&lt;/em&gt; pare no meio, os erros até ali já estarão no arquivo parcial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silenciando logs verbosos
&lt;/h3&gt;

&lt;p&gt;O Ecto por padrão loga cada query executada (no nível debug do Logger). Se não fizermos nada, rodar 100 mil comandos vai gerar 100 mil linhas de log do tipo &lt;code&gt;"QUERY OK ... etc"&lt;/code&gt;. Isso é totalmente indesejado aqui. Por isso usamos &lt;code&gt;log: false&lt;/code&gt; em cada &lt;code&gt;Repo.query&lt;/code&gt; – assim, nenhum log é emitido para queries bem-sucedidas. Apenas nossos próprios logs de erro (ou logs de progresso que quisemos inserir manualmente) aparecem. Essa é uma flag simples e poderosa para controlar isso.&lt;/p&gt;

&lt;p&gt;Como resultado, os logs do sistema ficaram limpos, contendo basicamente: inícios e fins de processamento de cada arquivo, um contador a cada X linhas (colocamos um log info a cada 1000 mil operações para saber que estava avançando) e quanto de memória o processo esta consumindo. Todo o resto – detalhes de falhas – ficou confinado ao arquivo de erro específico.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitorando progresso e métricas
&lt;/h3&gt;

&lt;p&gt;Em migrações longas, é bom ter noção do andamento. Implementamos, como dito, um log de progresso a cada bloco de linhas processadas (ex: "10k linhas processadas...", "20k..."). Isso ajuda a estimar tempo restante e verificar que não está travado. Também é possível coletar métricas, como tempo total, contagem de sucessos vs erros, etc., e expor no final. Essas informações podem ir para um relatório final ou mesmo enviados a um sistema de monitoramento.&lt;/p&gt;

&lt;h3&gt;
  
  
  Considerações para múltiplos serviços
&lt;/h3&gt;

&lt;p&gt;No nosso cenário hipotético, precisamos mudar dados em vários bancos de diferentes serviços simultaneamente. Como coordenar isso? Existem algumas abordagens possíveis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Job distribuído&lt;/strong&gt;: Colocar um job de migração em cada serviço (por exemplo, uma migration no estilo &lt;em&gt;ActiveRecord/Ecto&lt;/em&gt; própria de cada microserviço) e acioná-los todos juntos (manualmente ou via orquestração). Cada um cuidaria do seu banco local. A coordenação aqui é mais humana: garantir que todos rodem na mesma janela de manutenção, talvez colocando os serviços em modo de espera enquanto roda, se necessário.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orquestrador central&lt;/strong&gt;: Criar um serviço ou script mestre que conecta em todos os bancos necessários (ou chama APIs dos serviços) para aplicar as mudanças. Isso centraliza em um ponto, mas pode ser mais complexo de implementar (precisa de credenciais de todos, etc) e um ponto de falha único.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature Flags e migração gradual&lt;/strong&gt;: Dependendo da mudança, às vezes podemos introduzir o novo formato de dado em paralelo ao antigo, fazer os sistemas aceitarem ambos temporariamente (feature flag), migrar dados gradualmente, e depois desligar o formato antigo. Essa é a estratégia &lt;strong&gt;blue-green&lt;/strong&gt; ou &lt;strong&gt;expand-contract&lt;/strong&gt;. Contudo, em exigências regulatórias rígidas de “trocar tudo até data X”, talvez não seja possível conviver com dois formatos; de qualquer forma, é sempre bom avaliar se uma migração big bang é realmente necessária ou se dá pra fazer incrementalmente.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No caso de precisar do “ao mesmo tempo”, uma prática é agendar uma janela de manutenção onde todos os serviços ficam indisponíveis ou em modo read-only enquanto a migração roda. Assim, não há problema se um terminar antes do outro, pois nada está processando dados inconsistentes nesse meio tempo. &lt;strong&gt;Claro, downtime coordenado é algo a se evitar em geral, mas às vezes é a opção mais simples para eliminar riscos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se optar por downtime zero, aí a coisa fica mais complexa: tem que garantir que cada passo seja compatível com o sistema rodando. Por exemplo, você poderia:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adicionar a nova coluna/identificador em todas bases (passo preparatório).&lt;/li&gt;
&lt;li&gt;Popular a nova coluna com base nos dados antigos (talvez com scripts de migração como descrito).&lt;/li&gt;
&lt;li&gt;Alterar os serviços para usar a nova coluna a partir de um timestamp.&lt;/li&gt;
&lt;li&gt;Remover a coluna antiga depois de verificar que tudo está usando a nova.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Essa estratégia de expandir e contrair o esquema minimiza indisponibilidade, mas requer planejar a aplicação também.&lt;/p&gt;

&lt;p&gt;No nosso cenário, não entraremos nos detalhes de negócio, mas vale destacar que utilizamos um orquestrador para conduzir o processamento e aplicar os scripts de migração em paralelo nos múltiplos bancos — tudo isso sem qualquer downtime. Foi como trocar a turbina com o avião em pleno voo, em uma operação extremamente coordenada e controlada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações Finais e Dicas
&lt;/h2&gt;

&lt;p&gt;Reunindo os pontos principais que aprendemos com essa migração de dados em larga escala, aqui vai um pequeno guia de melhores práticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Planejamento é tudo&lt;/strong&gt;: Entenda exatamente quais sistemas e dados serão afetados. Desenhe o plano completo, incluindo ordem das operações, dependências, plano de rollback (o que fazer se algo der muito errado), e teste em ambiente de staging com uma cópia dos dados se possível.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use streams e processamento incremental&lt;/strong&gt;: Evite ler ou carregar grande volumes de uma vez. Em Elixir, aproveite do &lt;code&gt;Stream&lt;/code&gt; para ler arquivos ou consultas aos poucos, em Python use &lt;em&gt;geradores&lt;/em&gt;, em Java use &lt;em&gt;iteradores&lt;/em&gt; ou &lt;em&gt;streams&lt;/em&gt;, etc. Isso torna seu processo mais leve e previsível.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conheça suas ferramentas&lt;/strong&gt;: No nosso caso, saber das limitações do Ecto/Postgrex (um statement por vez) e funcionalidades úteis como &lt;code&gt;Repo.insert_all&lt;/code&gt;, &lt;code&gt;log: false&lt;/code&gt; e controle de timeouts fez a diferença. Seja qual for sua linguagem, pesquise se há suporte a &lt;em&gt;bulk&lt;/em&gt; operations, qual o comportamento de transações grandes, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Não abuse das transações&lt;/strong&gt;: A menos que estritamente necessário, não coloque terabytes de operações numa única transação. Prefira &lt;em&gt;checkpointar&lt;/em&gt; (se é que esse termo existe) progresso (commits parciais) para não perder tudo em falhas pontuais.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logs e comunicação&lt;/strong&gt;: Uma migração desse porte deve ter logs claros. Consolidar erros em um arquivo separado provou ser uma boa decisão no nosso caso – facilitou compartilhar os problemas específicos com o time para corrigir aqueles registros manualmente depois, por exemplo. Também mantenha stakeholders informados do progresso (um simples &lt;code&gt;“já foram processados X% dos registros”&lt;/code&gt; a cada tanto tempo acalma os ânimos durante uma manutenção prolongada).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Teste de performance e tuning&lt;/strong&gt;: Se possível, rode um subset da migração (ex: 10 mil operações) para medir o tempo e extrapolar, identificando gargalos. Ajuste parâmetros como &lt;code&gt;chunk_size&lt;/code&gt; do download, tamanho de pool de conexão, paralelismo, etc., conforme os recursos disponíveis. No Kubernetes, assegure-se de solicitar CPU/memória adequadas para o job de migração – ele provavelmente vai usar bem mais recursos do que sua aplicação no dia-a-dia.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ferramentas específicas de migração&lt;/strong&gt;: Às vezes, vale avaliar ferramentas desenhadas para ETL ou migração, como Apache Beam, Spark, etc., especialmente se for transformar dados. No nosso caso, como era basicamente SQL direto no banco, o Elixir deu conta de orquestrar bem. Mas se fosse algo como migrar dados entre bancos diferentes, talvez um pipeline de dados dedicado fosse mais adequado.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elixir como aliado&lt;/strong&gt;: De modo geral, Elixir se mostrou uma ótima escolha para essa tarefa pela sua facilidade em lidar com IO e streams, pelas &lt;strong&gt;primitivas de concorrência&lt;/strong&gt; (que nos deram a opção de paralelizar caso quiséssemos, sem complicação), e pela robustez do ecosistema (ExAws para S3, Ecto para DB, etc.). Outros ecossistemas também conseguem, mas apreciar essas vantagens ajuda a valorizar a ferramenta certa para o job certo.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Grandes migrações de dados não precisam ser um bicho de sete cabeças. Com as técnicas certas, &lt;strong&gt;é possível processar milhões de registros de forma eficiente, segura e sem estresse desnecessário&lt;/strong&gt;. Recapitulando o que vimos: primeiro, opte por leitura e processamento &lt;em&gt;streaming&lt;/em&gt; para manter os recursos sob controle (memória constante, sem sobrecarga); segundo, garanta execução ordenada e atomicidade na medida certa – nem de menos (não perder consistência), nem demais (não travar tudo numa mega transação); terceiro, aplique otimizações de desempenho viáveis, agrupando operações quando possível e evitando repetir trabalho desnecessário; quarto, trate erros como cidadãos de primeira classe – não deixe uma exceção interromper sua migração no meio, capture, logue e siga adiante sempre que fizer sentido; e por último, planeje a coordenação entre sistemas se for um cenário distribuído, seja através de sincronização manual ou automações de orquestração. &lt;/p&gt;

&lt;p&gt;Ao seguir essas práticas, no caso real obtivemos sucesso na migração: todos os sistemas passaram a usar a nova referência de titular sem downtime e sem incidentes graves. Os poucos ajustes manuais necessários (devido a erros registrados) puderam ser feitos rapidamente depois, graças ao nosso log consolidado. E, talvez mais importante, ganhamos confiança para enfrentar a próxima migração que surgir – &lt;strong&gt;porque em um ambiente de rápido crescimento e evolução, sempre haverá a próxima!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Referências
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📦 Elixir e Ecossistema
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/ex_aws/ExAws.S3.html" rel="noopener noreferrer"&gt;ExAws.S3 – Documentação Oficial (Hexdocs)&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Uso de &lt;code&gt;download_file/4&lt;/code&gt;, &lt;code&gt;stream!/2&lt;/code&gt; e estratégias de leitura em chunks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/elixir/Stream.html#chunk_while/4" rel="noopener noreferrer"&gt;Stream.chunk_while/4 – Elixir (Hexdocs)&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Para dividir binários de chunks em linhas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/elixir/File.html#stream!/3" rel="noopener noreferrer"&gt;File.stream!/3 – Leitura eficiente de arquivos locais&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/ecto/Ecto.Repo.html#c:query/4" rel="noopener noreferrer"&gt;Ecto.Repo.query/3 – Execução de SQL cru&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Com uso de &lt;code&gt;log: false&lt;/code&gt; para suprimir logs excessivos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.html#insert_all/3" rel="noopener noreferrer"&gt;Ecto.Adapters.SQL.insert_all/3 – Inserções em lote&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/db_connection/DBConnection.html#run/3" rel="noopener noreferrer"&gt;DBConnection.run/3 – Controle manual de conexões&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://groups.google.com/g/elixir-lang-talk/c/jjY7s4_Ty08" rel="noopener noreferrer"&gt;Postgrex – Discussão sobre múltiplos statements&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Limitações do driver ao lidar com múltiplos comandos SQL em uma query.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🌐 Comunidade e Casos de Uso
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://elixirforum.com/t/insert-all-vs-multiple-single-inserts/2146" rel="noopener noreferrer"&gt;ElixirForum – insert_all vs múltiplos inserts&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Comparação prática entre performance de batches e inserts unitários.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://poeticoding.com/streaming-large-files-efficiently/" rel="noopener noreferrer"&gt;Poeticoding – Streaming de arquivos grandes do S3 ou HTTP&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Aborda leitura eficiente via S3, streaming HTTP e processamento incremental.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🗄️ Banco de Dados – PostgreSQL
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.postgresql.org/docs/current/errcodes-appendix.html" rel="noopener noreferrer"&gt;PostgreSQL SQLSTATE Error Codes&lt;/a&gt;
Lista oficial de códigos de erro (ex: deadlocks, timeout, violação de chave única).&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🧰 Infraestrutura e Operações
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/" rel="noopener noreferrer"&gt;Kubernetes Jobs – Documentação oficial&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Execução de jobs batch e configuração de memória/timeout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/logger/Logger.html" rel="noopener noreferrer"&gt;Logger – Documentação do Elixir Logger&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Controle de níveis de log durante execuções longas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/stream_gzip/StreamGzip.html" rel="noopener noreferrer"&gt;StreamGzip – Descompactação em stream no Elixir&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Permite leitura de arquivos &lt;code&gt;.gz&lt;/code&gt; diretamente em fluxo.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Porque Evitar o Prefixo maybe_ em Funções Elixir</title>
      <dc:creator>Luan Andryl</dc:creator>
      <pubDate>Wed, 22 Jan 2025 15:30:04 +0000</pubDate>
      <link>https://forem.com/andryl_/por-que-evitar-o-prefixo-maybe-em-funcoes-elixir-2i6</link>
      <guid>https://forem.com/andryl_/por-que-evitar-o-prefixo-maybe-em-funcoes-elixir-2i6</guid>
      <description>&lt;p&gt;Em linguagens funcionais, conceitos como monads desempenham um papel crucial no gerenciamento de efeitos colaterais e no encadeamento seguro de operações. Um exemplo famoso é o tipo &lt;code&gt;Maybe&lt;/code&gt; em Haskell, que abstrai cálculos opcionais e evita o tratamento explícito de valores ausentes. Entretanto, Elixir, uma linguagem funcional moderna construída sobre a BEAM (a máquina virtual do Erlang), segue um caminho diferente no design de linguagem.&lt;/p&gt;

&lt;p&gt;Essa escolha tem implicações diretas na forma como lidamos com cenários similares e explica por que o uso do prefixo &lt;code&gt;maybe_&lt;/code&gt; em funções não é uma prática recomendada. Vamos explorar as razões técnicas e de design por trás dessa decisão e como Elixir oferece alternativas alinhadas aos seus objetivos.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Papel das Monads e o Tipo &lt;code&gt;Maybe&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Monads, em linguagens como Haskell, são estruturas abstratas que permitem encadear operações enquanto lidam de maneira transparente com efeitos colaterais ou valores ausentes. O tipo &lt;code&gt;Maybe&lt;/code&gt;, por exemplo, representa valores opcionais e encapsula dois casos: &lt;code&gt;Just&lt;/code&gt; (um valor presente) ou &lt;code&gt;Nothing&lt;/code&gt; (ausência de valor).&lt;/p&gt;

&lt;p&gt;Isso é poderoso porque evita que desenvolvedores escrevam condicionais explícitos para cada caso onde um valor pode ou não existir. Veja este exemplo em Haskell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;safeDivide&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Maybe&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
&lt;span class="n"&gt;safeDivide&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Nothing&lt;/span&gt;
&lt;span class="n"&gt;safeDivide&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;`&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(`&lt;/span&gt;&lt;span class="n"&gt;safeDivide&lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(`&lt;/span&gt;&lt;span class="n"&gt;safeDivide&lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- Resultado: Just 1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui, o operador &lt;code&gt;&amp;gt;&amp;gt;=&lt;/code&gt; permite encadear chamadas que respeitam as regras do tipo &lt;code&gt;Maybe&lt;/code&gt;. Se qualquer etapa retornar &lt;code&gt;Nothing&lt;/code&gt;, as subsequentes serão ignoradas automaticamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que Elixir Não Adota Monads?
&lt;/h2&gt;

&lt;p&gt;Elixir, apesar de ser funcional, não implementa monads diretamente, e isso é uma escolha consciente. O design da linguagem privilegia simplicidade e desempenho na BEAM. Há algumas razões principais para essa decisão:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Semântica Simples e Explícita: Monads, embora elegantes, podem introduzir complexidade conceitual para quem não está familiarizado com o paradigma funcional. Em Elixir, o objetivo é manter a linguagem acessível para um público amplo, inclusive desenvolvedores com histórico em linguagens imperativas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A BEAM Já Resolve Muitos Problemas: A BEAM oferece mecanismos robustos para lidar com falhas e concorrência, como links, supervisores e processos isolados. Isso reduz a necessidade de abstrações como monads para controle de fluxo ou propagação de erros.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alternativas Idiomáticas em Elixir: Construções como pattern matching, &lt;code&gt;with&lt;/code&gt; e o uso de tuplas (&lt;code&gt;{:ok, value}&lt;/code&gt; ou &lt;code&gt;{:error, reason}&lt;/code&gt;) fornecem maneiras idiomáticas e explícitas de lidar com fluxos de dados opcionais ou falhas. Essas ferramentas já atendem às necessidades comuns sem introduzir uma camada extra de abstração.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  O Problema do Prefixo &lt;code&gt;maybe_&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Quando usamos o prefixo &lt;code&gt;maybe_&lt;/code&gt; para nomear funções em Elixir, estamos implicitamente trazendo associações do tipo &lt;code&gt;Maybe&lt;/code&gt; de linguagens como Haskell. Isso cria uma expectativa equivocada de que essas funções se comportem como uma monad, ou seja, que possam ser encadeadas automaticamente e que lidem com valores opcionais de maneira implícita. Contudo, em Elixir, qualquer controle de fluxo requer estruturas explícitas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;maybe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;maybe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um nome como &lt;code&gt;maybe_divide&lt;/code&gt; sugere que estamos lidando com uma abstração como o &lt;code&gt;Maybe&lt;/code&gt; de Haskell, mas o resultado da função é apenas um nil em caso de falha. Para encadear essa função, é necessário criar lógica explícita:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;maybe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maybe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não reflete a elegância associada às monads e vai contra o espírito do design explícito e simples do Elixir.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Solução Idiomática em Elixir: &lt;code&gt;with&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Elixir resolve o problema de controle de fluxo e propagação de falhas com a macro &lt;code&gt;with&lt;/code&gt;. Essa construção permite encadear operações que podem falhar, mas mantém a clareza e a simplicidade.&lt;/p&gt;

&lt;p&gt;Reescrevendo o exemplo anterior com &lt;code&gt;with&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;safe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:division_by_zero&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;safe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;safe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;safe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
         &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;with&lt;/code&gt; elimina a necessidade de lógica explícita de case, simplifica o código e fornece um controle de fluxo elegante, sem depender de conceitos complexos como monads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão: O Papel do Design do Elixir na Nomenclatura e no Tratamento de Fluxos
&lt;/h2&gt;

&lt;p&gt;O prefixo &lt;code&gt;maybe_&lt;/code&gt; em funções Elixir não é bem-visto porque carrega associações enganosas com o conceito de monads, que não fazem parte do design da linguagem. Isso pode levar a confusão e expectativas errôneas, ao sugerir que há uma abstração implícita para lidar com valores opcionais, quando na verdade o Elixir se baseia em construções explícitas e diretas como tuplas (&lt;code&gt;{:ok, valor}&lt;/code&gt; ou &lt;code&gt;{:error, motivo}&lt;/code&gt;), pattern matching e a macro with. Essas ferramentas são mais do que suficientes para resolver problemas semelhantes, de forma clara e idiomática.&lt;/p&gt;

&lt;p&gt;Além disso, nomes como &lt;code&gt;try_algo&lt;/code&gt; ou &lt;code&gt;maybe_algo&lt;/code&gt; também contradizem a filosofia subjacente ao Elixir e à BEAM, que adota o princípio "let it crash" ("deixe falhar"). Em Elixir, não &lt;strong&gt;tentamos fazer&lt;/strong&gt; algo; simplesmente &lt;strong&gt;fazemos&lt;/strong&gt;, e qualquer falha é tratada de forma explícita ou delegada ao sistema robusto de supervisão da BEAM. Essa abordagem elimina o excesso de condicionais defensivos, promovendo código limpo e alinhado ao design da linguagem.&lt;/p&gt;

&lt;p&gt;Considere um exemplo simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Não idiomático&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;try_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:division_by_zero&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;try_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neste caso, o nome &lt;code&gt;try_divide&lt;/code&gt; não reflete a realidade do código nem a forma como a BEAM trata operações. Um nome mais claro e idiomático seria simplesmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;safe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:division_by_zero&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;safe_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com essa abordagem, o nome comunica exatamente o que a função faz: realiza uma divisão segura e retorna um erro se necessário. Isso está em harmonia com a simplicidade e a clareza esperadas no design de Elixir.&lt;/p&gt;

&lt;p&gt;Por fim, ao evitar nomenclaturas como &lt;code&gt;try_&lt;/code&gt; e &lt;code&gt;maybe_&lt;/code&gt;, garantimos que nosso código reflita os princípios fundamentais de Elixir: simplicidade, eficiência, resiliência e um alinhamento direto com os valores centrais da BEAM. A linguagem foi projetada para ser clara e explícita, e nossas escolhas de nomenclatura e design devem reforçar essa filosofia, promovendo código mais idiomático, legível e poderoso.&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Sai do Barroco, Vem pro meio do Rococó: Um Novo Olhar sobre a Programação Funcional</title>
      <dc:creator>Luan Andryl</dc:creator>
      <pubDate>Fri, 11 Oct 2024 19:11:17 +0000</pubDate>
      <link>https://forem.com/andryl_/sai-do-barroco-vem-pro-meio-do-rococo-um-novo-olhar-sobre-a-programacao-funcional-1an2</link>
      <guid>https://forem.com/andryl_/sai-do-barroco-vem-pro-meio-do-rococo-um-novo-olhar-sobre-a-programacao-funcional-1an2</guid>
      <description>&lt;p&gt;Esses dias, estava codificando e escutando essa &lt;a href="https://www.youtube.com/watch?v=otXx46_FA80&amp;amp;ab_channel=CercleRecords" rel="noopener noreferrer"&gt;música&lt;/a&gt;, e no meio da música havia a seguinte frase: "Sai do Barroco, vai pro meio do Rococó". Terminei o código e fui participar de algumas reuniões mundanas da vida de um engenheiro de software, mas, em específico, o tema era: como podemos tornar um conjunto de processos dentro do ecossistema da empresa mais simples e resiliente.&lt;/p&gt;

&lt;p&gt;Mas, por algum motivo, a frase ainda ficava na minha cabeça: "Sai do Barroco, vai pro meio do Rococó". Terminadas as reuniões, fui até o Google tentar entender mais sobre o que se tratam esses movimentos, e minha mente simplesmente explodiu. Não consegui fazer mais nada além de produzir o conteúdo abaixo. Sugiro que deem play na música e me acompanhem nessa viagem.&lt;/p&gt;

&lt;h3&gt;
  
  
  A viagem começa aqui
&lt;/h3&gt;

&lt;p&gt;Imagine-se caminhando pelas ruas de uma cidade histórica europeia. De um lado, ergue-se uma catedral barroca, com suas fachadas exuberantes, colunas imponentes e detalhes ornamentais que parecem competir entre si pela sua atenção. Cada elemento arquitetônico é carregado de simbolismo e complexidade, refletindo uma época em que a grandiosidade e o esplendor eram sinônimos de poder e inovação.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk1nr5fgdq5jjw8rtjup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk1nr5fgdq5jjw8rtjup.png" alt="Interior de uma igreja barroca" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agora, ao dobrar a esquina, você se depara com um palácio rococó. A atmosfera muda imediatamente. As linhas pesadas dão lugar a curvas graciosas, os detalhes ornamentais tornam-se mais leves e delicados. Há uma sensação de harmonia e elegância que convida à contemplação tranquila. A complexidade dá lugar à simplicidade refinada, sem perder a riqueza artística.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdw468965vhv48jtccld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdw468965vhv48jtccld.png" alt="Interior de um palacio rococó" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essa transição arquitetônica entre o Barroco e o Rococó serve como uma metáfora poderosa para o mundo da programação. Assim como a arquitetura barroca refletia uma abordagem pesada e complexa, a Programação Orientada a Objetos (POO) tem sido, por décadas, o paradigma dominante, estruturando sistemas com hierarquias profundas e interdependências complexas. No entanto, à medida que avançamos para uma era onde a Lei de Moore já não se sustenta, há uma necessidade crescente de repensar nossas abordagens. É hora de "sair do Barroco e vir para o Rococó", abraçando a programação funcional como uma forma mais elegante e eficiente de resolver problemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Esplendor e o Peso do Barroco na Programação
&lt;/h2&gt;

&lt;p&gt;No século XVII, o Barroco surgiu na Europa como uma resposta à busca por magnificência e impacto emocional na arte e arquitetura. As edificações barrocas eram conhecidas por sua opulência, com fachadas ornamentadas, interiores luxuosos e uma abundância de detalhes. A intenção era impressionar, provocar admiração e demonstrar poder.&lt;/p&gt;

&lt;p&gt;Analogamente, a Programação Orientada a Objetos emergiu nas décadas de 1960 e 1970 como uma solução inovadora para lidar com a crescente complexidade dos softwares. Linguagens como Simula e Smalltalk introduziram conceitos como classes, objetos, herança e encapsulamento. A POO permitiu que desenvolvedores modelassem sistemas complexos, organizando o código em estruturas que refletiam o mundo real.&lt;/p&gt;

&lt;p&gt;Durante anos, a POO foi a resposta para a necessidade de organizar códigos que cresciam em tamanho e complexidade. No entanto, assim como o Barroco, essa abordagem começou a revelar suas limitações. Projetos orientados a objetos frequentemente sofrem com códigos excessivamente complexos, difíceis de manter e escalar. A criação de hierarquias profundas de classes pode levar a um emaranhado de dependências, onde a alteração de um pequeno componente pode ter efeitos imprevisíveis em todo o sistema.&lt;/p&gt;

&lt;p&gt;As semelhanças com a arquitetura barroca são evidentes. A abundância de detalhes e a busca por grandeza podem resultar em um peso estrutural que dificulta a adaptação e evolução. Da mesma forma, sistemas orientados a objetos podem se tornar rígidos, resistindo a mudanças e exigindo esforços significativos para incorporar novas funcionalidades ou corrigir problemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Fim da Lei de Moore e os Novos Desafios
&lt;/h2&gt;

&lt;p&gt;Por décadas, a indústria da tecnologia se beneficiou da Lei de Moore, que previu o aumento exponencial do número de transistores em um chip, permitindo avanços contínuos no poder de processamento. Essa tendência mascarou, em muitos casos, a ineficiência do software, já que o hardware sempre avançava para compensar códigos pesados.&lt;/p&gt;

&lt;p&gt;Contudo, chegamos a um ponto em que os limites físicos tornam essa progressão insustentável. O fim da Lei de Moore significa que não podemos mais contar com o hardware para melhorar o desempenho dos nossos softwares automaticamente. Além disso, a demanda por sistemas distribuídos, computação em nuvem e processamento paralelo colocou em evidência a necessidade de códigos mais eficientes e escaláveis.&lt;/p&gt;

&lt;p&gt;Nesse novo cenário, a complexidade da POO torna-se um obstáculo. O gerenciamento de estados mutáveis, típico em objetos, dificulta a programação concorrente e aumenta o risco de erros difíceis de detectar e corrigir. A manutenção de grandes sistemas orientados a objetos pode se tornar insustentável, com custos elevados e tempos de desenvolvimento prolongados.&lt;/p&gt;

&lt;p&gt;Assim como o Barroco enfrentou críticas e deu lugar ao Rococó, que buscava uma abordagem mais leve e elegante, a programação também precisa evoluir. É necessário adotar paradigmas que permitam construir sistemas robustos, mas que sejam ao mesmo tempo flexíveis, eficientes e mais fáceis de manter.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Elegância do Rococó e a Programação Funcional
&lt;/h2&gt;

&lt;p&gt;O Rococó surgiu na França, no início do século XVIII, como uma evolução natural do Barroco, mas com uma mudança significativa no foco estético. Os arquitetos rococós valorizavam a leveza, a assimetria e a ornamentação delicada. As estruturas buscavam criar espaços mais íntimos e acolhedores, utilizando cores claras, motivos naturais e linhas curvas que transmitiam movimento e fluidez.&lt;/p&gt;

&lt;p&gt;Transpondo essa filosofia para o universo da programação, encontramos na programação funcional um paralelo interessante. Embora os conceitos fundamentais da programação funcional existam desde os primórdios da computação, foi nos últimos anos que esse paradigma ganhou destaque. Com linguagens como Haskell, Erlang, Clojure e Elixir, a programação funcional oferece uma abordagem que prioriza a simplicidade, a imutabilidade e a ausência de efeitos colaterais.&lt;/p&gt;

&lt;p&gt;Na programação funcional, funções são cidadãos de primeira classe. Elas podem ser passadas como parâmetros, retornadas como resultados e compostas para criar funcionalidades mais complexas. A ênfase na imutabilidade significa que os dados não são alterados após serem criados, eliminando uma série de problemas relacionados ao estado compartilhado e à concorrência.&lt;/p&gt;

&lt;p&gt;Essa abordagem se alinha com os princípios do Rococó: em vez de construir estruturas pesadas e complexas, busca-se a elegância através da simplicidade e da harmonia. O código funcional tende a ser mais conciso e expressivo, facilitando a leitura e a compreensão. Além disso, a ausência de efeitos colaterais torna o comportamento do software mais previsível e confiável.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Redescoberta da Programação Funcional na Era Moderna
&lt;/h2&gt;

&lt;p&gt;Com o fim da Lei de Moore, a indústria começou a buscar alternativas para continuar avançando. A programação funcional emergiu como uma resposta eficaz aos desafios da programação concorrente e paralela. Em sistemas onde a escalabilidade é crucial, a imutabilidade e as funções puras oferecem vantagens significativas.&lt;/p&gt;

&lt;p&gt;Empresas como a Microsoft, com a introdução de recursos funcionais no .NET através do F#, e a crescente adoção de Scala e Clojure no ecossistema Java, demonstram que a programação funcional está deixando de ser uma curiosidade acadêmica para se tornar uma ferramenta prática e poderosa.&lt;/p&gt;

&lt;p&gt;A programação funcional não é apenas uma nova forma de escrever código, mas uma mudança fundamental na forma de pensar sobre problemas. Requer que os desenvolvedores desapeguem de conceitos arraigados na POO e adotem uma mentalidade que privilegia a transformação de dados através de funções puras.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Necessidade de Mudar: Saindo do Barroco e Abraçando o Rococó
&lt;/h2&gt;

&lt;p&gt;Assim como a transição do Barroco para o Rococó representou uma busca por equilíbrio e refinamento, a migração para a programação funcional é uma resposta às demandas atuais da indústria. A complexidade excessiva não é mais sustentável. É preciso adotar abordagens que permitam construir sistemas robustos, mas que sejam também flexíveis e fáceis de manter.&lt;/p&gt;

&lt;p&gt;A programação funcional oferece ferramentas para lidar com a concorrência de forma mais natural, reduzindo os riscos associados a estados mutáveis e efeitos colaterais. Além disso, promove um código mais limpo e legível, facilitando a colaboração e a manutenção a longo prazo.&lt;/p&gt;

&lt;p&gt;Essa transição não significa abandonar completamente os conceitos da POO. Muitas linguagens modernas permitem a combinação de paradigmas, aproveitando o melhor de cada abordagem. No entanto, é fundamental reconhecer as limitações da POO tradicional e estar aberto a novas formas de pensar e resolver problemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Jornada não Acaba aqui
&lt;/h2&gt;

&lt;p&gt;A história nos ensina que a evolução é essencial para o progresso. Na arte, na arquitetura e na tecnologia, precisamos constantemente revisar e adaptar nossas abordagens para atender às novas realidades. A metáfora entre o Barroco e o Rococó ilustra a necessidade de abandonar a complexidade excessiva em favor da elegância e da simplicidade.&lt;/p&gt;

&lt;p&gt;Na programação, essa mudança é ainda mais premente. Com os desafios atuais de desempenho, escalabilidade e manutenção, é vital adotarmos paradigmas que nos permitam construir softwares de alta qualidade de forma eficiente. A programação funcional oferece essa oportunidade, convidando-nos a repensar nossas práticas e abraçar uma nova forma de criar.&lt;/p&gt;

&lt;p&gt;Portanto, "sair do Barroco e vir para o Rococó" é mais do que uma simples metáfora. &lt;strong&gt;É um chamado à ação para que todos os desenvolvedores repensem suas decisões técnicas e simplifiquem, a cada dia, os sistemas e processos sob seu controle.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Ao adotar a programação funcional, podemos não apenas resolver problemas de forma mais eficaz, mas também contribuir para a construção de um futuro onde o software seja cada vez mais confiável, eficiente e elegante.&lt;/p&gt;

</description>
      <category>oop</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
