<?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>DEV Community: Richardson</title>
    <description>The latest articles on DEV Community by Richardson (@_richardson_).</description>
    <link>https://dev.to/_richardson_</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%2F434961%2Fa5b1178a-ce86-4caf-ab00-fd37720c3bce.jpg</url>
      <title>DEV Community: Richardson</title>
      <link>https://dev.to/_richardson_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_richardson_"/>
    <language>en</language>
    <item>
      <title>Estacionariedade: Por que a Média Histórica é Perigosa para suas Projeções</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Sun, 04 Jan 2026 21:18:02 +0000</pubDate>
      <link>https://dev.to/_richardson_/estacionariedade-por-que-a-media-historica-e-perigosa-para-suas-projecoes-1ko7</link>
      <guid>https://dev.to/_richardson_/estacionariedade-por-que-a-media-historica-e-perigosa-para-suas-projecoes-1ko7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Resumo:&lt;/strong&gt; Neste artigo, exploramos o conceito de estacionariedade em séries temporais, como utilizar o teste Augmented Dickey-Fuller (ADF) para diagnosticar tendências estocásticas e como configurar o parâmetro de integração (&lt;em&gt;d&lt;/em&gt;) no SARIMAX para evitar o viés da média global.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;No mundo dos negócios, existe uma atração pela "média". Quando um time de stakeholders pede uma projeção para 2026, o instinto inicial de muitos analistas é calcular a média de 2024/2025 e projetá-la à frente.&lt;/p&gt;

&lt;p&gt;Se você trabalha com séries temporais financeiras ou de e-commerce, essa abordagem geralmente está errada.&lt;/p&gt;

&lt;p&gt;O motivo é a &lt;strong&gt;Estacionariedade&lt;/strong&gt;. Se uma métrica possui uma tendência forte (seja de alta ou queda), sua média e variância não são constantes ao longo do tempo. Projetar uma média global em uma série com tendência resulta em erros grosseiros de previsão.&lt;/p&gt;

&lt;p&gt;Neste post, vou mostrar como diagnosticamos esse problema em dados de e-commerce usando o teste &lt;strong&gt;Augmented Dickey-Fuller (ADF)&lt;/strong&gt; e como corrigimos isso matematicamente no modelo &lt;strong&gt;SARIMAX&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Conceito: Raiz Unitária e Estacionariedade
&lt;/h2&gt;

&lt;p&gt;Para um modelo preditivo funcionar bem (especialmente da família ARIMA), a série precisa ser, idealmente, estacionária.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que é Estacionariedade?&lt;/strong&gt;&lt;br&gt;
Uma série é estacionária quando suas propriedades estatísticas — média, variância e autocorrelação — são constantes ao longo do tempo. Ela oscila em torno de um valor fixo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O Problema da Raiz Unitária&lt;/strong&gt;&lt;br&gt;
Muitas métricas de negócio (como Receita ou Preço) possuem o que chamamos de "Raiz Unitária". Simplificando, isso significa que a série tem uma memória forte: o valor de hoje depende fortemente do valor de ontem, mais um choque aleatório. Isso cria uma &lt;strong&gt;tendência estocástica&lt;/strong&gt; que faz a série "driftar" (derivar) para longe da média histórica.&lt;/p&gt;

&lt;p&gt;Se você tentar forçar uma média fixa em uma série com raiz unitária, seu modelo estará sempre "correndo atrás" da tendência, errando sistematicamente.&lt;/p&gt;


&lt;h2&gt;
  
  
  O Diagnóstico: Teste Augmented Dickey-Fuller (ADF)
&lt;/h2&gt;

&lt;p&gt;Não confiamos apenas no "olhômetro". Para validar se uma série é estacionária, usamos o teste ADF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como interpretar o teste:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Hipótese Nula (

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;H0H_0&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;H&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
):&lt;/strong&gt; A série tem uma raiz unitária (NÃO é estacionária).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hipótese Alternativa (
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;H1H_1&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;H&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
):&lt;/strong&gt; A série é estacionária.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Regra de Decisão:&lt;/strong&gt; Se o &lt;strong&gt;p-valor &amp;lt; 0.05&lt;/strong&gt;, rejeitamos 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;H0H_0&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;H&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 (a série é estacionária). Caso contrário, ela tem tendência e precisa de tratamento.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Exemplo Prático em Python
&lt;/h3&gt;

&lt;p&gt;Em um estudo recente, rodamos o ADF em diversas métricas usando a biblioteca &lt;code&gt;statsmodels&lt;/code&gt;. Veja o código e os resultados reais:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statsmodels.tsa.stattools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;adfuller&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;teste_estacionariedade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;adfuller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Métrica: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Estatística ADF: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p-valor: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resultado: Estacionária (d=0)&lt;/span&gt;&lt;span class="sh"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resultado: NÃO Estacionária (Requer d=1)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Exemplo de uso com dados reais do nosso dataset
# df_main é o dataframe contendo as séries temporais
&lt;/span&gt;&lt;span class="nf"&gt;teste_estacionariedade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df_main&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ticket_medio&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Ticket Médio&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;teste_estacionariedade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df_main&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sessoes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sessões&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resultados Obtidos
&lt;/h3&gt;

&lt;p&gt;Ao analisar os resultados e gráficos, encontramos dois cenários que invalidariam qualquer modelo baseado em média simples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Ticket Médio (Preço):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;p-valor:&lt;/strong&gt; 0.76&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Diagnóstico:&lt;/strong&gt; Extremamente não estacionário. A série tem uma tendência de alta clara e forte. O modelo não pode aprender o valor absoluto (ex.: R$ 1.500), pois amanhã ele será maior.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estatística ADF: -0.9714409080154276&lt;br&gt;
p-valor: 0.7635375639207811&lt;br&gt;
A série parece ser NÃO-ESTACIONÁRIA (p-valor &amp;gt;= 0.05). Falha ao rejeitar H0.&lt;br&gt;
Indício de presença de Tendência.&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%2Faxcarxbfuy8udgvi1wr4.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%2Faxcarxbfuy8udgvi1wr4.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Sessões (Tráfego):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;p-valor:&lt;/strong&gt; 0.10&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Diagnóstico:&lt;/strong&gt; Não estacionário (0.10 &amp;gt; 0.05). Há uma tendência de queda no tráfego. Uma média histórica superestimaria o tráfego futuro.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estatística ADF: -2.552426454254865&lt;br&gt;
p-valor: 0.10323368222873064&lt;br&gt;
A série parece ser NÃO-ESTACIONÁRIA (p-valor &amp;gt;= 0.05). Falha ao rejeitar H0.&lt;br&gt;
Indício de presença de Tendência.&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%2Fqo1lxq90qgo17ku85rm0.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%2Fqo1lxq90qgo17ku85rm0.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  A Solução: Diferenciação no SARIMAX
&lt;/h2&gt;

&lt;p&gt;Uma vez identificado que 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;p-valor&amp;gt;0.05p\text{-valor} &amp;gt; 0.05&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;-valor&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.05&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, a correção técnica obrigatória é a &lt;strong&gt;Diferenciação (Integration)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Em vez de modelar o valor absoluto 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;YtY_t&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;Y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, modelamos a diferença entre o valor atual e o anterior:&lt;br&gt;

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;ΔYt=Yt−Yt−1\Delta Y_t = Y_t - Y_{t-1}&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;Δ&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;Y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;Y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;Y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;t&lt;/span&gt;&lt;span class="mbin mtight"&gt;−&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
&lt;/p&gt;

&lt;p&gt;Isso transforma uma série com tendência em uma série estacionária de "variações".&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementação no SARIMAX
&lt;/h3&gt;

&lt;p&gt;No Python, você não precisa criar a coluna de diferença manualmente. O algoritmo &lt;strong&gt;SARIMAX&lt;/strong&gt; possui o parâmetro &lt;code&gt;order=(p,d,q)&lt;/code&gt;, onde &lt;strong&gt;&lt;code&gt;d&lt;/code&gt;&lt;/strong&gt; é a ordem de integração.&lt;/p&gt;

&lt;p&gt;Baseados nos testes ADF acima, definimos a estratégia de modelagem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statsmodels.tsa.statespace.sarimax&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SARIMAX&lt;/span&gt;

&lt;span class="c1"&gt;# Para Ticket Médio (Não-Estacionário, p-valor=0.76)
# Usamos d=1. O modelo aprende a taxa de crescimento, não o valor fixo.
&lt;/span&gt;&lt;span class="n"&gt;model_ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SARIMAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df_main&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ticket_medio&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;1&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;# (AR, Integração, MA)
&lt;/span&gt;    &lt;span class="n"&gt;seasonal_order&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;1&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="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Sazonalidade anual
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Para Transações (Estacionário mas com queda estrutural)
# Mesmo que o ADF indique estacionariedade (p &amp;lt; 0.05),
# forçamos d=1 para capturar a 'inércia' da queda recente,
# evitando reversão à média antiga.
&lt;/span&gt;&lt;span class="n"&gt;model_transacoes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SARIMAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df_main&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;transacoes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;1&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="n"&gt;seasonal_order&lt;/span&gt;&lt;span class="o"&gt;=&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="mi"&gt;1&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="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model_fit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_fit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;O impacto no negócio:&lt;/strong&gt;&lt;br&gt;
Ao usar &lt;code&gt;d=1&lt;/code&gt;, o modelo entendeu que o Ticket Médio estava subindo cerca de R$ 10,00 por semana e projetou essa continuidade para 2026. Se tivéssemos usado &lt;code&gt;d=0&lt;/code&gt; (ou uma média simples), a projeção teria achatado a curva, subestimando a receita futura e ignorando a inflação de preços interna da loja.&lt;/p&gt;




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

&lt;p&gt;Não confie cegamente na média histórica. Séries temporais de negócios raramente ficam paradas.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Rode o teste ADF:&lt;/strong&gt; É a maneira estatisticamente robusta de verificar se sua métrica está "presa" a uma média ou se está derivando.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Olhe o p-valor:&lt;/strong&gt; Se 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&amp;gt;0.05&amp;gt; 0.05&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.05&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, sua série não é estacionária.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Use Diferenciação (
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;d=1d=1&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;d&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
):&lt;/strong&gt; Configure seu modelo ARIMA/SARIMAX para projetar a variação, capturando a tendência real.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fazer ciência de dados aplicada é garantir que a matemática do modelo reflita a realidade econômica do negócio. Ignorar a não-estacionariedade é planejar o futuro olhando para uma foto estática do passado.&lt;/p&gt;




&lt;h3&gt;
  
  
  Referências e Bibliografia
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Introduction to Time Series and Forecasting&lt;/em&gt; (Brockwell &amp;amp; Davis) - Para fundamentos matemáticos de estacionariedade.&lt;/li&gt;
&lt;li&gt;  Documentação Statsmodels (Augmented Dickey-Fuller).&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Practical Statistics for Data Scientists&lt;/em&gt; (Bruce &amp;amp; Bruce) - Para aplicação prática de conceitos estatísticos em DS.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>datascience</category>
    </item>
    <item>
      <title>Regressão Linear para Inferência Causal: Indo Além da Predição</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Tue, 30 Dec 2025 17:44:11 +0000</pubDate>
      <link>https://dev.to/_richardson_/regressao-linear-para-inferencia-causal-indo-alem-da-predicao-81b</link>
      <guid>https://dev.to/_richardson_/regressao-linear-para-inferencia-causal-indo-alem-da-predicao-81b</guid>
      <description>&lt;p&gt;Na engenharia de dados, o foco costuma residir na integridade e velocidade do pipeline. No entanto, ao transitar para a modelagem, é fundamental compreender que a Regressão Linear — frequentemente reduzida à fórmula  — possui aplicações que extrapolam a simples previsão de valores. Enquanto o aprendizado de máquina convencional prioriza a predição (), a regressão é uma ferramenta de &lt;strong&gt;inferência causal&lt;/strong&gt;, capaz de isolar o impacto de variáveis específicas () sobre um resultado, controlando o ruído estatístico.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Diferença entre Predição e Inferência
&lt;/h3&gt;

&lt;p&gt;Em modelos de predição, o objetivo é minimizar o erro (como o RMSE) entre o valor real e a estimativa . O modelo pode ser uma "caixa preta", desde que a acurácia seja alta.&lt;/p&gt;

&lt;p&gt;Na &lt;strong&gt;Inferência Causal&lt;/strong&gt;, o interesse reside nos &lt;strong&gt;coeficientes de regressão&lt;/strong&gt; (). O objetivo é mensurar como  se altera quando uma variável  é modificada, mantendo todos os outros fatores constantes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Estudo de Caso: Qualidade de Cadastro no E-commerce
&lt;/h3&gt;

&lt;p&gt;Recentemente fiz um estudo sobre o impacto da qualidade do cadastro na conversão de vendas que ilustra essa aplicação. Em vez de apenas prever vendas, a regressão foi utilizada como diagnóstico de negócio.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discretização (Variáveis Dummy):&lt;/strong&gt; A análise exploratória revelou que o ganho de conversão não era linear, mas ocorria em "degraus". Foi criada uma variável binária &lt;code&gt;is_score_elite&lt;/code&gt;, onde 1 representa score  80 e 0 para valores menores.&lt;/li&gt;
&lt;/ul&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%2Fmi7d415avjxq2l2ovbd0.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%2Fmi7d415avjxq2l2ovbd0.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Controle de Variáveis:&lt;/strong&gt; Para evitar que o efeito do preço ou do frete mascarasse o impacto da qualidade, utilizou-se a Regressão Múltipla:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resultados e Métricas:&lt;/strong&gt; O modelo apresentou um  de apenas 0,024 (2,4%). Para predição, esse valor seria insuficiente, mas para inferência, ele foi aceitável, pois o objetivo era isolar o coeficiente . O resultado indicou um ganho de conversão de 10,3% atribuído puramente à qualidade do cadastro.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Outras Aplicações Técnicas
&lt;/h3&gt;

&lt;p&gt;A regressão atua como um "bisturi estatístico" em diversos domínios onde experimentos controlados (Testes A/B) são difíceis ou impossíveis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Marketing e Influência Social:&lt;/strong&gt; A técnica permite distinguir &lt;strong&gt;homofilia&lt;/strong&gt; (conexão entre pessoas similares) de &lt;strong&gt;influência&lt;/strong&gt; real. Ao controlar características demográficas e gostos prévios, a regressão revela se uma compra ocorreu devido à influência de um terceiro ou apenas por afinidade pré-existente entre os indivíduos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setor Imobiliário (Precificação Hedônica):&lt;/strong&gt; Para determinar o valor causal de um cômodo adicional, a regressão múltipla isola variáveis de confusão, como a localização. Sem esse controle, modelos simples podem indicar erroneamente que mais quartos diminuem o valor da casa, apenas porque casas maiores em áreas rurais são mais baratas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Saúde Pública:&lt;/strong&gt; Quando não é ético realizar experimentos, a regressão múltipla fixa variáveis como idade e histórico médico para identificar o impacto isolado de um medicamento ou hábito sobre a saúde, mitigando fenômenos como o Paradoxo de Simpson.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Limitações e Boas Práticas
&lt;/h3&gt;

&lt;p&gt;Para garantir a validade da inferência, o engenheiro de dados deve estar atento a:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Variáveis Omitidas:&lt;/strong&gt; A ausência de uma variável importante pode inflar artificialmente os coeficientes de outras variáveis, gerando viés.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Correlação vs. Causalidade:&lt;/strong&gt; A matemática identifica associações; o conhecimento de domínio é necessário para interpretar a causalidade.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extrapolação:&lt;/strong&gt; Modelos de regressão perdem a validade quando aplicados a intervalos de dados fora do conjunto de treinamento.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Referências Bibliográficas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;PROVOST, Foster; FAWCETT, Tom. &lt;strong&gt;Data Science for Business&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SCHUTT, Rachel; O'NEIL, Cathy. &lt;strong&gt;Doing Data Science&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BRUCE, Peter; BRUCE, Andrew. &lt;strong&gt;Practical Statistics for Data Scientists&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>datascience</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Guia arquitetônico de ponta para a construção de uma plataforma de dados</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Sun, 12 Oct 2025 03:16:17 +0000</pubDate>
      <link>https://dev.to/_richardson_/guia-arquitetonico-de-ponta-para-a-construcao-de-uma-plataforma-de-dados-2h54</link>
      <guid>https://dev.to/_richardson_/guia-arquitetonico-de-ponta-para-a-construcao-de-uma-plataforma-de-dados-2h54</guid>
      <description>&lt;h3&gt;
  
  
  Etapa 1: A Conexão Fundamental - Modelo Lógico/Físico e a Arquitetura Medalhão
&lt;/h3&gt;

&lt;p&gt;A Arquitetura Medalhão é a estrutura que nos permite aplicar a modelagem de dados de forma estratégica, conectando o caos da origem à clareza do consumo. A separação entre o modelo lógico e o físico se manifesta de forma clara no fluxo entre as camadas.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Camada Bronze para Prata (Foco na Criação do Modelo Lógico Normalizado):&lt;/strong&gt; A transformação fundamental aqui é a &lt;strong&gt;imposição de sentido, integridade e governança&lt;/strong&gt;. Pegamos dados brutos (um modelo físico simples, mas logicamente caótico) e os forjamos em um &lt;strong&gt;modelo lógico e normalizado&lt;/strong&gt; que representa as entidades e processos de negócio de forma clara e consistente. Metodologias baseadas em &lt;strong&gt;Modelagem de Entidade-Relacionamento (ER)&lt;/strong&gt; ou &lt;strong&gt;Terceira Forma Normal (3FN)&lt;/strong&gt; são ideais para essa camada de integração, visando a &lt;strong&gt;verdade semântica&lt;/strong&gt;, a integridade e a redução da redundância de dados.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Camada Prata para Ouro (Foco na Otimização do Modelo Físico Desnormalizado):&lt;/strong&gt; Com a verdade lógica já estabelecida na camada Prata, o foco muda completamente para a &lt;strong&gt;entrega de performance e simplicidade para o usuário final&lt;/strong&gt;. Pegamos o modelo lógico normalizado (que é ótimo para integridade, mas ruim para performance de BI devido aos &lt;code&gt;JOINs&lt;/code&gt;) e criamos uma &lt;strong&gt;representação física otimizada&lt;/strong&gt; para casos de uso específicos. Aqui aplicamos estratégias de &lt;strong&gt;desnormalização&lt;/strong&gt;, cuja manifestação mais comum é a &lt;strong&gt;Modelagem Dimensional (Star Schema)&lt;/strong&gt; ou, em sua forma mais extrema, a &lt;strong&gt;One Big Table (OBT)&lt;/strong&gt;. O sucesso dessa estratégia é garantido pela natureza do &lt;strong&gt;armazenamento colunar&lt;/strong&gt; do BigQuery, que assegura que as consultas leiam apenas os dados relevantes, superando a ineficiência dos &lt;code&gt;JOINs&lt;/code&gt; para o consumidor final.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Etapa 2: A Estrutura Detalhada das Camadas no Google Cloud
&lt;/h3&gt;

&lt;p&gt;Apresento a estrutura detalhada revisada, incorporando as melhores práticas e as nuances discutidas.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Camada Bronze: O Data Lake Bruto e Imutável&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Categoria&lt;/th&gt;
&lt;th&gt;Detalhes e Conceitos&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Propósito Principal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ingestão e persistência de dados brutos, imutáveis e históricos. É o "backup" auditável da realidade da fonte.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstração Envolvida&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"Aterrissagem de Dados" (Data Landing Zone)&lt;/strong&gt;. É um repositório que aceita dados em qualquer formato e velocidade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Estrutura (Modelo)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Schema-on-Read&lt;/strong&gt; O &lt;strong&gt;modelo físico&lt;/strong&gt; é simples: uma linha por registro. O &lt;strong&gt;modelo lógico&lt;/strong&gt; é indefinido.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metodologias e Padrões&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Ingestão Imutável:&lt;/strong&gt; Nunca alterar um dado na Bronze.• &lt;strong&gt;Ingestão Desacoplada (CDC/Streaming):&lt;/strong&gt; Prioriza a replicação de logs (via Datastream) ou a captura de eventos (via Pub/Sub) para minimizar o impacto nos sistemas de origem (OLTP).&lt;br&gt;• &lt;strong&gt;Formatos de Arquivo Otimizados:&lt;/strong&gt; Priorizar formatos colunares como &lt;strong&gt;Apache Parquet&lt;/strong&gt; ou baseados em esquema como &lt;strong&gt;Apache Avro&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design Partners&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Engenheiros de Dados:&lt;/strong&gt; Construtores dos pipelines.• &lt;strong&gt;Auditores e Equipes de Conformidade:&lt;/strong&gt; Utilizam a Bronze para rastrear a linhagem.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tecnologias Google Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Cloud Storage (GCS):&lt;/strong&gt; O repositório primário e ideal para o Data Lake Bruto.• &lt;strong&gt;BigQuery:&lt;/strong&gt; Atua como componente de apoio (sink para streaming/CDC ou motor de consulta via &lt;strong&gt;tabelas externas&lt;/strong&gt;).• &lt;strong&gt;Dataplex:&lt;/strong&gt; Para &lt;strong&gt;catalogação de dados&lt;/strong&gt;, descoberta de metadados e governança centralizada desde a ingestão.&lt;br&gt;• &lt;strong&gt;Pub/Sub, Datastream:&lt;/strong&gt; Serviços de ingestão (o "E" e "L" do ELT).• &lt;strong&gt;Cloud Composer (Airflow):&lt;/strong&gt; Orquestrador principal para agendar e gerenciar o fluxo de ingestão.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desafios Comuns&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Schema Drift:&lt;/strong&gt; A estrutura dos dados na fonte muda.• &lt;strong&gt;Governança (Data Swamp):&lt;/strong&gt; Risco de se tornar um "pântano de dados". Mitigado pela catalogação proativa com &lt;strong&gt;Dataplex&lt;/strong&gt; para garantir linhagem, documentação e detecção de PII (via integração com DLP).• &lt;strong&gt;Gerenciamento de Custos:&lt;/strong&gt; Implementar &lt;strong&gt;políticas de ciclo de vida (Lifecycle Management)&lt;/strong&gt; no GCS para mover dados para classes de armazenamento mais frias.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Exemplo Prático&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Arquivos Avro armazenados no GCS em &lt;code&gt;gs://ecommerce-bronze/...&lt;/code&gt;, com metadados gerenciados pelo Dataplex.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Camada Prata: A Fonte da Verdade Normalizada e Confiável&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Categoria&lt;/th&gt;
&lt;th&gt;Detalhes e Conceitos&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Propósito Principal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limpar, validar, enriquecer e &lt;strong&gt;integrar&lt;/strong&gt; os dados brutos. É a &lt;strong&gt;"Fonte Única da Verdade" (SSOT)&lt;/strong&gt; e o local de implementação da &lt;strong&gt;governança de dados&lt;/strong&gt; de baseline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstração Envolvida&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"Hub de Integração" (Integration Hub)&lt;/strong&gt; Aqui criamos um modelo de dados corporativo consistente.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Estrutura (Modelo)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Modelo Lógico Normalizado (3FN, Modelo ER).&lt;/strong&gt; A prioridade é a integridade. O &lt;strong&gt;modelo físico&lt;/strong&gt; é otimizado com &lt;strong&gt;Particionamento e Clustering&lt;/strong&gt; para acelerar as operações de integração.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metodologias e Padrões&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Modelagem de Entidade-Relacionamento (ER) / 3FN:&lt;/strong&gt; Para garantir a integridade.• &lt;strong&gt;Data Vault:&lt;/strong&gt; Metodologia robusta para ambientes com alta variação de esquema.• &lt;strong&gt;Regras de Qualidade de Dados (DQ):&lt;/strong&gt; Implementação de testes automatizados.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design Partners&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Engenheiros de Dados:&lt;/strong&gt; Construtores do modelo.• &lt;strong&gt;Analistas e Cientistas de Dados:&lt;/strong&gt; Consumidores para explorações profundas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tecnologias Google Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;BigQuery:&lt;/strong&gt; O coração da camada Prata, executando as transformações.• &lt;strong&gt;Dataform / dbt:&lt;/strong&gt; Ferramentas para orquestrar as transformações SQL-first e &lt;strong&gt;injetar testes de validação (DQ)&lt;/strong&gt;.• &lt;strong&gt;Cloud Composer (Airflow):&lt;/strong&gt; Orquestra o pipeline ponta-a-ponta, acionando os jobs do Dataform/dbt.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desafios Comuns&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Lógica de Negócio Complexa:&lt;/strong&gt; Traduzir regras de negócio em um modelo normalizado.• &lt;strong&gt;Custo e Complexidade dos &lt;code&gt;JOINs&lt;/code&gt;:&lt;/strong&gt; A normalização exige &lt;code&gt;JOINs&lt;/code&gt; computacionalmente caros.• &lt;strong&gt;Manutenção do Modelo:&lt;/strong&gt; Atualizar o modelo à medida que o negócio evolui.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Exemplo Prático&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Um conjunto de tabelas normalizadas em &lt;code&gt;ecommerce_silver&lt;/code&gt;: &lt;code&gt;clientes&lt;/code&gt;, &lt;code&gt;produtos&lt;/code&gt;, &lt;code&gt;pedidos&lt;/code&gt;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Camada Ouro: Os Produtos de Dados Focados no Negócio&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Categoria&lt;/th&gt;
&lt;th&gt;Detalhes e Conceitos&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Propósito Principal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fornecer dados prontos para consumo, agregados, &lt;strong&gt;desnormalizados&lt;/strong&gt; e otimizados para casos de uso específicos com máxima performance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstração Envolvida&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"Produto de Dados" (Data Product)&lt;/strong&gt; Cada tabela na Ouro é um produto curado, que inclui dados, metadados, governança e testes, tornando-o &lt;strong&gt;self-service e confiável&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Estrutura (Modelo)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Modelo Físico Desnormalizado e Otimizado.&lt;/strong&gt; O objetivo é eliminar &lt;code&gt;JOINs&lt;/code&gt; em tempo de consulta. A estrutura utiliza &lt;strong&gt;Particionamento e Clustering&lt;/strong&gt; e recursos nativos como &lt;strong&gt;STRUCTs e ARRAYs&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metodologias e Padrões&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Modelagem Dimensional (Star Schema):&lt;/strong&gt; Padrão para data marts de BI.&lt;br&gt;• &lt;strong&gt;Criação de Tabelas Amplas (OBT):&lt;/strong&gt; Para dashboards de alta performance.• &lt;strong&gt;MLOps (Feature Engineering):&lt;/strong&gt; Orquestração do ciclo de vida de modelos de ML, desde a featurização até o treinamento e a predição.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design Partners&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Analistas de Negócio, Executivos (via dashboards):&lt;/strong&gt; Consumidores finais.• &lt;strong&gt;Aplicações e APIs:&lt;/strong&gt; Consomem dados da camada Ouro.• &lt;strong&gt;Engenheiros de ML:&lt;/strong&gt; Utilizam o Ouro para MLOps.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tecnologias Google Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;BigQuery:&lt;/strong&gt; O motor de serviço serverless perfeito.&lt;br&gt;• &lt;strong&gt;Looker / Looker Studio:&lt;/strong&gt; Ferramentas de BI que se conectam à camada Ouro.• &lt;strong&gt;Vertex AI (incluindo Pipelines):&lt;/strong&gt; Consome tabelas da Ouro para MLOps. &lt;strong&gt;Vertex AI Pipelines&lt;/strong&gt; orquestra o ciclo de vida do ML.• &lt;strong&gt;Cloud Composer (Airflow):&lt;/strong&gt; Orquestra a execução dos jobs que atualizam esta camada.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desafios Comuns&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;strong&gt;Explosão de Marts:&lt;/strong&gt; Criar dezenas de tabelas Ouro sem governança, levando à inconsistência.• &lt;strong&gt;Balanceamento da Granularidade:&lt;/strong&gt; Decidir a agregação correta.• &lt;strong&gt;Custo Computacional Alto:&lt;/strong&gt; A construção da camada Ouro envolve &lt;code&gt;JOINs&lt;/code&gt; e agregações custosas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Exemplo Prático&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;• &lt;code&gt;ecommerce_gold.dm_vendas&lt;/code&gt;: Um &lt;strong&gt;Star Schema&lt;/strong&gt; para análise de BI.&lt;br&gt;• &lt;code&gt;ml_gold.customer_features&lt;/code&gt;: Uma tabela de features para um modelo de churn.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Etapa 3: Práticas Transversais Essenciais
&lt;/h3&gt;

&lt;p&gt;Além da estrutura em camadas, práticas de engenharia de ponta são cruciais para o sucesso da plataforma.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Governança Proativa com Catálogo de Dados:&lt;/strong&gt; Para mitigar o risco de "pântano de dados", a integração com o &lt;strong&gt;Dataplex&lt;/strong&gt; deve ser implementada desde a camada Bronze. Isso garante que metadados, linhagem de dados e detecção de PII (via integração com o Cloud DLP) sejam implementados desde o início, aumentando a governança e a capacidade de descoberta de todos os ativos de dados.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gestão Estratégica de Custos no BigQuery:&lt;/strong&gt; O controle de custos vai além do design das tabelas. É fundamental utilizar features específicas do BigQuery para otimizar o processamento, como a alocação de capacidade com &lt;strong&gt;BigQuery Editions/Reservations&lt;/strong&gt; para cargas de trabalho previsíveis e o uso de &lt;strong&gt;&lt;code&gt;dry-run&lt;/code&gt;&lt;/strong&gt; (simulação) para estimar o volume de dados lidos antes da execução de consultas complexas.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Etapa 4: A Síntese Revisada - O Fluxo de Valor Orquestrado
&lt;/h3&gt;

&lt;p&gt;A jornada de um dado através desta arquitetura representa um fluxo contínuo de agregação de valor, orquestrado de ponta a ponta:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Um pipeline de dados, orquestrado pelo &lt;strong&gt;Cloud Composer&lt;/strong&gt;, é iniciado. Ele usa o &lt;strong&gt;Datastream&lt;/strong&gt; para capturar mudanças e aterrissá-las como arquivos &lt;strong&gt;Avro&lt;/strong&gt; no &lt;strong&gt;Cloud Storage&lt;/strong&gt; (Camada &lt;strong&gt;Bronze&lt;/strong&gt;), com seus metadados sendo registrados no &lt;strong&gt;Dataplex&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; O Composer aciona um job do &lt;strong&gt;Dataform&lt;/strong&gt;. Este job lê os arquivos da Bronze, executa SQLs que limpam, validam, aplicam testes de qualidade (DQ) e inserem os dados em um &lt;strong&gt;modelo normalizado (3FN)&lt;/strong&gt; na camada &lt;strong&gt;Prata&lt;/strong&gt; do &lt;strong&gt;BigQuery&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Com a conclusão da Prata, o Composer aciona um segundo job do &lt;strong&gt;Dataform&lt;/strong&gt; que lê as tabelas normalizadas, &lt;strong&gt;executa os &lt;code&gt;JOINs&lt;/code&gt; e agregações necessários para desnormalizar os dados&lt;/strong&gt;, e constrói os "Produtos de Dados" (ex.: um &lt;strong&gt;Star Schema&lt;/strong&gt;) na camada &lt;strong&gt;Ouro&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Em paralelo, o Composer pode acionar um pipeline do &lt;strong&gt;Vertex AI&lt;/strong&gt;, que consome uma tabela da camada Ouro para retreinar um modelo de ML, orquestrando todo o ciclo de vida do MLOps.&lt;/li&gt;
&lt;li&gt; Finalmente, o &lt;strong&gt;Looker&lt;/strong&gt; se conecta aos Produtos de Dados da camada Ouro, permitindo que os usuários de negócio explorem informações atualizadas e confiáveis com altíssima performance.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dataengineering</category>
      <category>data</category>
      <category>architecture</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Otimizando Redshift na Prática: Um Estudo de Caso com DISTKEY e SORTKEY</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Wed, 08 Oct 2025 01:07:34 +0000</pubDate>
      <link>https://dev.to/_richardson_/otimizando-redshift-na-pratica-um-estudo-de-caso-com-distkey-e-sortkey-53ao</link>
      <guid>https://dev.to/_richardson_/otimizando-redshift-na-pratica-um-estudo-de-caso-com-distkey-e-sortkey-53ao</guid>
      <description>&lt;p&gt;Neste guia, vamos otimizar uma tabela no Amazon Redshift do zero. Analisaremos o padrão de consulta, usaremos a view &lt;code&gt;svv_table_info&lt;/code&gt; para diagnosticar problemas e aplicaremos as &lt;code&gt;DISTKEY&lt;/code&gt; e &lt;code&gt;SORTKEY&lt;/code&gt; corretas para transformar a performance.&lt;/p&gt;

&lt;p&gt;O Amazon Redshift é um data warehouse com arquitetura MPP (Massively Parallel Processing). Em resumo, dados e processamento são divididos entre múltiplos nós. Para extrair a performance máxima, precisamos instruir o Redshift sobre como distribuir (&lt;code&gt;DISTKEY&lt;/code&gt;) e ordenar (&lt;code&gt;SORTKEY&lt;/code&gt;) os dados de forma inteligente.&lt;/p&gt;

&lt;p&gt;Embora o Redshift ofereça otimizações automáticas (&lt;code&gt;AUTO&lt;/code&gt;), elas são um ponto de partida genérico. A otimização manual, baseada em padrões de consulta conhecidos, é o que realmente faz a diferença.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parte 1: Conceitos Fundamentais
&lt;/h2&gt;

&lt;p&gt;Antes de otimizar, precisamos dominar duas ferramentas.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Chaves de Distribuição (DISTKEY)
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;DISTKEY&lt;/code&gt; define como as linhas de uma tabela são distribuídas entre os nós do cluster. O objetivo é minimizar a movimentação de dados pela rede (&lt;code&gt;data shuffling&lt;/code&gt;) durante a execução de uma query.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DISTSTYLE KEY(coluna)&lt;/code&gt;: Linhas com o mesmo valor na &lt;code&gt;coluna&lt;/code&gt; da &lt;code&gt;DISTKEY&lt;/code&gt; são armazenadas no mesmo nó. Ideal para colunas com alta cardinalidade usadas em &lt;code&gt;JOIN&lt;/code&gt;s ou &lt;code&gt;GROUP BY&lt;/code&gt;, pois a operação ocorre localmente em cada nó.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DISTSTYLE ALL&lt;/code&gt;: Uma cópia completa da tabela é armazenada em cada nó. Use apenas para tabelas pequenas e de baixa frequência de atualização (ex: tabelas de dimensão &amp;lt; 3 milhões de linhas) que são frequentemente usadas em &lt;code&gt;JOIN&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DISTSTYLE EVEN&lt;/code&gt;: Os dados são distribuídos em round-robin. É o padrão, mas raramente a melhor escolha para tabelas grandes, pois não otimiza a co-localização de dados para &lt;code&gt;JOIN&lt;/code&gt;s.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  B. Chaves de Ordenação (SORTKEY)
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;SORTKEY&lt;/code&gt; define a ordem física em que as linhas são armazenadas nos blocos de 1MB em disco. O objetivo é minimizar a quantidade de dados lidos (I/O).&lt;/p&gt;

&lt;p&gt;O Redshift mantém metadados (Zone Maps) que registram os valores mínimo e máximo de cada bloco. Se uma query filtra por uma coluna da &lt;code&gt;SORTKEY&lt;/code&gt; (ex: &lt;code&gt;WHERE data &amp;gt; '2025-10-01'&lt;/code&gt;), o Redshift usa os Zone Maps para ler apenas os blocos que podem conter esses dados, ignorando o resto.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COMPOUND SORTKEY(col1, col2, ...)&lt;/code&gt;: Ordena os dados estritamente na ordem das colunas listadas. É extremamente eficiente quando os filtros usam um prefixo da chave (principalmente a &lt;code&gt;col1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INTERLEAVED SORTKEY(col1, col2, ...)&lt;/code&gt;: Dá peso igual a todas as colunas na chave. É útil quando os filtros são imprevisíveis, mas tem um custo maior de &lt;code&gt;VACUUM&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Parte 2: A Ferramenta de Diagnóstico: &lt;code&gt;svv_table_info&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Esta view do sistema é o painel de saúde de qualquer tabela. Para usá-la, rode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diststyle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skew_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sortkey1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unsorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats_off&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;svv_table_info&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"schema"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'seu_schema'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nv"&gt;"table"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sua_tabela'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Abaixo, os campos essenciais e como interpretá-los:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Campo Relevante&lt;/th&gt;
&lt;th&gt;O que é?&lt;/th&gt;
&lt;th&gt;Como Interpretar e Agir?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;diststyle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O estilo de distribuição atual.&lt;/td&gt;
&lt;td&gt;Confirma se sua &lt;code&gt;DISTKEY&lt;/code&gt; foi aplicada. Se for &lt;code&gt;EVEN&lt;/code&gt; em uma tabela grande, é um forte candidato à otimização.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skew_rows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A distorção na distribuição. Razão entre o maior e o médio slice.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1.0&lt;/code&gt; é perfeito. Valores altos (&lt;code&gt;&amp;gt; 4.0&lt;/code&gt;) indicam "skew", onde um nó está sobrecarregado. A &lt;code&gt;DISTKEY&lt;/code&gt; pode estar mal escolhida.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sortkey1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A primeira coluna da sua &lt;code&gt;SORTKEY&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Essencial para garantir que a coluna mais filtrada em ranges (como datas) esteja aqui.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unsorted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O percentual de dados não ordenado.&lt;/td&gt;
&lt;td&gt;O objetivo é &lt;code&gt;0.00&lt;/code&gt;. Um valor alto anula os benefícios da &lt;code&gt;SORTKEY&lt;/code&gt;. &lt;strong&gt;Ação:&lt;/strong&gt; &lt;code&gt;VACUUM SORT sua_tabela;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stats_off&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O percentual de "desatualização" das estatísticas.&lt;/td&gt;
&lt;td&gt;O objetivo é &lt;code&gt;0.00&lt;/code&gt;. Um valor alto pode gerar planos de execução ineficientes. &lt;strong&gt;Ação:&lt;/strong&gt; &lt;code&gt;ANALYZE sua_tabela;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Parte 3: O Ciclo de Otimização - Estudo de Caso
&lt;/h2&gt;

&lt;p&gt;Vamos aplicar os conceitos à tabela &lt;code&gt;agg_cotacoes_produto_fornecedor_dia&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Etapa 1: Análise do Padrão de Consulta
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problema:&lt;/strong&gt; A tabela é a fonte de dashboards no Looker, e as consultas estão lentas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Padrão Identificado:&lt;/strong&gt; Os filtros dos dashboards são quase sempre por &lt;code&gt;id_fornecedor&lt;/code&gt; e por um range de &lt;code&gt;data_cotacao&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Etapa 2: Diagnóstico da Tabela Original
&lt;/h3&gt;

&lt;p&gt;Uma consulta na &lt;code&gt;svv_table_info&lt;/code&gt; revelou:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;diststyle&lt;/code&gt;: &lt;code&gt;AUTO(KEY(id_produto))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sortkey1&lt;/code&gt;: &lt;code&gt;AUTO(SORTKEY)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conclusão do Diagnóstico:&lt;/strong&gt; A otimização automática do Redshift escolheu &lt;code&gt;id_produto&lt;/code&gt;, provavelmente para otimizar &lt;code&gt;JOIN&lt;/code&gt;s com uma tabela de produtos. No entanto, essa escolha não atende ao nosso padrão principal de filtro (&lt;code&gt;id_fornecedor&lt;/code&gt; e &lt;code&gt;data_cotacao&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Etapa 3: Definição da Estratégia de Otimização
&lt;/h3&gt;

&lt;p&gt;Baseado no padrão de consulta, definimos a nova estratégia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DISTKEY(id_fornecedor)&lt;/code&gt;&lt;/strong&gt;: Como os filtros são por fornecedor, isso vai co-localizar os dados necessários em um mesmo nó, acelerando filtros e agregações.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;COMPOUND SORTKEY(data_cotacao, id_fornecedor)&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;data_cotacao&lt;/code&gt; é a coluna mais filtrada em ranges, então deve vir primeiro na &lt;code&gt;SORTKEY&lt;/code&gt;. Isso permitirá ao Redshift pular blocos de dados massivamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Etapa 4: Implementação (Processo "Deep Copy")
&lt;/h3&gt;

&lt;p&gt;Para aplicar as mudanças em uma tabela populada, o processo mais seguro é criar uma nova tabela otimizada e depois trocá-las de lugar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Criação (CTAS):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia_novo&lt;/span&gt; 
&lt;span class="n"&gt;DISTKEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id_fornecedor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;COMPOUND&lt;/span&gt; &lt;span class="n"&gt;SORTKEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_cotacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_fornecedor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; 
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Manutenção Pós-Criação:&lt;/strong&gt;&lt;br&gt;
A &lt;code&gt;svv_table_info&lt;/code&gt; da nova tabela mostrou &lt;code&gt;unsorted&lt;/code&gt; e &lt;code&gt;stats_off&lt;/code&gt; altos, o que é esperado após uma carga massiva.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;VACUUM&lt;/span&gt; &lt;span class="n"&gt;SORT&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia_novo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia_novo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Validação Final:&lt;/strong&gt;&lt;br&gt;
Verificamos a &lt;code&gt;svv_table_info&lt;/code&gt; novamente. Agora, &lt;code&gt;unsorted: 0.00&lt;/code&gt; e &lt;code&gt;stats_off: 0.00&lt;/code&gt;. A tabela está pronta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A Troca Atômica:&lt;/strong&gt;&lt;br&gt;
Executamos a troca de nomes dentro de uma transação para garantir que a operação seja instantânea e segura, sem downtime para os usuários.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia&lt;/span&gt; &lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia_old&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia_novo&lt;/span&gt; &lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;agg_cotacoes_produto_fornecedor_dia&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;O ciclo de otimização no Redshift é contínuo e baseado em dados:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Analisar Padrão de Query&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Diagnosticar com &lt;code&gt;svv_table_info&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Definir Estratégia (DISTKEY, SORTKEY)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Implementar e Validar&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ao dominar esses conceitos, você sai do modo "automático" e passa a ter controle total sobre a performance do seu data warehouse, transformando tabelas lentas em fontes de dados eficientes para qualquer ferramenta de BI como o Looker.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>redshift</category>
      <category>dataengineering</category>
      <category>database</category>
    </item>
    <item>
      <title>Modelagem de Dados para Análise de Funis no Amazon Redshift</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Sat, 27 Sep 2025 01:43:59 +0000</pubDate>
      <link>https://dev.to/_richardson_/modelagem-de-dados-para-analise-de-funis-no-amazon-redshift-120n</link>
      <guid>https://dev.to/_richardson_/modelagem-de-dados-para-analise-de-funis-no-amazon-redshift-120n</guid>
      <description>&lt;p&gt;Como engenheiros de dados, frequentemente enfrentamos o desafio de transformar logs transacionais—sequências longas de eventos—em uma visão consolidada que permita análises de negócio. Um caso clássico é a modelagem de um funil de conversão, como o rastreamento do ciclo de vida de tickets de suporte em um sistema de atendimento ao cliente.&lt;/p&gt;

&lt;p&gt;Neste post, vamos explorar as decisões de arquitetura e as técnicas de SQL para construir uma Tabela Analítica eficiente para esse cenário no Amazon Redshift, comparando duas abordagens de modelagem (One Big Table vs. Star Schema) e três técnicas de pivotagem de dados.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Cenário: Funil de Tickets de Suporte
&lt;/h3&gt;

&lt;p&gt;Imagine uma tabela de logs, &lt;code&gt;suporte_ticket_eventos&lt;/code&gt;, com milhões de linhas e a seguinte estrutura:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id_evento&lt;/th&gt;
&lt;th&gt;id_ticket&lt;/th&gt;
&lt;th&gt;status_novo&lt;/th&gt;
&lt;th&gt;timestamp&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;901&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;Aberto&lt;/td&gt;
&lt;td&gt;2025-09-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;902&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;Aberto&lt;/td&gt;
&lt;td&gt;2025-09-20 10:05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;905&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;Em Análise&lt;/td&gt;
&lt;td&gt;2025-09-20 11:30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;908&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;Resolvido&lt;/td&gt;
&lt;td&gt;2025-09-21 14:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;909&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;Resolvido&lt;/td&gt;
&lt;td&gt;2025-09-20 18:45&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O objetivo de negócio é responder a perguntas como: "Qual o tempo médio entre a abertura e a resolução de um ticket?". Para isso, precisamos de uma tabela final com uma linha por ticket, contendo as datas de cada marco importante.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decisão 1: A Arquitetura da Tabela - OBT vs. Star Schema
&lt;/h3&gt;

&lt;p&gt;A primeira grande decisão é como estruturar nossa tabela analítica final.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;One Big Table (OBT) / Desnormalizada&lt;/th&gt;
&lt;th&gt;Star Schema (Esquema Estrela)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Estrutura&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tabela única e larga com &lt;code&gt;id_ticket&lt;/code&gt;, &lt;code&gt;data_abertura&lt;/code&gt;, &lt;code&gt;data_analise&lt;/code&gt;, &lt;code&gt;data_resolucao&lt;/code&gt;, &lt;code&gt;nome_cliente&lt;/code&gt;, &lt;code&gt;categoria_produto&lt;/code&gt;, etc.&lt;/td&gt;
&lt;td&gt;Tabela &lt;strong&gt;Fato&lt;/strong&gt; (&lt;code&gt;fato_tickets&lt;/code&gt;) com &lt;code&gt;id_ticket&lt;/code&gt;, &lt;code&gt;tempo_resolucao_horas&lt;/code&gt; e chaves para as &lt;strong&gt;Dimensões&lt;/strong&gt; &lt;code&gt;dim_cliente&lt;/code&gt; e &lt;code&gt;dim_produto&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance de Leitura&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Excelente.&lt;/strong&gt; Sem &lt;code&gt;JOINs&lt;/code&gt;, as consultas para dashboards são instantâneas.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Muito boa.&lt;/strong&gt; &lt;code&gt;JOINs&lt;/code&gt; otimizados entre a tabela fato e as dimensões.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexibilidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Menor.&lt;/strong&gt; Otimizada para o processo de funil. Perguntas sobre novas dimensões exigem alterações no ETL.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Excelente.&lt;/strong&gt; Permite análises exploratórias (ad-hoc), cruzando fatos com qualquer combinação de dimensões.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manutenção&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lógica complexa e centralizada no ETL de criação da tabela.&lt;/td&gt;
&lt;td&gt;Mais simples. Atualizar o nome de um cliente, por exemplo, afeta apenas a &lt;code&gt;dim_cliente&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ideal Para&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dashboards de alta performance e análise de processos específicos como funis.&lt;/td&gt;
&lt;td&gt;Business Intelligence geral e exploração de dados, criando uma "fonte da verdade" reutilizável.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Por que a OBT é frequentemente a escolha certa para funis?&lt;/strong&gt;&lt;br&gt;
Para analisar um processo sequencial como um funil, a OBT é superior. A complexidade de calcular os tempos entre múltiplos estados (&lt;code&gt;data_resolucao - data_abertura&lt;/code&gt;) é resolvida &lt;strong&gt;uma única vez&lt;/strong&gt; durante o ETL. A estrutura é desenhada especificamente para responder a perguntas sobre esse funil da forma mais rápida possível. Apresentar uma única linha por ticket com todos os seus marcos temporais é a forma mais intuitiva e performática para o consumo em dashboards.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decisão 2: A Técnica de Pivotagem no Redshift
&lt;/h3&gt;

&lt;p&gt;Uma vez decidida a estrutura OBT, precisamos transformar nossas linhas de log em colunas. Existem várias formas de fazer isso em SQL.&lt;/p&gt;
&lt;h4&gt;
  
  
  Abordagem 1: Agregação Condicional (Recomendada)
&lt;/h4&gt;

&lt;p&gt;Esta técnica utiliza &lt;code&gt;GROUP BY&lt;/code&gt; e &lt;code&gt;CASE WHEN&lt;/code&gt; dentro de funções de agregação. É a abordagem mais idiomática e performática em bancos de dados colunares como o Redshift.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tickets_funil&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;id_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status_novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Aberto'&lt;/span&gt;      &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="nv"&gt;"timestamp"&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;AS&lt;/span&gt; &lt;span class="n"&gt;data_abertura&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status_novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Em Análise'&lt;/span&gt;  &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="nv"&gt;"timestamp"&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;AS&lt;/span&gt; &lt;span class="n"&gt;data_inicio_analise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status_novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Resolvido'&lt;/span&gt;   &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="nv"&gt;"timestamp"&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;AS&lt;/span&gt; &lt;span class="n"&gt;data_resolucao&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;suporte_ticket_eventos&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
    &lt;span class="n"&gt;id_ticket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Por que funciona tão bem no Redshift?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Pass:&lt;/strong&gt; A tabela de logs é lida apenas uma vez.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execução Colunar:&lt;/strong&gt; O Redshift lê apenas as colunas necessárias (&lt;code&gt;id_ticket&lt;/code&gt;, &lt;code&gt;status_novo&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;), ignorando o resto e minimizando I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processamento Paralelo (MPP):&lt;/strong&gt; A operação &lt;code&gt;GROUP BY&lt;/code&gt; é massivamente paralelizada entre os nós do cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Abordagem 2: Múltiplos Self-Joins (Não Recomendada)
&lt;/h4&gt;

&lt;p&gt;Uma abordagem intuitiva para quem vem de bancos de dados transacionais é criar uma subquery para cada status e juntá-las com &lt;code&gt;LEFT JOIN&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- NÃO FAÇA ISSO EM REDSHIFT PARA PIVOTAGEM&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;abertos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_abertura&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resolvidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_resolucao&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;id_ticket&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;suporte_ticket_eventos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;data_abertura&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;suporte_ticket_eventos&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status_novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Aberto'&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&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;AS&lt;/span&gt; &lt;span class="n"&gt;abertos&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abertos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ticket&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id_ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;data_resolucao&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;suporte_ticket_eventos&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status_novo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Resolvido'&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&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;AS&lt;/span&gt; &lt;span class="n"&gt;resolvidos&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolvidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ticket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Por que esta abordagem é ruim?&lt;/strong&gt; Ela força o Redshift a escanear a tabela &lt;code&gt;suporte_ticket_eventos&lt;/code&gt; múltiplas vezes, uma para cada subquery. O plano de execução se torna muito mais caro, com mais I/O e movimentação de dados entre os nós.&lt;/p&gt;

&lt;h4&gt;
  
  
  Alternativa: Funções de Janela (&lt;code&gt;Window Functions&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Funções de janela como &lt;code&gt;ROW_NUMBER()&lt;/code&gt; ou &lt;code&gt;LEAD()&lt;/code&gt;/&lt;code&gt;LAG()&lt;/code&gt; são extremamente poderosas, mas para o problema simples de pivotagem, a agregação condicional é geralmente mais direta e performática. As funções de janela brilham em análises de sequência mais complexas, como "qual foi o tempo gasto no status anterior, qualquer que seja ele?".&lt;/p&gt;

&lt;h3&gt;
  
  
  Dicas Finais de Otimização no Redshift
&lt;/h3&gt;

&lt;p&gt;Para garantir que a abordagem de agregação condicional seja ainda mais rápida, otimize sua tabela de logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DISTKEY&lt;/code&gt; (Chave de Distribuição):&lt;/strong&gt; Defina a &lt;code&gt;DISTKEY&lt;/code&gt; como o campo de agrupamento (ex: &lt;code&gt;id_ticket&lt;/code&gt;). Isso garante que todos os eventos do mesmo ticket fiquem no mesmo nó, eliminando a movimentação de dados pela rede (&lt;code&gt;shuffle&lt;/code&gt;) durante o &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SORTKEY&lt;/code&gt; (Chave de Ordenação):&lt;/strong&gt; Use uma chave de ordenação composta (ex: &lt;code&gt;id_ticket&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;). Isso acelera a busca e a ordenação dos eventos de um mesmo ticket.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A documentação da AWS sobre &lt;a href="https://aws.amazon.com/blogs/big-data/amazon-redshift-engineerings-advanced-table-design-playbook-distribution-styles-and-distribution-keys/" rel="noopener noreferrer"&gt;"Amazon Redshift Engineering’s Advanced Table Design Playbook: Distribution Styles and Distribution Keys"&lt;/a&gt; oferece excelentes guias sobre estes conceitos.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;A modelagem de dados para análise de funis exige decisões de arquitetura deliberadas. Para cenários focados em processos, como o nosso exemplo de tickets, a abordagem &lt;strong&gt;One Big Table&lt;/strong&gt; oferece performance e clareza para o usuário final. Dentro do Redshift, a técnica de &lt;strong&gt;agregação condicional&lt;/strong&gt; é a ferramenta mais eficiente para construir essa tabela a partir de dados de log, superando alternativas como múltiplos &lt;code&gt;JOINs&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>aws</category>
      <category>redshift</category>
      <category>sql</category>
    </item>
    <item>
      <title>Usando Funções de Ordem Superior (Higher-Order Functions - HOFs)</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Thu, 25 Sep 2025 00:41:27 +0000</pubDate>
      <link>https://dev.to/_richardson_/usando-funcoes-de-ordem-superior-higher-order-functions-hofs-5594</link>
      <guid>https://dev.to/_richardson_/usando-funcoes-de-ordem-superior-higher-order-functions-hofs-5594</guid>
      <description>&lt;p&gt;Se você trabalha com PySpark e já precisou aplicar uma lógica dentro de uma coluna do tipo array, sua primeira reação provavelmente foi: "Vou criar uma UDF". É uma solução rápida e flexível, mas que esconde um grave problema de performance.&lt;/p&gt;

&lt;p&gt;Neste post, vamos explorar a forma correta e muito mais eficiente de fazer isso usando &lt;strong&gt;Funções de Ordem Superior&lt;/strong&gt; (Higher-Order Functions - HOFs).&lt;/p&gt;

&lt;h3&gt;
  
  
  O Custo Invisível das UDFs de Python
&lt;/h3&gt;

&lt;p&gt;Uma User-Defined Function (UDF) em Python parece simples, mas por baixo dos panos, o Spark faz um trabalho caro:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Serialização:&lt;/strong&gt; Para cada linha, os dados da coluna saem do ambiente otimizado do Spark (JVM).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Transferência:&lt;/strong&gt; Os dados são enviados para um processo Python.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execução:&lt;/strong&gt; Sua função Python é executada.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Desserialização:&lt;/strong&gt; O resultado volta da Python para a JVM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esse "ida e volta" para cada elemento de dados em um cluster é um gargalo gigante. Em datasets de milhões ou bilhões de linhas, isso pode fazer seu job demorar horas a mais ou até mesmo falhar.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Solução: Funções de Ordem Superior (HOFs)
&lt;/h3&gt;

&lt;p&gt;HOFs são funções nativas do Spark SQL que recebem outras funções (geralmente lambdas) como argumento para processar dados complexos, como arrays e mapas.&lt;/p&gt;

&lt;p&gt;A grande vantagem é que &lt;strong&gt;toda a operação acontece dentro da JVM&lt;/strong&gt;, sem o custo de serialização/desserialização. A lógica que você define na função lambda é executada pelo próprio motor do Spark, aproveitando toda a sua otimização.&lt;/p&gt;

&lt;p&gt;Use HOFs quando precisar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transformar cada elemento de um array (&lt;code&gt;transform&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Filtrar elementos de um array com base em uma condição (&lt;code&gt;filter&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Verificar se um elemento que satisfaz uma condição existe no array (&lt;code&gt;exists&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Agregar elementos de um array (&lt;code&gt;aggregate&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mão na Massa: Exemplos Práticos
&lt;/h3&gt;

&lt;p&gt;Vamos criar um DataFrame simples para nossos exemplos. Nossa coluna &lt;code&gt;scores&lt;/code&gt; é um array de inteiros.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;

&lt;span class="c1"&gt;# Criando a Spark Session
&lt;/span&gt;&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HOF_Examples&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getOrCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# DataFrame de exemplo
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aluno_a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;]),&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aluno_b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aluno_c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aluno&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scores&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;df_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataFrame&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;columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. &lt;code&gt;transform&lt;/code&gt;: Aplicando uma transformação a cada elemento
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df_bonus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scores_com_bonus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transform(scores, x -&amp;gt; x + 10)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df_bonus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. &lt;code&gt;filter&lt;/code&gt;: Filtrando elementos de um array
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df_aprovados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notas_altas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter(scores, nota -&amp;gt; nota &amp;gt;= 90)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df_aprovados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. &lt;code&gt;exists&lt;/code&gt;: Verificando a existência de um elemento
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df_nota_max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tirou_100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exists(scores, nota -&amp;gt; nota = 100)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df_nota_max&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Guia de Referência Rápida: Funções de Ordem Superior
&lt;/h3&gt;

&lt;p&gt;Aqui está uma lista das HOFs mais comuns para você consultar.&lt;/p&gt;

&lt;h4&gt;
  
  
  Para Arrays
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;transform(array, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Aplica uma função a cada elemento do array e retorna um novo array com os resultados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Converter todos os nomes de um array para maiúsculas. &lt;code&gt;transform(nomes, nome -&amp;gt; upper(nome))&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;filter(array, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Retorna um novo array contendo apenas os elementos que satisfazem uma condição booleana.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Manter apenas os números pares de um array. &lt;code&gt;filter(numeros, n -&amp;gt; n % 2 == 0)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;exists(array, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Retorna &lt;code&gt;true&lt;/code&gt; se pelo menos um elemento do array satisfaz a condição.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Verificar se há algum produto com status "URGENTE" em um array de status. &lt;code&gt;exists(status, s -&amp;gt; s = 'URGENTE')&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;forall(array, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Retorna &lt;code&gt;true&lt;/code&gt; se &lt;strong&gt;todos&lt;/strong&gt; os elementos do array satisfazem a condição.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Checar se todas as tarefas de um projeto estão com status "CONCLUÍDO". &lt;code&gt;forall(tarefas, t -&amp;gt; t.status = 'CONCLUÍDO')&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;aggregate(array, start, merge [, finish])&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Reduz os elementos de um array a um único valor, começando com um valor inicial e aplicando uma função de merge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Somar todos os valores de um array de números. &lt;code&gt;aggregate(valores, 0, (acumulador, valor) -&amp;gt; acumulador + valor)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;zip_with(array1, array2, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Une dois arrays, elemento por elemento, aplicando uma função que combina os pares.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Calcular o total de cada item multiplicando um array de &lt;code&gt;quantidades&lt;/code&gt; por um de &lt;code&gt;precos&lt;/code&gt;. &lt;code&gt;zip_with(quantidades, precos, (q, p) -&amp;gt; q * p)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Para Mapas
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;transform_keys(map, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Aplica uma função a cada chave do mapa e retorna um novo mapa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Padronizar todas as chaves de um mapa para minúsculas. &lt;code&gt;transform_keys(mapa, (k, v) -&amp;gt; lower(k))&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;transform_values(map, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Aplica uma função a cada valor do mapa e retorna um novo mapa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Aplicar um desconto de 10% em todos os preços de um mapa &lt;code&gt;produto -&amp;gt; preco&lt;/code&gt;. &lt;code&gt;transform_values(mapa, (k, v) -&amp;gt; v * 0.9)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;map_filter(map, function)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descrição:&lt;/strong&gt; Retorna um novo mapa contendo apenas as entradas que satisfazem uma condição.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemplo de Uso:&lt;/strong&gt; Filtrar um mapa de &lt;code&gt;produto -&amp;gt; estoque&lt;/code&gt; para manter apenas produtos com estoque maior que zero. &lt;code&gt;map_filter(mapa, (k, v) -&amp;gt; v &amp;gt; 0)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Experimente Você Mesmo!
&lt;/h3&gt;

&lt;p&gt;Uma ótima maneira de testar tudo o que vimos aqui, sem a dor de cabeça de configurar um ambiente Spark local, é através da &lt;a href="https://www.databricks.com/learn/free-edition" rel="noopener noreferrer"&gt;Databricks Free Edition&lt;/a&gt;, que oferece um cluster gratuito para estudo e desenvolvimento.&lt;/p&gt;

&lt;p&gt;Para facilitar, deixei um notebook público com todo o código deste post pronto para ser visualizado e importado para a sua conta:&lt;/p&gt;

&lt;p&gt;➡️ Notebook com os Exemplos: &lt;a href="https://dbc-b3c65858-96b0.cloud.databricks.com/editor/notebooks/3213103558069130?o=3983328642459414" rel="noopener noreferrer"&gt;Visualizar no Databricks&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Da próxima vez que você precisar manipular elementos dentro de um array (ou mapa) no PySpark, respire fundo e lembre-se das Funções de Ordem Superior.&lt;/p&gt;

&lt;p&gt;Com os exemplos práticos e o guia de referência acima, você tem tudo o que precisa para começar. Pergunte-se: "Consigo resolver isso com &lt;code&gt;transform&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;exists&lt;/code&gt; ou outra HOF?". A resposta quase sempre será "sim", e seu pipeline de dados vai te agradecer com uma performance muito superior.&lt;/p&gt;

</description>
      <category>pyspark</category>
      <category>dataengineering</category>
      <category>python</category>
      <category>bigdata</category>
    </item>
    <item>
      <title>Paralelismo em Python para Engenharia de Dados: O Segredo das Tarefas I/O-Bound</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Sat, 06 Sep 2025 19:55:25 +0000</pubDate>
      <link>https://dev.to/_richardson_/paralelismo-em-python-para-engenharia-de-dados-o-segredo-das-tarefas-io-bound-4edi</link>
      <guid>https://dev.to/_richardson_/paralelismo-em-python-para-engenharia-de-dados-o-segredo-das-tarefas-io-bound-4edi</guid>
      <description>&lt;p&gt;Você já escreveu um script para buscar dados de centenas de APIs ou ler milhares de arquivos e ficou olhando para o progresso, linha por linha, enquanto a maior parte do tempo o seu processador parecia estar de férias?&lt;/p&gt;

&lt;p&gt;Se a resposta é sim, você provavelmente estava lidando com uma tarefa &lt;strong&gt;I/O-Bound&lt;/strong&gt;. Em engenharia de dados, entender esse conceito é a chave para transformar pipelines lentos em processos eficientes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conceitos Fundamentais: Concorrência não é Paralelismo
&lt;/h3&gt;

&lt;p&gt;Antes de mergulharmos no código, vale a pena esclarecer uma coisa. Como diz Rob Pike, um dos criadores da linguagem Go:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concorrência&lt;/strong&gt; é lidar com muitas coisas ao mesmo tempo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paralelismo&lt;/strong&gt; é fazer muitas coisas ao mesmo tempo.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nosso caso de I/O-bound é um exemplo clássico de &lt;strong&gt;concorrência&lt;/strong&gt;. Nosso programa gerencia centenas de requisições de rede "pendentes" de uma só vez. Mesmo que tenhamos apenas um punhado de cores de CPU, o sistema operacional consegue dar progresso a cada uma delas, aproveitando o tempo de espera. Não estamos necessariamente executando os downloads em paralelo (o que exigiria centenas de cores), mas estamos gerenciando a concorrência entre eles de forma eficiente.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Problema na Prática: A Execução Sequencial
&lt;/h3&gt;

&lt;p&gt;A abordagem mais intuitiva para processar uma lista de tarefas é um loop &lt;code&gt;for&lt;/code&gt;. Vamos simular a busca de dados para 200 produtos, onde cada busca leva 1 segundo (simulando a espera da rede).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buscar_dados_produto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Simula uma chamada de rede que leva 1 segundo.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buscando dados para o produto &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dados do produto &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# --- Execução Sequencial ---
&lt;/span&gt;&lt;span class="n"&gt;inicio_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;resultados_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;resultados_seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buscar_dados_produto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;fim_seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Tempo total (Sequencial): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fim_seq&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;inicio_seq&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; segundos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O cálculo do tempo é simples:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tempo Total (Sequencial) = Número de Tarefas × Tempo por Tarefa&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;200 tarefas × 1s/tarefa = 200 segundos&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É um processo lento, pois não aproveitamos o tempo de espera.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Solução Concorrente: Threads ao Resgate
&lt;/h3&gt;

&lt;p&gt;Se a CPU está ociosa enquanto espera, por que não usá-la para iniciar outras requisições? É exatamente isso que a concorrência com threads nos permite fazer. A biblioteca &lt;code&gt;concurrent.futures&lt;/code&gt; do Python torna isso simples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buscar_dados_produto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Simula uma chamada de rede que leva 1 segundo.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buscando dados para o produto &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dados do produto &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# --- Execução Paralela com Threads ---
&lt;/span&gt;
&lt;span class="c1"&gt;# Identificar o número de cores para definir os workers dinamicamente
&lt;/span&gt;&lt;span class="n"&gt;num_cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpu_count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="c1"&gt;# Para I/O-Bound, usamos um multiplicador. 4x o número de cores é um bom começo.
&lt;/span&gt;&lt;span class="n"&gt;MAX_WORKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num_cores&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Configurando pool com &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MAX_WORKERS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; threads...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;inicio_par&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MAX_WORKERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# O executor.map agenda as tarefas e as executa nas threads.
&lt;/span&gt;    &lt;span class="n"&gt;resultados_par&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buscar_dados_produto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;fim_par&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Tempo total (Threads): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fim_par&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;inicio_par&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; segundos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O tempo de execução cai drasticamente. Para uma máquina com 4 cores (16 workers), o tempo teórico seria:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tempo Total (Paralelo) ≈ (Número de Tarefas / Número de Workers) × Tempo por Tarefa&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;(200 tarefas / 16 workers) * 1s/tarefa ≈ 12.5 segundos&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Gráfico Comparativo: Visualizando o Ganho
&lt;/h3&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%2F1gf3habi8j8mo9z2og88.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%2F1gf3habi8j8mo9z2og88.png" alt=" " width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este gráfico ilustra perfeitamente os conceitos que discutimos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Ineficiência Sequencial:&lt;/strong&gt; A linha "Tempo Sequencial" cresce de forma perfeitamente linear e íngreme. Dobrar o número de tarefas dobra o tempo de execução, como esperado. Em uma escala logarítmica, isso se manifesta como uma linha reta e diagonal.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Impacto Massivo do Paralelismo:&lt;/strong&gt; Todas as linhas de execução paralela estão ordens de magnitude abaixo da linha sequencial, mostrando o ganho imediato e drástico de performance ao simplesmente não esperar em fila.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Benefícios de Mais Workers:&lt;/strong&gt; A linha de 32 workers está consistentemente abaixo da de 16, que por sua vez está abaixo da de 8. Isso confirma que, para esta tarefa I/O-Bound, adicionar mais "trabalhadores" (threads) para fazer requisições concorrentes acelera ainda mais o processo.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Retornos Decrescentes:&lt;/strong&gt; Note que a distância entre as linhas paralelas diminui. O salto de performance de "Sequencial" para "8 Workers" é gigantesco. O salto de "8" para "16" é ótimo, e o de "16" para "32" é bom, mas menor. Isso sugere que, em algum ponto, adicionar mais workers não trará um benefício tão grande, pois o sistema começará a ser limitado por outros fatores (largura de banda da rede, limites da API, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Por Que Threads e Não Processos? O GIL
&lt;/h3&gt;

&lt;h4&gt;
  
  
  O GIL: O Vilão que se Torna Herói no I/O
&lt;/h4&gt;

&lt;p&gt;O &lt;strong&gt;Global Interpreter Lock (GIL)&lt;/strong&gt; do Python é uma trava que permite que apenas uma thread execute bytecode Python por vez. Para tarefas que usam intensivamente a CPU, isso é um gargalo, pois impede o paralelismo real em múltiplos cores.&lt;/p&gt;

&lt;p&gt;No entanto, para tarefas I/O-bound, o GIL se comporta de maneira diferente. O segredo é que &lt;strong&gt;toda função da biblioteca padrão do Python que faz uma chamada de sistema (syscall) libera o GIL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Operações de rede e de disco são syscalls. Isso significa que quando a &lt;code&gt;Thread A&lt;/code&gt; faz uma chamada para ler um arquivo do S3, ela libera a trava do GIL. Isso permite que a &lt;code&gt;Thread B&lt;/code&gt; assuma o controle e inicie sua própria chamada de rede.&lt;/p&gt;

&lt;p&gt;O resultado, como diz o autor David Beazley, é que &lt;strong&gt;"threads em Python são ótimas em não fazer nada"&lt;/strong&gt; — e isso é exatamente o que queremos. Elas são a ferramenta perfeita para gerenciar a "espera" de forma concorrente. Usar processos (&lt;code&gt;ProcessPoolExecutor&lt;/code&gt;) teria um custo de memória e inicialização muito maior, que é desnecessário para tarefas que não competem por tempo de CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Que Ferramentas Como o &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; Fazem por Nós?
&lt;/h3&gt;

&lt;p&gt;Escrever código concorrente do zero é complexo. Ferramentas como &lt;code&gt;concurrent.futures&lt;/code&gt; escondem essa complexidade de nós.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Gerenciamento do Ciclo de Vida:&lt;/strong&gt; Iniciar threads tem um custo. Reutilizá-las em um "pool" é muito mais eficiente do que criar e destruir uma thread para cada pequena tarefa. O &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; faz exatamente esse gerenciamento de pool para nós.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Coordenação da Comunicação:&lt;/strong&gt; Como obter o resultado de uma tarefa que rodou em outra thread? O método &lt;code&gt;.map()&lt;/code&gt; do executor abstrai tudo isso, coletando os resultados e até mesmo tratando erros de forma transparente, sem que precisemos implementar filas ou outros mecanismos de comunicação manualmente.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Próximos Passos: Dask e Spark
&lt;/h3&gt;

&lt;p&gt;Para tarefas de engenharia de dados mais complexas ou com volumes que não cabem na memória, as ferramentas da biblioteca padrão atingem seu limite. É aí que entram frameworks mais robustos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dask:&lt;/strong&gt; Oferece uma abstração de alto nível sobre o paralelismo em Python, com DataFrames e Bags que podem operar em dados maiores que a memória e escalar para múltiplos nós.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spark:&lt;/strong&gt; É o padrão da indústria para processamento de Big Data. Com seu motor otimizado (Catalyst) e arquitetura distribuída, ele lida com transformações complexas (joins, agregações) em terabytes de dados de forma eficiente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exploraremos como essas ferramentas resolvem o mesmo problema (e muitos outros) em posts futuros.&lt;/p&gt;

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

&lt;p&gt;Entender a natureza da sua carga de trabalho é o primeiro passo para a otimização. Muitas tarefas em engenharia de dados não são limitadas pela velocidade de processamento, mas pelo tempo de espera.&lt;/p&gt;

&lt;p&gt;Da próxima vez que seu script de ingestão de dados parecer lento, pergunte-se: meu código está realmente trabalhando ou está apenas esperando? Se a resposta for "esperando", &lt;code&gt;concurrent.futures.ThreadPoolExecutor&lt;/code&gt; é uma ferramenta simples e poderosa da biblioteca padrão para transformar essa espera em eficiência.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referências
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com.br/Fluent-Python-Concise-Effective-Programming/dp/1492056359/ref=sr_1_1?adgrpid=128418323676&amp;amp;dib=eyJ2IjoiMSJ9.udVJAGRmoKtaJ2jA7XqGd_wy9hZ6d7AvRqPVzFE8TLiliIXBtrJUknDPqRPz4wObkBeRawxq5oGP8-hfDuvoWtvtVRXwjXLqo4RUZe4lsNhRov3Mf3lo3fN5QGG7_iNhBHH7nFcsWESKX7t-iwbub6amnIiv-oSFm_i8hbtZ2E2ZHuFeT4w9R3nOmV2_mh0lMgUmSt5yxNCeHLkvvoow833QNvmpHGyi6dso56LtunKY9VCC1VFJbxErdVyKzzg9iBjPRbwN3ghvw0qeQuDx-2mxMcB3ZRiOyxhJEkqTQfE.vOSz_soG9rIr49Cy1pUW0PFqwRF7eRH9B4Fhabja_qs&amp;amp;dib_tag=se&amp;amp;hvadid=595773152781&amp;amp;hvdev=c&amp;amp;hvlocphy=9101692&amp;amp;hvnetw=g&amp;amp;hvqmt=e&amp;amp;hvrand=1968933781755217023&amp;amp;hvtargid=kwd-417452057488&amp;amp;hydadcr=29347_14593528&amp;amp;keywords=python+fluente&amp;amp;mcid=ee69eb34327d3de1ac5ac935b44c1bbc&amp;amp;qid=1757188127&amp;amp;sr=8-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Fluent Python (Luciano Ramalho)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>dataengineering</category>
      <category>data</category>
      <category>programming</category>
    </item>
    <item>
      <title>Automatize Suas Tarefas Diárias de Programação com Gemini CLI</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Tue, 26 Aug 2025 17:00:57 +0000</pubDate>
      <link>https://dev.to/_richardson_/automatize-suas-tarefas-diarias-de-programacao-com-gemini-cli-3h0n</link>
      <guid>https://dev.to/_richardson_/automatize-suas-tarefas-diarias-de-programacao-com-gemini-cli-3h0n</guid>
      <description>&lt;p&gt;O Gemini CLI, com sua recente adição de comandos customizados e a capacidade de interagir com o shell, se tornou uma ferramenta indispensável no meu dia a dia. Neste post, vou mostrar como você pode usar o Gemini CLI, tanto com seus comandos internos quanto com scripts Bash, para automatizar algumas das tarefas mais comuns e liberar seu tempo para desafios mais interessantes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comando personalizado para analisar e explicar o código
&lt;/h2&gt;

&lt;p&gt;Frequentemente encontra arquivos Python com funções complexas ou não documentadas. É necessário investir um tempo lendo manualmente o código para entender sua finalidade, parâmetros e valores de retorno antes de poder usá-lo ou modificá-lo com segurança.&lt;/p&gt;

&lt;p&gt;Podemos criar um comando Gemini CLI reutilizável que possa analisar qualquer arquivo Python. Este comando deve gerar uma explicação clara e bem estruturada de cada função dentro daquele arquivo e fornecer uma maneira fácil de salvar essa análise para referência futura.&lt;/p&gt;

&lt;p&gt;1.Criaremos um comando global com namespace chamado &lt;code&gt;/py:explain&lt;/code&gt; dentro do diretório &lt;code&gt;.gemini/commands&lt;/code&gt;. Isso permitirá que você o execute em qualquer diretório do projeto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.gemini/commands/py
&lt;span class="nb"&gt;touch&lt;/span&gt; ~/.gemini/commands/py/explain.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Agora, abra o arquivo &lt;code&gt;explain.toml&lt;/code&gt; e adicione o seguinte prompt. Este prompt instrui o Gemini a atuar como um redator técnico especialista e fornece um formato estruturado para a saída.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.gemini/commands/py/explain.toml&lt;/span&gt;

&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Analyzes a Python file and generates a detailed explanation of each function."&lt;/span&gt;
&lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
You are an expert Python programmer and technical writer, skilled at creating clear and concise documentation.

Please analyze the following Python code, which I am providing from the file `{{args}}`:

!{cat {{args}}}
For each function in this file, generate a detailed explanation in Markdown format. Follow this structure precisely for every function:

function_name()
Purpose: A single, clear sentence explaining what the function does.

Parameters:

param_name (type): Description of the parameter.

Returns:

(type): Description of what the function returns.

Example Usage:

Python

# A simple, self-contained code snippet showing how to use the function.
Notes: Mention any important details, potential edge cases, or dependencies.

"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Como funciona:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt;: Fornece o texto útil que você vê no menu de ajuda do Gemini CLI.&lt;/li&gt;
&lt;/ul&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%2Fy0hizfg3rm2apdu8pb5i.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%2Fy0hizfg3rm2apdu8pb5i.png" alt="Gemini cli screenshot" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;prompt&lt;/code&gt;: Este é o conjunto de instruções detalhadas para o modelo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;!{cat {{args}}}&lt;/code&gt;: Esta é a parte principal. Ele executa o comando shell &lt;code&gt;cat&lt;/code&gt; no caminho do arquivo que você fornecer (&lt;code&gt;{{args}}&lt;/code&gt;), injetando todo o conteúdo do arquivo diretamente no prompt para análise.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agora você pode usar seu novo comando de dentro do Gemini CLI. Para salvar a saída diretamente em um arquivo, você pode executar &lt;code&gt;gemini&lt;/code&gt; de forma não interativa no seu terminal e usar o redirecionamento padrão do shell.&lt;/p&gt;

&lt;p&gt;Digamos que você queira entender um arquivo localizado em &lt;code&gt;src/data_processing.py&lt;/code&gt;.&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%2Fwo1294z86x3ohe4wek0t.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%2Fwo1294z86x3ohe4wek0t.png" alt="Gemini cli screenshot" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A interação com &lt;code&gt;gemini cli&lt;/code&gt; pode ser feita através também através de scripts (bash). Aqui tem um exemplo para criar commits: &lt;a href="https://github.com/richardson-souza/gemini-cli-automations/blob/main/.gemini/commands/git/git-atomic-commit.md" rel="noopener noreferrer"&gt;https://github.com/richardson-souza/gemini-cli-automations/blob/main/.gemini/commands/git/git-atomic-commit.md&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/gemini-cli-custom-slash-commands" rel="noopener noreferrer"&gt;Gemini CLI: Custom slash commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-gemini/gemini-cli" rel="noopener noreferrer"&gt;Gemini CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/commands.md" rel="noopener noreferrer"&gt;CLI Commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gemini</category>
      <category>cli</category>
      <category>automation</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>Testando com Monkey Patching</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Thu, 07 Aug 2025 20:31:35 +0000</pubDate>
      <link>https://dev.to/_richardson_/testando-com-monkey-patching-10c8</link>
      <guid>https://dev.to/_richardson_/testando-com-monkey-patching-10c8</guid>
      <description>&lt;h2&gt;
  
  
  O Cenário
&lt;/h2&gt;

&lt;p&gt;Todo desenvolvedor já passou por isso: você precisa alterar ou dar manutenção em um trecho de código que não foi escrito pensando em testes. Frequentemente, esse código mistura lógica de negócio com configurações globais ou dependências implícitas, tornando a criação de testes unitários um desafio.&lt;/p&gt;

&lt;p&gt;Um exemplo clássico, especialmente em pipelines de dados, é uma função que utiliza uma sessão Spark (&lt;code&gt;spark&lt;/code&gt;) que existe como uma variável global no ambiente de produção, mas que não está definida no escopo de um teste local.&lt;/p&gt;

&lt;p&gt;A solução ideal seria refatorar o código para usar &lt;strong&gt;Injeção de Dependência&lt;/strong&gt;, mas nem sempre temos tempo ou permissão para fazer grandes alterações na base de código. Então, como criamos uma rede de segurança para garantir que nossas alterações funcionem? A resposta tática é &lt;strong&gt;Monkey Patching&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definições Rápidas
&lt;/h2&gt;

&lt;p&gt;Antes de prosseguir, vamos alinhar dois conceitos-chave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Injeção de Dependência (Dependency Injection - DI):&lt;/strong&gt; Um padrão de projeto onde as dependências de um componente (objetos, configurações, conexões) são fornecidas a ele externamente, em vez de serem criadas internamente. Na prática, significa "passar o que a função precisa como parâmetro".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monkey Patching:&lt;/strong&gt; Uma técnica que permite modificar ou substituir dinamicamente o comportamento de módulos, classes ou funções em tempo de execução. Em testes, usamos isso para substituir dependências reais (como bancos de dados ou APIs) por objetos falsos ("mocks").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  O Código-Alvo
&lt;/h2&gt;

&lt;p&gt;Imagine a seguinte função em um arquivo &lt;code&gt;data_processor.py&lt;/code&gt;. Ela recebe um RDD, mas depende de um objeto &lt;code&gt;spark&lt;/code&gt; que não está em sua assinatura.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my_project/data_processor.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RDD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataFrame&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_raw_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_rdd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RDD&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;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Transforma um RDD de logs brutos, quebrando JSONs concatenados
    e retornando um DataFrame estruturado.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Mapeia o RDD para adicionar delimitadores
&lt;/span&gt;    &lt;span class="n"&gt;mapped_rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\}\{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}##!!##{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# CRASH! A variável 'spark' não existe no escopo do teste.
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapped_rdd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;df_processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;##!!##&lt;/span&gt;&lt;span class="sh"&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;return&lt;/span&gt; &lt;span class="n"&gt;df_processed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  O Desafio do Teste
&lt;/h2&gt;

&lt;p&gt;Um teste direto para essa função falharia, pois o &lt;code&gt;spark&lt;/code&gt; não está definido.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/test_data_processor.py
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_process_raw_logs_fails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# 'spark' aqui é uma fixture do pytest
&lt;/span&gt;    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;my_project.data_processor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;process_raw_logs&lt;/span&gt;

    &lt;span class="c1"&gt;# ... código para criar um RDD de teste ...
&lt;/span&gt;
    &lt;span class="c1"&gt;# A linha abaixo irá falhar com: NameError: name 'spark' is not defined
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_raw_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_rdd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Solução: Monkey Patching em Ação
&lt;/h2&gt;

&lt;p&gt;Para contornar isso, vamos usar o &lt;code&gt;monkeypatch&lt;/code&gt; do &lt;code&gt;pytest&lt;/code&gt; para injetar a sessão &lt;code&gt;spark&lt;/code&gt; (fornecida pela nossa fixture de teste) diretamente no &lt;em&gt;módulo&lt;/em&gt; &lt;code&gt;data_processor&lt;/code&gt; antes de chamar a função.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/test_data_processor.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_process_raw_logs_with_monkeypatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Importamos o módulo que queremos testar, não a função diretamente.
&lt;/span&gt;    &lt;span class="c1"&gt;#    Isso nos dá um objeto para "remendar".
&lt;/span&gt;    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;my_project&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;data_processor&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. AQUI ESTÁ O TRUQUE: Monkey Patching.
&lt;/span&gt;    &lt;span class="c1"&gt;#    Criamos um atributo chamado 'spark' dentro do módulo 'data_processor'
&lt;/span&gt;    &lt;span class="c1"&gt;#    e atribuímos a ele a nossa fixture 'spark' do teste.
&lt;/span&gt;    &lt;span class="n"&gt;data_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Agora, preparamos nosso cenário de teste.
&lt;/span&gt;    &lt;span class="n"&gt;log_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1}{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 2}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 3}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;test_rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparkContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 4. Executamos a função.
&lt;/span&gt;    &lt;span class="c1"&gt;#    Quando a função `process_raw_logs` procurar por 'spark', ela o encontrará
&lt;/span&gt;    &lt;span class="c1"&gt;#    no escopo do seu próprio módulo, pois nós o colocamos lá.
&lt;/span&gt;    &lt;span class="n"&gt;result_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_raw_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_rdd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 5. Verificamos o resultado.
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;

    &lt;span class="c1"&gt;# O pytest garante que essa modificação no módulo seja desfeita após o teste,
&lt;/span&gt;    &lt;span class="c1"&gt;# evitando contaminação entre testes.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Por Que Isso Funciona?
&lt;/h3&gt;

&lt;p&gt;Em Python, módulos são objetos. Quando você faz &lt;code&gt;import my_project.data_processor&lt;/code&gt;, você está carregando o código daquele arquivo em um objeto de módulo na memória. O que a linha &lt;code&gt;data_processor.spark = spark&lt;/code&gt; faz é simplesmente adicionar um novo atributo a esse objeto. A função &lt;code&gt;process_raw_logs&lt;/code&gt;, ao ser executada, resolve o nome &lt;code&gt;spark&lt;/code&gt; procurando primeiro em seu escopo local e depois no escopo do módulo onde foi definida, encontrando a nossa versão injetada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Análise da Abordagem
&lt;/h3&gt;

&lt;p&gt;Esta técnica é poderosa, mas deve ser usada com cautela.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prós:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permite testar código que de outra forma seria "intestável".&lt;/li&gt;
&lt;li&gt;Serve como uma "rede de segurança" essencial para permitir futuras refatorações. Você pode escrever um teste como este para garantir que não quebrou nada ao fazer uma alteração.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Contras:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;É um "code smell". O teste se torna mais complexo e menos legível.&lt;/li&gt;
&lt;li&gt;A dependência da função continua oculta. Um desenvolvedor precisa ler o teste para entender que a função &lt;code&gt;process_raw_logs&lt;/code&gt; depende de &lt;code&gt;spark&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;O teste fica fortemente acoplado à estrutura do arquivo, não ao contrato da função.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;Enfrentar código legado ou não projetado para testes é uma realidade. Embora a Injeção de Dependência seja o objetivo estratégico para um código limpo e manutenível, o Monkey Patching é uma ferramenta tática indispensável. Ele nos permite criar uma rede de segurança, garantindo a qualidade e a estabilidade do software enquanto pavimentamos o caminho para futuras melhorias. Use-o como um meio para um fim, não como o padrão final.&lt;/p&gt;




&lt;h3&gt;
  
  
  Referências
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pytest &lt;code&gt;monkeypatch&lt;/code&gt; fixture:&lt;/strong&gt; &lt;a href="https://docs.pytest.org/en/latest/how-to/monkeypatch.html" rel="noopener noreferrer"&gt;pytest.org documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;unittest.mock&lt;/code&gt; (Standard Library):&lt;/strong&gt; &lt;a href="https://docs.python.org/3/library/unittest.mock.html" rel="noopener noreferrer"&gt;docs.python.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection - Martin Fowler:&lt;/strong&gt; &lt;a href="https://martinfowler.com/articles/injection.html" rel="noopener noreferrer"&gt;martinfowler.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working Effectively with Legacy Code (Livro de Michael Feathers):&lt;/strong&gt; Um livro fundamental sobre as estratégias discutidas aqui, como "Characterization Tests" e "Seams".&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>pytest</category>
      <category>dataengineering</category>
      <category>pyspark</category>
    </item>
    <item>
      <title>Pytest: Como Testar Módulos Python com Configuração no Nível Superior</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Wed, 06 Aug 2025 23:31:13 +0000</pubDate>
      <link>https://dev.to/_richardson_/pytest-como-testar-modulos-python-com-configuracao-no-nivel-superior-1ifc</link>
      <guid>https://dev.to/_richardson_/pytest-como-testar-modulos-python-com-configuracao-no-nivel-superior-1ifc</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Em engenharia de dados, é comum criar scripts procedurais em Python para orquestrar pipelines. Um padrão frequente nesses scripts é a definição de configurações globais — como nomes de buckets — no nível superior do módulo, baseadas em variáveis de ambiente que mudam entre &lt;code&gt;dev&lt;/code&gt; e &lt;code&gt;prd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Embora seja uma abordagem direta, ela introduz um desafio significativo para a criação de testes unitários com &lt;code&gt;pytest&lt;/code&gt;. Frequentemente, os testes falham durante a fase de importação, antes da execução de qualquer lógica, devido a configurações de ambiente ausentes.&lt;/p&gt;

&lt;p&gt;Este artigo apresenta uma análise técnica da causa raiz desse problema, explica o papel fundamental do &lt;code&gt;conftest.py&lt;/code&gt; e oferece duas soluções práticas para garantir que seus módulos sejam testáveis.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Padrão de Código e o Desafio do Teste
&lt;/h2&gt;

&lt;p&gt;Considere o script &lt;code&gt;data_processor.py&lt;/code&gt;. Sua função é ler dados de um bucket "raw" e escrevê-los em um bucket "processed". Os nomes dos buckets são determinados pela variável de ambiente &lt;code&gt;DEPLOY_ENV&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;data_processor.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="c1"&gt;# Suponha uma biblioteca interna para operações S3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;s3_utils&lt;/span&gt; 

&lt;span class="c1"&gt;# 1. Configuração lida no nível do módulo
&lt;/span&gt;&lt;span class="n"&gt;ENV_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEPLOY_ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Ex: 'dev' ou 'prd'
&lt;/span&gt;
&lt;span class="c1"&gt;# 2. Validação que ocorre durante a importação
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ENV_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A variável de ambiente DEPLOY_ENV não foi definida.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Variáveis globais construídas a partir da configuração
&lt;/span&gt;&lt;span class="n"&gt;RAW_DATA_BUCKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-data-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ENV_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PROCESSED_DATA_BUCKET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-data-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ENV_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-processed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_source_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Lê um arquivo da zona raw, processa e salva na zona processed.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;source_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RAW_DATA_BUCKET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/sources/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;destination_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PROCESSED_DATA_BUCKET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/reports/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.parquet&lt;/span&gt;&lt;span class="sh"&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;s3_utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...lógica de processamento...
&lt;/span&gt;    &lt;span class="n"&gt;s3_utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_parquet&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;destination_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;destination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;destination_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O desafio é claro: a linha &lt;code&gt;raise ValueError&lt;/code&gt; será executada assim que o módulo for importado se &lt;code&gt;DEPLOY_ENV&lt;/code&gt; não estiver definida, impedindo qualquer teste.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Estratégia de Teste Inicial: Usando &lt;code&gt;conftest.py&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Para testar nosso script, precisamos controlar suas dependências externas. Neste caso, a dependência é a variável de ambiente &lt;code&gt;DEPLOY_ENV&lt;/code&gt;. A ferramenta padrão e mais poderosa do &lt;code&gt;pytest&lt;/code&gt; para gerenciar configurações e dependências compartilhadas é o arquivo &lt;code&gt;conftest.py&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O que é o &lt;code&gt;conftest.py&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
É um arquivo especial que o &lt;code&gt;pytest&lt;/code&gt; procura e carrega automaticamente. Ele permite definir &lt;strong&gt;fixtures&lt;/strong&gt;, que são funções de setup e teardown reutilizáveis. Tudo que é definido em um &lt;code&gt;conftest.py&lt;/code&gt; fica disponível para todos os testes no mesmo diretório e em subdiretórios.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nossa estratégia inicial seria criar um &lt;code&gt;conftest.py&lt;/code&gt; para carregar um ambiente de teste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;tests/conftest.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;autouse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_test_environment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Carrega variáveis de um arquivo .env para a sessão de testes.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dotenv_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/.env.test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos detalhar essa fixture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@pytest.fixture&lt;/code&gt;: Transforma a função em uma fixture do &lt;code&gt;pytest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scope="session"&lt;/code&gt;: Define que a fixture será executada &lt;strong&gt;apenas uma vez&lt;/strong&gt; por sessão de teste, e não antes de cada teste. É ideal para configurações que não mudam.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autouse=True&lt;/code&gt;: Este é o parâmetro chave. Ele instrui o &lt;code&gt;pytest&lt;/code&gt; a executar esta fixture &lt;strong&gt;automaticamente&lt;/strong&gt; para todos os testes, sem que precisemos solicitá-la explicitamente. É perfeito para um setup de ambiente global.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com essa configuração, parece que nosso problema está resolvido. No entanto, o seguinte teste ainda falhará:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;tests/test_processor_fail.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Este import irá disparar a validação em data_processor.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;data_processor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;process_source_file&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_file_processing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# O erro ocorre antes que o corpo do teste seja executado.
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Análise da Causa Raiz: Ordem de Execução no Pytest
&lt;/h2&gt;

&lt;p&gt;Apesar de nossa configuração correta no &lt;code&gt;conftest.py&lt;/code&gt;, a falha ocorre devido à interação entre o mecanismo de importação do Python e o ciclo de vida do &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Setup da Sessão Pytest&lt;/strong&gt;: &lt;code&gt;pytest&lt;/code&gt; inicia e executa nossa fixture &lt;code&gt;load_test_environment&lt;/code&gt; devido ao &lt;code&gt;scope="session"&lt;/code&gt; e &lt;code&gt;autouse=True&lt;/code&gt;. O ambiente de teste, com &lt;code&gt;DEPLOY_ENV=dev&lt;/code&gt;, é carregado.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Coleta de Testes&lt;/strong&gt;: Em seguida, &lt;code&gt;pytest&lt;/code&gt; inicia a fase de coleta. Ele encontra &lt;code&gt;tests/test_processor_fail.py&lt;/code&gt; e o interpretador Python executa a instrução &lt;code&gt;from data_processor import process_source_file&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Falha na Importação&lt;/strong&gt;: É neste momento que o código no nível superior do &lt;code&gt;data_processor.py&lt;/code&gt; é executado. Por razões de "timing" e isolamento de processos na fase de coleta, o ambiente recém-configurado pela fixture pode não estar visível para este processo de importação imediato, resultando no &lt;code&gt;ValueError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execução do Teste&lt;/strong&gt;: A fase de execução do teste nunca é alcançada.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A solução é garantir que a importação do módulo problemático ocorra somente &lt;em&gt;após&lt;/em&gt; o ambiente de teste do &lt;code&gt;pytest&lt;/code&gt; estar completamente estabelecido e visível.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solução 1: Importação Local
&lt;/h2&gt;

&lt;p&gt;A abordagem mais direta é mover a instrução &lt;code&gt;import&lt;/code&gt; do topo do arquivo para dentro da função de teste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;tests/test_processor_solution1.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_process_source_file_with_local_import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Testa o processamento de arquivos adiando a importação do módulo.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. A importação ocorre aqui, dentro do escopo de execução do teste.
&lt;/span&gt;    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;data_processor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;process_source_file&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Agora podemos mockar as dependências do módulo recém-importado.
&lt;/span&gt;    &lt;span class="n"&gt;mock_s3_utils&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_processor.s3_utils&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Executamos a função e validamos o resultado.
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_source_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;expected_dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://company-data-dev-processed/reports/user_123.parquet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;mock_s3_utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_parquet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;destination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_dest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vantagens&lt;/strong&gt;: Simples, explícito e resolve o problema de forma eficaz.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desvantagens&lt;/strong&gt;: Pode levar à repetição do &lt;code&gt;import&lt;/code&gt; se múltiplos testes no mesmo arquivo precisarem da mesma função.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solução 2: Fixture para Injeção do Módulo
&lt;/h2&gt;

&lt;p&gt;Uma alternativa mais escalável é encapsular a importação local dentro de uma fixture. O teste então declara sua dependência nesta fixture, que fornece o módulo importado como um objeto. Para maior clareza, esta fixture pode ser definida no próprio arquivo de teste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;tests/test_processor_solution2.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;module&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;data_processor_module&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Fixture que importa e retorna o módulo data_processor.
    A importação ocorre apenas quando a fixture é utilizada.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;data_processor&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data_processor&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_process_source_file_with_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_processor_module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Testa o processamento de arquivos usando um módulo injetado via fixture.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. A fixture `data_processor_module` é executada, importando o módulo.
&lt;/span&gt;    &lt;span class="n"&gt;mock_s3_utils&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_processor.s3_utils&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Chamamos a função através do objeto do módulo injetado.
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_processor_module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_source_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;expected_dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3://company-data-dev-processed/reports/user_123.parquet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;mock_s3_utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_parquet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;destination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_dest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vantagens&lt;/strong&gt;: Promove a reutilização de código (DRY), mantém os testes limpos e centraliza a lógica de importação tardia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desvantagens&lt;/strong&gt;: Adiciona um nível de indireção que pode ser menos óbvio para desenvolvedores não familiarizados com o padrão.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão e Recomendações
&lt;/h2&gt;

&lt;p&gt;O acoplamento entre a lógica de um módulo e sua configuração no nível superior é um desafio comum para a testabilidade. Compreender o ciclo de vida do &lt;code&gt;pytest&lt;/code&gt; e o papel do &lt;code&gt;conftest.py&lt;/code&gt; é fundamental para diagnosticar e resolver os problemas de importação resultantes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;importação local&lt;/strong&gt; (Solução 1) é a abordagem mais direta e recomendada para casos simples.&lt;/li&gt;
&lt;li&gt;O uso de uma &lt;strong&gt;fixture para injeção&lt;/strong&gt; (Solução 2) é preferível em cenários onde múltiplos testes precisam acessar diferentes funções de um mesmo módulo, oferecendo uma solução mais limpa e organizada.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ambas as técnicas são ferramentas valiosas para aumentar a cobertura de testes e a robustez de aplicações Python que não foram inicialmente projetadas com a testabilidade em mente.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/contents.html" rel="noopener noreferrer"&gt;https://docs.pytest.org/en/stable/contents.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/reference/fixtures.html" rel="noopener noreferrer"&gt;https://docs.pytest.org/en/stable/reference/fixtures.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/explanation/goodpractices.html" rel="noopener noreferrer"&gt;https://docs.pytest.org/en/stable/explanation/goodpractices.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>pytest</category>
      <category>testing</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>What Is Change Data Capture (CDC) and How It Works on Google Cloud</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Sun, 13 Jul 2025 18:52:55 +0000</pubDate>
      <link>https://dev.to/_richardson_/what-is-change-data-capture-cdc-and-how-it-works-on-google-cloud-5fb2</link>
      <guid>https://dev.to/_richardson_/what-is-change-data-capture-cdc-and-how-it-works-on-google-cloud-5fb2</guid>
      <description>&lt;p&gt;Keeping analytics pipelines &lt;strong&gt;real‑time&lt;/strong&gt; and &lt;strong&gt;resource‑efficient&lt;/strong&gt; is table stakes in 2025.&lt;br&gt;&lt;br&gt;
That’s where &lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; shines—streaming &lt;em&gt;only&lt;/em&gt; the rows that changed instead of bulk‑copying entire tables.&lt;/p&gt;

&lt;p&gt;In this post, you’ll learn:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What CDC is&lt;/strong&gt; (and why it matters).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three core implementation patterns&lt;/strong&gt;—query‑based, trigger‑based, log‑based.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to pick the right Google Cloud tool&lt;/strong&gt;—&lt;em&gt;Datastream&lt;/em&gt; or a DIY &lt;em&gt;Debezium + Dataflow&lt;/em&gt; combo.
&lt;/li&gt;
&lt;li&gt;A quick‑reference &lt;strong&gt;exam tip&lt;/strong&gt; for anyone chasing the &lt;strong&gt;Google Cloud Professional Data Engineer&lt;/strong&gt; cert.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔁 What Exactly Is CDC?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; = detecting inserts, updates, and deletes in a source database and pushing just those deltas downstream (e.g., into BigQuery).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Result:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No heavy &lt;strong&gt;full‑table copies&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Near–real‑time&lt;/strong&gt; dashboards and ML features.
&lt;/li&gt;
&lt;li&gt;Lower &lt;strong&gt;source‑DB load&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔍 CDC Patterns Every Engineer Should Know
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Query‑Based CDC (Timestamp / Version Column)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;last_updated_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2025-07-12 21:20:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;✅ Pros&lt;/th&gt;
&lt;th&gt;❌ Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dead‑simple scripting&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Misses deletes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No extra tooling&lt;/td&gt;
&lt;td&gt;Adds query load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Requires schema change (extra column)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  2. Trigger‑Based CDC
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Flow:&lt;/em&gt; Triggers (&lt;code&gt;AFTER INSERT/UPDATE/DELETE&lt;/code&gt;) copy changes into a &lt;code&gt;*_history&lt;/code&gt; table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;✅ Pros&lt;/th&gt;
&lt;th&gt;❌ Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Captures &lt;strong&gt;all&lt;/strong&gt; ops, incl. deletes&lt;/td&gt;
&lt;td&gt;High write‑time overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built‑in audit trail&lt;/td&gt;
&lt;td&gt;Harder to maintain at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  3. Log‑Based CDC (Modern Standard)
&lt;/h3&gt;

&lt;p&gt;Reads the DB’s transaction log (MySQL binlog, Postgres WAL, etc.).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;✅ Pros&lt;/th&gt;
&lt;th&gt;❌ Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Low latency&lt;/strong&gt; (near real time)&lt;/td&gt;
&lt;td&gt;Needs specialized tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimal DB impact&lt;/td&gt;
&lt;td&gt;Setup can be tricky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Captures deletes &amp;amp; schema changes&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🚀 Implementing CDC on Google Cloud
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A — &lt;strong&gt;Datastream&lt;/strong&gt; (Managed, Serverless)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Log‑based&lt;/strong&gt; CDC for MySQL, Postgres, Oracle, AlloyDB.&lt;/li&gt;
&lt;li&gt;Streams raw events into BigQuery staging tables.&lt;/li&gt;
&lt;li&gt;Auto‑executes &lt;code&gt;MERGE&lt;/code&gt; so target tables stay current.&lt;/li&gt;
&lt;li&gt;Handles schema drift for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect when you want &lt;strong&gt;“set it and forget it”&lt;/strong&gt; replication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B — &lt;strong&gt;Debezium + Pub/Sub + Dataflow&lt;/strong&gt; (DIY Flex)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Debezium&lt;/strong&gt; connectors tail the transaction log.&lt;/li&gt;
&lt;li&gt;Changes land in &lt;strong&gt;Pub/Sub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataflow&lt;/strong&gt; applies custom transforms → BigQuery.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use this path when you need &lt;strong&gt;complex, in‑flight transformations&lt;/strong&gt; or to support a niche source DB Debezium already speaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎓 Exam Tip
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;For the Google Cloud PDE exam, default to &lt;strong&gt;Datastream&lt;/strong&gt; for relational‑to‑BigQuery CDC.&lt;br&gt;
Reach for &lt;strong&gt;Dataflow + Debezium&lt;/strong&gt; only if the scenario explicitly calls for heavy transformations or bespoke routing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 Pattern Cheat‑Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Captures Deletes?&lt;/th&gt;
&lt;th&gt;Source DB Load&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;GCP Tool of Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query‑Based&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;🔺 Medium&lt;/td&gt;
&lt;td&gt;🟢 Low&lt;/td&gt;
&lt;td&gt;N/A (custom script)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger‑Based&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🔺 High&lt;/td&gt;
&lt;td&gt;🔺 Medium&lt;/td&gt;
&lt;td&gt;N/A (DB triggers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log‑Based&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🟢 Low&lt;/td&gt;
&lt;td&gt;🔺 High&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Datastream&lt;/strong&gt;, Debezium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Ready to Build?
&lt;/h2&gt;

&lt;p&gt;CDC turns stale ETL batches into streaming insights with surprisingly little effort—especially with Datastream doing the heavy lifting.&lt;/p&gt;

&lt;p&gt;Have questions, war stories, or tips? Drop them below—let’s level‑up together 💬&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt; If you found this helpful, consider following me for more posts on data engineering, GCP, and real‑world pipeline design.&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>bigquery</category>
      <category>dataengineering</category>
      <category>database</category>
    </item>
    <item>
      <title>Um Projeto Prático para Estudar RAG: Análise Qualitativa de Código com LLMs Locais</title>
      <dc:creator>Richardson</dc:creator>
      <pubDate>Sun, 15 Jun 2025 03:43:44 +0000</pubDate>
      <link>https://dev.to/_richardson_/crie-um-chatgpt-local-para-estudar-llms-e-seu-codigo-3mao</link>
      <guid>https://dev.to/_richardson_/crie-um-chatgpt-local-para-estudar-llms-e-seu-codigo-3mao</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Procurar informações em um repositório de código gigante pode ser frustrante. A resposta muitas vezes está ali, mas perdida em meio a centenas de arquivos. Ferramentas como grep resolvem buscas exatas, mas e se precisássemos de uma interpretação, uma "conversa" com o código?&lt;/p&gt;

&lt;p&gt;Para explorar essa questão, desenvolvi este projeto. O objetivo não é criar uma ferramenta de produção, mas sim servir como um laboratório prático para apoiar os estudos sobre LLMs e a arquitetura RAG. É uma oportunidade de observar, com um exemplo concreto, como esses componentes funcionam juntos.&lt;/p&gt;

&lt;p&gt;O foco do projeto foi criar um sistema capaz de responder a perguntas como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Quem é o responsável pela DAG X?"&lt;/li&gt;
&lt;li&gt;"Qual a finalidade da função Y?"&lt;/li&gt;
&lt;li&gt;"Mostre-me as queries que utilizam a tabela XYZ."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O resultado foi uma ferramenta de linha de comando funcional e, mais importante, um grande aprendizado que compartilho a seguir.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parte 1: A Ideia - RAG e o Papel Qualitativo do LLM
&lt;/h2&gt;

&lt;p&gt;A base do projeto é a arquitetura RAG (Retrieval-Augmented Generation), que une uma busca eficiente com a capacidade de interpretação de um Modelo de Linguagem Grande (LLM).&lt;/p&gt;

&lt;p&gt;O processo funciona em três etapas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A Base de Conhecimento (Vector Store): Primeiro, o conteúdo do repositório é indexado. Um script lê, divide os arquivos em trechos (chunks) e usa um modelo de embedding para converter cada trecho em vetores numéricos, que são armazenados localmente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O Detetive (Retriever): Quando uma pergunta é feita, o sistema busca na base de vetores os trechos de código mais relevantes. Esta é a etapa de recuperação de dados, puramente quantitativa: encontrar a informação exata.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O Intérprete (LLM): Aqui está o ponto crucial e o grande diferencial deste projeto. Os trechos de código encontrados são entregues ao LLM junto com a pergunta original. É neste momento que a mágica acontece.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  LLMs para Respostas Qualitativas
&lt;/h3&gt;

&lt;p&gt;Enquanto a busca (retriever) encontra o "o quê" (o trecho de código, a linha exata), o LLM é excepcional em fornecer o "porquê" e o "como". Sua força não está em encontrar dados, mas em sintetizar, resumir, explicar e inferir informações a partir do contexto fornecido. Ele transforma dados brutos em respostas qualitativas, que se assemelham à interpretação que um ser humano faria.&lt;/p&gt;

&lt;p&gt;O melhor de tudo? Todo o processo acontece localmente na sua máquina, tornando-o um ambiente perfeito e sem custos para estudos e experimentos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parte 2: Mãos à Obra - Preparando o Ambiente
&lt;/h2&gt;

&lt;p&gt;Vamos reunir as ferramentas necessárias para o nosso projeto.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ambiente Python
&lt;/h3&gt;

&lt;p&gt;Primeiro, certifique-se de ter o Python 3.8 (ou superior) instalado. Em seguida, crie uma pasta para o projeto e um ambiente virtual para manter as dependências organizadas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;code-qa-bot &lt;span class="c"&gt;# Ou o nome que preferir&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;code-qa-bot
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ollama: IA na sua Máquina
&lt;/h3&gt;

&lt;p&gt;O &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; é a forma mais simples de rodar modelos de linguagem localmente. Baixe e instale a versão para o seu sistema operacional. Depois, via terminal, baixe o modelo que usaremos para gerar as respostas. Usaremos o &lt;code&gt;gemma:2b&lt;/code&gt;, um modelo do Google leve e competente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma:2b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Abaixo, uma tabela comparativa para ajudar na escolha de outros modelos, caso queira experimentar:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model Name&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Context Length&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Quality (Code/NL)&lt;/th&gt;
&lt;th&gt;RAM (Quantized)&lt;/th&gt;
&lt;th&gt;Best Use Case&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phi-3 Mini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.8B&lt;/td&gt;
&lt;td&gt;4K&lt;/td&gt;
&lt;td&gt;⚡ Very Fast&lt;/td&gt;
&lt;td&gt;🟢 Excellent NL&lt;/td&gt;
&lt;td&gt;~4GB (Q4)&lt;/td&gt;
&lt;td&gt;General Q&amp;amp;A, search, chat&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 2B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2B&lt;/td&gt;
&lt;td&gt;8K&lt;/td&gt;
&lt;td&gt;⚡ Very Fast&lt;/td&gt;
&lt;td&gt;🟡 Moderate code&lt;/td&gt;
&lt;td&gt;~3.5GB (Q4)&lt;/td&gt;
&lt;td&gt;Lightweight assistants&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mistral 7B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;8K&lt;/td&gt;
&lt;td&gt;⚠️ Moderate&lt;/td&gt;
&lt;td&gt;🟢 Strong code+NL&lt;/td&gt;
&lt;td&gt;~8–9GB (Q4)&lt;/td&gt;
&lt;td&gt;General-purpose, coding&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TinyLlama 1.1B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.1B&lt;/td&gt;
&lt;td&gt;2K&lt;/td&gt;
&lt;td&gt;🚀 Extremely Fast&lt;/td&gt;
&lt;td&gt;🟡 Limited NL/code&lt;/td&gt;
&lt;td&gt;~2GB (Q4)&lt;/td&gt;
&lt;td&gt;Embedded tools, CLI help&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLaMA 3 8B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;8K&lt;/td&gt;
&lt;td&gt;⚠️ Moderate&lt;/td&gt;
&lt;td&gt;🟢 Very strong NL/code&lt;/td&gt;
&lt;td&gt;~9–10GB (Q4)&lt;/td&gt;
&lt;td&gt;High-quality retrieval + logic&lt;/td&gt;
&lt;td&gt;Meta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenChat 3.5 7B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;8K&lt;/td&gt;
&lt;td&gt;⚠️ Moderate&lt;/td&gt;
&lt;td&gt;🟢 Very accurate code&lt;/td&gt;
&lt;td&gt;~8GB (Q4)&lt;/td&gt;
&lt;td&gt;Coding-focused assistant&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Bibliotecas Python
&lt;/h3&gt;

&lt;p&gt;Crie um arquivo &lt;code&gt;requirements.txt&lt;/code&gt; com as seguintes dependências. Você pode ver o &lt;a href="https://github.com/richardson-souza/Ctrl-F-on-Steroids/blob/master/requirements.txt" rel="noopener noreferrer"&gt;arquivo original aqui&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Orquestração principal da pipeline RAG
langchain
langchain_community

# Ferramenta para rodar LLMs locais
ollama

# Integrações para componentes específicos
langchain-huggingface  # Para o modelo de embedding
langchain-chroma       # Para o vector store ChromaDB
langchain-ollama       # Para conectar ao LLM local via Ollama

# Provedor do modelo de embedding
sentence-transformers

# Banco de dados vetorial local
chromadb

# Utilitários para processamento de arquivos
PyYAML              # Para arquivos .yaml
sql-metadata        # Para arquivos .sql
lark

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

&lt;/div&gt;



&lt;p&gt;Agora, instale todas as bibliotecas de uma vez:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parte 3: O Código - Construindo o Motor de Busca
&lt;/h2&gt;

&lt;p&gt;Nossa ferramenta será modular, dividida em vários arquivos Python para maior clareza.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/richardson-souza/Ctrl-F-on-Steroids/blob/master/config.py" rel="noopener noreferrer"&gt;config.py&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Este arquivo centraliza as configurações. A principal alteração que você deve fazer aqui é apontar a variável &lt;code&gt;REPO_PATH&lt;/code&gt; para o caminho do seu repositório local.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/richardson-souza/Ctrl-F-on-Steroids/blob/master/data_loader.py" rel="noopener noreferrer"&gt;data_loader.py&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Aqui está o coração do pré-processamento. Em vez de tratar todos os arquivos como texto genérico, este módulo os analisa para extrair informações estruturadas e valiosas. É aqui que definimos o &lt;code&gt;page_content&lt;/code&gt; e os &lt;code&gt;metadata&lt;/code&gt; de cada "documento".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Atenção: Ponto Crucial de Customização!&lt;/strong&gt;&lt;br&gt;
As classes de processamento (&lt;code&gt;YamlProcessor&lt;/code&gt;, &lt;code&gt;SqlProcessor&lt;/code&gt;, etc.) foram desenhadas para uma estrutura de projeto específica. O seu repositório provavelmente terá uma organização diferente.&lt;/p&gt;

&lt;p&gt;Pense neste código como um template. A estratégia fundamental é criar "chunks" de informação inteligentes. Você precisará adaptar a lógica dentro de cada classe &lt;code&gt;process&lt;/code&gt; para que ela entenda e extraia as informações mais relevantes do &lt;strong&gt;seu&lt;/strong&gt; contexto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A importância de &lt;code&gt;page_content&lt;/code&gt; e &lt;code&gt;metadata&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Definir bem esses dois parâmetros é o segredo para uma busca precisa. Pense neles como uma ficha de catalogação de uma biblioteca:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;page_content (O Conteúdo do Livro):&lt;/strong&gt; É o texto que será efetivamente "lido" e vetorizado. Um &lt;code&gt;page_content&lt;/code&gt; claro e rico em contexto gera uma representação vetorial muito mais fiel. Por exemplo, a frase &lt;em&gt;"Este documento descreve a DAG com ID 'dag_exemplo'. O proprietário é 'ana.silva'."&lt;/em&gt; tem um significado semântico muito mais forte do que um bloco YAML bruto. É também esse conteúdo que o LLM usará para formular a resposta final.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metadata (A Etiqueta na Lombada):&lt;/strong&gt; São os dados que descrevem o conteúdo, como &lt;code&gt;dag_id&lt;/code&gt;, &lt;code&gt;table_name&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, etc. A função mais poderosa do &lt;code&gt;metadata&lt;/code&gt; é permitir a &lt;strong&gt;filtragem inteligente&lt;/strong&gt;. Quando usamos um &lt;code&gt;SelfQueryRetriever&lt;/code&gt;, ele primeiro usa os metadados para filtrar os documentos relevantes e &lt;em&gt;só então&lt;/em&gt; faz a busca por similaridade semântica. Isso torna a busca dramaticamente mais rápida e precisa, evitando que o sistema se confunda com informações de arquivos não relacionados.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/richardson-souza/Ctrl-F-on-Steroids/blob/master/vector_store.py" rel="noopener noreferrer"&gt;vector_store.py&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Este arquivo gerencia a criação e o carregamento do nosso banco de dados vetorial persistente (ChromaDB).&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/richardson-souza/Ctrl-F-on-Steroids/blob/master/indexer.py" rel="noopener noreferrer"&gt;indexer.py&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Um script independente que você executa uma única vez (ou sempre que houver mudanças significativas no código) para popular o banco de dados vetorial.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.google.com/search?q=https://github.com/richardson-souza/Ctrl-F-on-Steroids/blob/master/ask.py" rel="noopener noreferrer"&gt;ask.py&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Este é o script interativo para fazer perguntas ao seu código. Ele configura o &lt;code&gt;Self-Querying Retriever&lt;/code&gt;, o cérebro da nossa operação. Graças aos metadados ricos que definimos, o retriever usa o LLM para analisar sua pergunta e criar um filtro preciso antes mesmo de realizar a busca vetorial.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Parte 4: O Fluxo de Trabalho na Prática
&lt;/h2&gt;

&lt;p&gt;Com todos os arquivos no lugar, usar a ferramenta se resume a duas etapas.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Indexe seu Repositório
&lt;/h3&gt;

&lt;p&gt;Este passo único lê todos os arquivos do seu projeto, os processa e constrói o banco de dados vetorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ python indexer.py
Starting the indexing process...
Found and processed 19052 document chunks.
Creating new vector store...
Vector store created and saved.
✅ Indexing complete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Faça suas Perguntas
&lt;/h4&gt;

&lt;p&gt;Agora, inicie a ferramenta de Q&amp;amp;A e comece a conversar com seu código.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplo 1: Pergunta sobre responsabilidade&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ python ask.py
✅ Q&amp;amp;A Tool is ready. Ask questions about your codebase.
Ask a question &lt;span class="o"&gt;(&lt;/span&gt;or &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'exit'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: Who is the owner of the dag &lt;span class="s2"&gt;"process_seller_data_dag"&lt;/span&gt;?

&lt;span class="nt"&gt;---&lt;/span&gt; Answer &lt;span class="nt"&gt;---&lt;/span&gt;
ana.silva@suaempresa.com

&lt;span class="nt"&gt;---&lt;/span&gt; Sources &lt;span class="nt"&gt;---&lt;/span&gt;
- ./data/dags/process_seller_data_dag/dag.yaml

Ask a question &lt;span class="o"&gt;(&lt;/span&gt;or &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'exit'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exemplo 2: Pergunta sobre uso de tabelas&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ python ask.py
✅ Q&amp;amp;A Tool is ready. Ask questions about your codebase.

Ask a question &lt;span class="o"&gt;(&lt;/span&gt;or &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'exit'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: Quais consultas utilizam a tabela &lt;span class="s1"&gt;'analytics_prod.seller_reputation'&lt;/span&gt;?

&lt;span class="nt"&gt;---&lt;/span&gt; Answer &lt;span class="nt"&gt;---&lt;/span&gt;
Com base nos documentos fornecidos, a tabela &lt;span class="sb"&gt;`&lt;/span&gt;analytics_prod.seller_reputation&lt;span class="sb"&gt;`&lt;/span&gt; é utilizada principalmente na seguinte consulta SQL, encontrada no arquivo &lt;span class="sb"&gt;`&lt;/span&gt;classificacao_seller.sql&lt;span class="sb"&gt;`&lt;/span&gt;:

&lt;span class="sb"&gt;`&lt;/span&gt;SELECT ... FROM analytics_prod.seller_reputation WHERE ...&lt;span class="sb"&gt;`&lt;/span&gt;

Esta query parece ser central para calcular a classificação e o score dos vendedores.

Além disso, podemos inferir outros usos potenciais baseados na estrutura &lt;span class="k"&gt;do &lt;/span&gt;projeto:

1.  &lt;span class="k"&gt;**&lt;/span&gt;Relatórios de Performance:&lt;span class="k"&gt;**&lt;/span&gt; A tabela é provavelmente uma fonte de dados para dashboards e relatórios que monitoram a performance dos vendedores.
2.  &lt;span class="k"&gt;**&lt;/span&gt;Análises de Dados:&lt;span class="k"&gt;**&lt;/span&gt; Consultas de agregação &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;GROUP BY&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; podem ser usadas para analisar tendências e padrões no comportamento dos vendedores.
3.  &lt;span class="k"&gt;**&lt;/span&gt;Lógica de Negócio:&lt;span class="k"&gt;**&lt;/span&gt; Os dados podem disparar processos automáticos, como o envio de notificações para vendedores com base em sua performance.

Para uma lista exaustiva, seria necessário analisar todas as dependências que consomem os outputs desta DAG.

&lt;span class="nt"&gt;---&lt;/span&gt; Sources &lt;span class="nt"&gt;---&lt;/span&gt;
- ./data/dags/process_seller_data_dag/assets/json/classificacao_seller.json
- ./data/dags/process_seller_data_dag/readme.md
- ./data/dags/process_seller_data_dag/sql/taxa_score/classificacao_seller.sql
- ./data/dags/process_seller_data_dag/sql/taxa_score/sellers.sql

Ask a question &lt;span class="o"&gt;(&lt;/span&gt;or &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'exit'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
+---------------------------+
|   Pergunta &lt;span class="k"&gt;do &lt;/span&gt;Usuário     |
| &lt;span class="o"&gt;(&lt;/span&gt;Ex: &lt;span class="s2"&gt;"Descreva a DAG..."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; |
+---------------------------+
             |
             v
+--------------------------------------+
| 1. O Retriever busca os documentos   |
|    mais relevantes no Vector Store.  |
+--------------------------------------+
             |
             v
+--------------------------------------+
| 2. O conteúdo dos documentos         |
|    &lt;span class="o"&gt;(&lt;/span&gt;page_content&lt;span class="o"&gt;)&lt;/span&gt; é enviado para o   |
|    LLM como contexto.                |
+--------------------------------------+
             |
             v
+--------------------------------------+
| 3. O LLM lê o contexto e gera uma    |
|    resposta em texto &lt;span class="o"&gt;(&lt;/span&gt;resumo&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;       |
+--------------------------------------+
             |
             v
+---------------------------+
|   Resposta para o Usuário |
+---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://github.com/richardson-souza/Ctrl-F-on-Steroids#" rel="noopener noreferrer"&gt;Repositório no github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>python</category>
      <category>langchain</category>
      <category>gemma</category>
    </item>
  </channel>
</rss>
