<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Milton Camara</title>
    <description>The latest articles on Forem by Milton Camara (@lostdeveloper).</description>
    <link>https://forem.com/lostdeveloper</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%2F2701171%2F46b66528-4d4f-445e-a549-fc9568c99d77.png</url>
      <title>Forem: Milton Camara</title>
      <link>https://forem.com/lostdeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lostdeveloper"/>
    <language>en</language>
    <item>
      <title>Redis além do tutorial. Com os problemas que ninguém te conta</title>
      <dc:creator>Milton Camara</dc:creator>
      <pubDate>Tue, 12 May 2026 14:16:15 +0000</pubDate>
      <link>https://forem.com/lostdeveloper/redis-alem-do-tutorial-com-os-problemas-que-ninguem-te-conta-5bd</link>
      <guid>https://forem.com/lostdeveloper/redis-alem-do-tutorial-com-os-problemas-que-ninguem-te-conta-5bd</guid>
      <description>&lt;p&gt;Seu banco de dados é rápido. As queries estão otimizadas. Os índices estão no lugar. E mesmo assim, sob carga real, o sistema continua lento.&lt;/p&gt;

&lt;p&gt;O problema muitas vezes não é o banco em si. É que você está pedindo para ele responder as mesmas perguntas, repetidamente, milhares de vezes por segundo.&lt;/p&gt;

&lt;p&gt;É aqui que o Redis começa a importar.&lt;/p&gt;

&lt;p&gt;Mas ao contrário do que muitos tutoriais sugerem, o Redis não é mágica. É uma ferramenta com trade-offs muito específicos — e usá-la errado pode criar problemas piores do que os que você tentou resolver.&lt;/p&gt;




&lt;h2&gt;
  
  
  Como o Redis Funciona Por Dentro
&lt;/h2&gt;

&lt;p&gt;Redis armazena tudo em RAM como pares chave–valor, mas os valores não se limitam a strings simples. Ele suporta um conjunto rico de estruturas de dados:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Estrutura&lt;/th&gt;
&lt;th&gt;Casos de uso típicos&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Contadores, flags, resultados em cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hashes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Perfis de usuário, dados de sessão&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lists&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Filas, feeds de atividade recente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tags, relacionamentos únicos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sorted Sets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Leaderboards, ranking com score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streams&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Log de eventos com consumer groups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cada estrutura vem com comandos otimizados que operam &lt;em&gt;diretamente nos dados&lt;/em&gt;. Isso é a chave: em vez de buscar tudo e processar na sua aplicação, você delega a operação ao Redis.&lt;/p&gt;

&lt;p&gt;A diferença entre &lt;code&gt;ZRANGE leaderboard 0 9 REV WITHSCORES&lt;/code&gt; e buscar 10.000 linhas e ordenar no código é drástica em escala.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por que RAM Não é o Único Motivo
&lt;/h3&gt;

&lt;p&gt;A maioria das pessoas assume que Redis é rápido "porque usa RAM". Isso é verdade, mas incompleto.&lt;/p&gt;

&lt;p&gt;O ganho real está em evitar &lt;strong&gt;round-trips desnecessários ao banco&lt;/strong&gt;. Mesmo em redes locais de baixa latência, cada chamada TCP ao banco custa entre 0.5ms e 5ms. Com 500 usuários simultâneos fazendo 10 requests por segundo cada, você está fazendo 5.000 chamadas/segundo ao banco, sendo que boa parte delas responde a mesma pergunta.&lt;/p&gt;

&lt;p&gt;Redis elimina essa camada de comunicação para os dados mais acessados. O hit em cache retorna em microssegundos não só porque está em RAM, mas porque está na &lt;em&gt;mesma máquina&lt;/em&gt;, sem handshake TCP, sem parser de query, sem plano de execução.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Event Loop Single-Threaded
&lt;/h3&gt;

&lt;p&gt;Redis usa um event loop de thread única, um comando executa de cada vez.&lt;/p&gt;

&lt;p&gt;Isso parece limitante. Na prática, é uma escolha deliberada e que funciona bem para workloads típicos: sem locks, sem contenção, operações atomicamente previsíveis. Em benchmarks, o Redis processa centenas de milhares de operações por segundo em hardware comum porque cada operação é pequena, previsível e não bloqueia outras.&lt;/p&gt;

&lt;p&gt;O modelo escala bem enquanto os comandos são simples e rápidos, que é o caso da esmagadora maioria dos usos reais de Redis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Onde isso vira problema:&lt;/strong&gt; A partir do Redis 6.0, o I/O de rede passou a ser multi-threaded, mas a execução dos comandos continua single-threaded. Comandos lentos como &lt;code&gt;KEYS *&lt;/code&gt;, &lt;code&gt;SORT&lt;/code&gt; sem &lt;code&gt;LIMIT&lt;/code&gt;, ou &lt;code&gt;LRANGE&lt;/code&gt; em listas muito grandes bloqueiam &lt;em&gt;todo o servidor&lt;/em&gt; enquanto executam. Um único comando mal escrito pode degradar o sistema inteiro. É por isso que &lt;code&gt;KEYS *&lt;/code&gt; em produção é considerado um erro grave.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Persistência: Você Escolhe o Risco
&lt;/h3&gt;

&lt;p&gt;Redis é in-memory, mas pode persistir dados de duas formas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RDB (Redis Database):&lt;/strong&gt; Snapshots periódicos. Mais rápido, mas você pode perder as escritas desde o último snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AOF (Append-Only File):&lt;/strong&gt; Registra cada operação de escrita. Mais durável, mas gera arquivos grandes e pode impactar latência dependendo do &lt;code&gt;fsync&lt;/code&gt; configurado (&lt;code&gt;always&lt;/code&gt;, &lt;code&gt;everysec&lt;/code&gt;, &lt;code&gt;no&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Você pode usar ambos simultaneamente. A configuração padrão (&lt;code&gt;appendfsync everysec&lt;/code&gt;) aceita perder até 1 segundo de dados em caso de crash, aceitável para cache, problemático para dados financeiros.&lt;/p&gt;




&lt;h2&gt;
  
  
  Onde o Redis Realmente Brilha (Com Código)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Caching - O Padrão Cache-Aside
&lt;/h3&gt;

&lt;p&gt;O caso de uso mais comum. A aplicação verifica o Redis primeiro; em um hit, os dados retornam em microssegundos; em um miss, a aplicação consulta o banco e popula o Redis.&lt;/p&gt;

&lt;p&gt;Em .NET, a abstração mais portável é &lt;code&gt;IDistributedCache&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDistributedCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;CacheDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDistributedCache&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"product:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&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;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DistributedCacheEntryOptions&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AbsoluteExpirationRelativeToNow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CacheDuration&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;product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuração no &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddStackExchangeRedisCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&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="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Redis"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InstanceName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"myapp:"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quando isso funciona bem:&lt;/strong&gt; Para dados lidos muito mais frequentemente do que mudam. A chave é ter um TTL razoável e aceitar que usuários diferentes podem ver versões ligeiramente diferentes do dado durante a janela de cache.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. Rate Limiting — Atomicidade é Tudo
&lt;/h3&gt;

&lt;p&gt;Redis torna rate limiting simples através de operações atômicas. Com &lt;code&gt;StackExchange.Redis&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedisRateLimiter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDatabase&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;RedisRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConnectionMultiplexer&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;IsAllowedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"rate_limit:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUnixTimeSeconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringIncrementAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;KeyExpireAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window&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;current&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este padrão usa uma janela fixa (fixed window). É simples, mas tem um edge case: um usuário pode fazer o dobro de requisições na virada da janela. Para uso mais preciso, o padrão &lt;strong&gt;sliding window&lt;/strong&gt; usa Sorted Sets, cada requisição é inserida com timestamp como score, e você remove as entradas fora da janela antes de contar.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Session Storage
&lt;/h3&gt;

&lt;p&gt;Sessions precisam de leituras e escritas rápidas em cada requisição. Redis se encaixa porque:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Latência baixa sem delay por request&lt;/li&gt;
&lt;li&gt;TTL nativo, sessões expiram automaticamente sem cleanup jobs&lt;/li&gt;
&lt;li&gt;Alta throughput para bases de usuários grandes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No ASP.NET Core, a configuração é direta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddStackExchangeRedisCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"localhost:6379"&lt;/span&gt;&lt;span class="p"&gt;;&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="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdleTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpOnly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEssential&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Pub/Sub vs. Streams — Saiba a Diferença
&lt;/h3&gt;

&lt;p&gt;Redis tem Pub/Sub built-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Publisher&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;multiplexer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSubscriber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order:created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Subscriber&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SubscribeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order:created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;
    &lt;span class="c1"&gt;// processar pedido&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pub/Sub é simples e eficiente, mas tem uma limitação fundamental: mensagens são &lt;strong&gt;fire-and-forget&lt;/strong&gt;. Se nenhum subscriber estiver ouvindo no momento do publish, a mensagem é &lt;strong&gt;perdida para sempre&lt;/strong&gt;. Não há histórico, não há replay, não há garantia de entrega.&lt;/p&gt;

&lt;p&gt;Para workloads onde isso importa, &lt;strong&gt;Redis Streams&lt;/strong&gt; é a alternativa correta. A diferença conceitual é significativa:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pub/Sub&lt;/th&gt;
&lt;th&gt;Streams&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Persistência&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nenhuma — mensagem some após entrega&lt;/td&gt;
&lt;td&gt;Persistida no log até você deletar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consumer groups&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Não — todos recebem tudo&lt;/td&gt;
&lt;td&gt;Sim — múltiplos consumers dividem as mensagens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Impossível&lt;/td&gt;
&lt;td&gt;Sim — leitura por offset ou timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pending entries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Não existe&lt;/td&gt;
&lt;td&gt;Sim — mensagens entregues mas não confirmadas ficam no PEL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caso de uso&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notificações em tempo real, broadcast&lt;/td&gt;
&lt;td&gt;Pipelines de eventos, filas duráveis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Com Streams, um consumer group garante que cada mensagem seja processada por exatamente um consumer do grupo. Se o consumer falha antes de confirmar (&lt;code&gt;XACK&lt;/code&gt;), a mensagem fica no &lt;strong&gt;Pending Entries List (PEL)&lt;/strong&gt; e pode ser reclamada e reprocessada. Isso não existe no Pub/Sub.&lt;/p&gt;

&lt;p&gt;Resumindo: use Pub/Sub para broadcast em tempo real onde perder mensagens é tolerável. Use Streams quando você precisa de garantias de entrega, múltiplos consumers independentes ou auditoria de eventos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benefícios vs. Trade-offs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Benefício&lt;/th&gt;
&lt;th&gt;Trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Velocidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Microssegundos de latência, sem round-trips ao banco&lt;/td&gt;
&lt;td&gt;RAM é cara por GB; inviável para datasets grandes e frios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Simplicidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API rica e intuitiva&lt;/td&gt;
&lt;td&gt;Operações relacionais e agregações complexas não existem&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;Múltiplos data structures otimizados&lt;/td&gt;
&lt;td&gt;Modelagem exige pensar diferente de um banco relacional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durabilidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Configurável (RDB + AOF)&lt;/td&gt;
&lt;td&gt;Por padrão, risco de perda de dados em crash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operação&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud-managed disponível&lt;/td&gt;
&lt;td&gt;Cluster mode tem complexidade; resharding não é trivial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  As Armadilhas Que Aparecem em Produção
&lt;/h2&gt;

&lt;p&gt;Esta é a parte que a maioria dos artigos ignora. Redis em produção tem problemas específicos que se manifestam sob carga real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Stampede (Thundering Herd)
&lt;/h3&gt;

&lt;p&gt;Imagine 10.000 usuários acessando simultaneamente um dado cujo cache acabou de expirar. Todos consultam o banco ao mesmo tempo. O banco sofre um spike de carga. Paradoxalmente, o cache que deveria proteger o banco o derruba no exato momento em que expira.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como mitigar:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Probabilistic Early Expiration (PER):&lt;/strong&gt; Antes do TTL expirar, alguns processos já começam a renovar o cache de forma probabilística:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetWithEarlyRenewalAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;beta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringGetWithExpiryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Expiry&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;recomputeTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EstimatedRecomputeTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// medir em produção&lt;/span&gt;

        &lt;span class="c1"&gt;// Decide probabilisticamente se renova antes de expirar&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;shouldRenew&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;recomputeTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;beta&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NextDouble&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;shouldRenew&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;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringSetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ttl&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;result&lt;/span&gt;&lt;span class="p"&gt;;&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. Mutex/Lock no miss com ownership:&lt;/strong&gt; Apenas um processo refaz a query; os outros aguardam ou retornam o dado stale temporariamente. Mas atenção ao bug clássico desta abordagem: se o processo que adquiriu o lock demorar mais do que o TTL do lock para terminar, o lock expira, outro processo o adquire, e o processo original — ao finalizar — deleta o lock do processo &lt;em&gt;errado&lt;/em&gt;, criando uma race condition silenciosa.&lt;/p&gt;

&lt;p&gt;A solução é usar um &lt;strong&gt;token de ownership&lt;/strong&gt;: só quem criou o lock pode deletá-lo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lockKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"lock:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lockToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// token único por processo&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lockAcquired&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringSetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lockToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;When&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotExists&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lockAcquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;options&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;fresh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Deleta APENAS se o token ainda é o nosso — operação atômica via Lua&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;releaseLua&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ScriptEvaluateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;releaseLua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;RedisKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lockToken&lt;/span&gt;&lt;span class="p"&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;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&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;retry&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Para distributed locking em ambientes com múltiplas instâncias Redis (cluster ou sentinel), o algoritmo &lt;strong&gt;RedLock&lt;/strong&gt; oferece garantias mais fortes, adquirindo o lock em N instâncias independentes. A biblioteca &lt;code&gt;RedLock.net&lt;/code&gt; implementa isso para .NET. Para a maioria dos casos com uma instância Redis, o padrão acima com token é suficiente.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Key Eviction: O Redis Descarta Dados Silenciosamente
&lt;/h3&gt;

&lt;p&gt;Quando a memória do Redis está cheia e &lt;code&gt;maxmemory&lt;/code&gt; está configurado, o Redis precisa remover chaves. A política padrão (&lt;code&gt;noeviction&lt;/code&gt;) retorna erros de escrita — o que derruba a aplicação silenciosamente se não for tratado.&lt;/p&gt;

&lt;p&gt;As políticas mais comuns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Política&lt;/th&gt;
&lt;th&gt;Comportamento&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;noeviction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Erros nas escritas quando memória cheia. Seguro para filas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allkeys-lru&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove as chaves menos recentemente usadas. Boa para cache puro.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volatile-lru&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LRU apenas em chaves com TTL definido.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allkeys-random&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove aleatoriamente. Raramente é o que você quer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volatile-ttl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove as chaves com TTL mais curto primeiro.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Erro comum:&lt;/strong&gt; usar Redis para cache &lt;em&gt;e&lt;/em&gt; para dados persistentes (sessões, filas) na mesma instância sem separar políticas de eviction. Quando a memória enche, o Redis pode descartar uma sessão ativa ou uma mensagem de fila. Use instâncias separadas ou, no mínimo, chaves com TTL apenas no que pode ser descartado.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Hot Keys em Cluster Mode
&lt;/h3&gt;

&lt;p&gt;Em uma instância Redis única, toda a carga vai para o mesmo processo — e ele aguenta bem. Em cluster mode, as chaves são distribuídas entre shards por hash slot. O problema: se uma chave específica é acessada muito mais do que as outras (uma página inicial, um produto em promoção, uma configuração global), ela concentra carga em um único shard enquanto os outros ficam ociosos.&lt;/p&gt;

&lt;p&gt;Isso anula o benefício de escala horizontal do cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como detectar:&lt;/strong&gt; o comando &lt;code&gt;redis-cli --hotkeys&lt;/code&gt; (disponível com &lt;code&gt;maxmemory-policy&lt;/code&gt; configurada) ou monitoramento via &lt;code&gt;MONITOR&lt;/code&gt; em ambiente de staging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como mitigar:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key sharding local:&lt;/strong&gt; criar múltiplas chaves com sufixos (&lt;code&gt;config:app:1&lt;/code&gt;, &lt;code&gt;config:app:2&lt;/code&gt;, ...) e distribuir as leituras entre elas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side caching:&lt;/strong&gt; Redis 6.0 introduziu client-side caching via o protocolo &lt;code&gt;CLIENT TRACKING&lt;/code&gt;, permitindo que o cliente mantenha uma cópia local e invalide apenas quando o servidor notifica mudança — eliminando o round-trip para hot keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache em memória local:&lt;/strong&gt; para dados imutáveis ou de baixíssima variação, um &lt;code&gt;IMemoryCache&lt;/code&gt; na aplicação serve como primeira camada antes do Redis.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Memory Fragmentation
&lt;/h3&gt;

&lt;p&gt;Redis aloca e libera memória constantemente. Com o tempo, a fragmentação cresce: o processo ocupa mais RAM do que os dados realmente precisam.&lt;/p&gt;

&lt;p&gt;O indicador é o &lt;code&gt;mem_fragmentation_ratio&lt;/code&gt; no &lt;code&gt;INFO memory&lt;/code&gt;. Valores acima de 1.5 indicam fragmentação significativa. Acima de 2.0, você provavelmente precisa de um restart controlado ou de habilitar &lt;code&gt;activedefrag&lt;/code&gt; (disponível desde Redis 4.0):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;activedefrag&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;active&lt;/span&gt;-&lt;span class="n"&gt;defrag&lt;/span&gt;-&lt;span class="n"&gt;ignore&lt;/span&gt;-&lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;mb&lt;/span&gt;
&lt;span class="n"&gt;active&lt;/span&gt;-&lt;span class="n"&gt;defrag&lt;/span&gt;-&lt;span class="n"&gt;threshold&lt;/span&gt;-&lt;span class="n"&gt;lower&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  O Problema do KEYS * em Produção
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;KEYS *&lt;/code&gt; bloqueia o event loop single-threaded enquanto escaneia todo o keyspace. Em instâncias com milhões de chaves, isso pode causar segundos de indisponibilidade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nunca use &lt;code&gt;KEYS *&lt;/code&gt; em produção.&lt;/strong&gt; Use &lt;code&gt;SCAN&lt;/code&gt; com cursor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0L&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product:*"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SCAN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"MATCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"COUNT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedisResult&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedisResult&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SCAN&lt;/code&gt; não garante ausência de duplicatas, mas não bloqueia o servidor — e é iterativo, o que permite processar keyspaces enormes sem travar nada.&lt;/p&gt;




&lt;h3&gt;
  
  
  O que Monitorar em Produção
&lt;/h3&gt;

&lt;p&gt;Redis falha de formas silenciosas: evicta dados sem avisar, fragmenta memória gradualmente, acumula latência em comandos específicos. Sem observabilidade, você descobre o problema quando o usuário reclama.&lt;/p&gt;

&lt;p&gt;As métricas essenciais do &lt;code&gt;INFO&lt;/code&gt; para monitorar:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Métrica&lt;/th&gt;
&lt;th&gt;O que indica&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;evicted_keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chaves removidas por pressão de memória. Zero é o ideal; qualquer valor crescente é alerta.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;used_memory_rss&lt;/code&gt; vs &lt;code&gt;used_memory&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A diferença é a fragmentação. Se &lt;code&gt;rss&lt;/code&gt; for muito maior que &lt;code&gt;used_memory&lt;/code&gt;, você tem fragmentação.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instantaneous_ops_per_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Throughput atual. Útil para correlacionar com picos de latência.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;latency&lt;/code&gt; (via &lt;code&gt;LATENCY HISTORY&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Latência por evento. Detecta comandos lentos específicos.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connected_clients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pico de conexões pode indicar connection pool mal configurado.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;keyspace_hits&lt;/code&gt; vs &lt;code&gt;keyspace_misses&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A taxa de hit é o indicador mais direto de eficiência do cache.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Em ambientes cloud (ElastiCache, Azure Cache for Redis), essas métricas são exportadas para CloudWatch/Azure Monitor nativamente. Em ambientes self-hosted, ferramentas como &lt;strong&gt;Redis Exporter&lt;/strong&gt; + Prometheus + Grafana são o padrão de mercado.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quando Não Usar Redis
&lt;/h2&gt;

&lt;p&gt;Redis não é a ferramenta certa quando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dataset grande e frio:&lt;/strong&gt; RAM custa muito por GB. Use um banco em disco e faça cache apenas do subconjunto quente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries relacionais e agregações complexas:&lt;/strong&gt; "Todos os usuários da Região A que compraram o Produto B nos últimos 30 dias" exige um banco relacional com suporte a joins e índices compostos. Redis não é otimizado para esse tipo de acesso, ele recupera por chave, não por predicado sobre os dados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durabilidade forte:&lt;/strong&gt; Se perder segundos de escrita é inaceitável (transações financeiras, registros médicos), o modelo de persistência do Redis exige configuração cuidadosa, e um banco ACID-compliant é a opção mais segura.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workload stateless e simples:&lt;/strong&gt; Para sistemas sem cache, sem sessões e sem mensageria, adicionar Redis introduz complexidade operacional sem benefício proporcional.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Redis no Stack Real
&lt;/h2&gt;

&lt;p&gt;Em arquiteturas de microsserviços, o Redis assume um segundo papel como datastore compartilhado rápido: um session store central que todos os frontends consultam, um rate limiter que todos os API gateways respeitam, ou um message broker leve que conecta serviços sem o overhead de Kafka ou RabbitMQ.&lt;/p&gt;

&lt;p&gt;Provedores cloud oferecem Redis gerenciado (AWS ElastiCache, Azure Cache for Redis, Google Cloud Memorystore) que cuidam de provisionamento, scaling e failover. Isso facilita inserir Redis numa stack existente sem gerenciar infraestrutura diretamente.&lt;/p&gt;

&lt;p&gt;Uma alternativa que ganhou tração recentemente é o &lt;strong&gt;Garnet&lt;/strong&gt;, lançado pela Microsoft em 2024 como projeto open source. É compatível com o protocolo RESP do Redis, escrito em C#, e apresenta throughput superior em alguns benchmarks específicos de .NET. Ainda é jovem e não tem o ecossistema do Redis, mas vale acompanhar se você roda .NET.&lt;/p&gt;




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

&lt;p&gt;Redis é um multiplicador de performance, não um substituto de banco de dados.&lt;/p&gt;

&lt;p&gt;Ele não tenta resolver tudo. Resolve um conjunto estreito de problemas com precisão: caching, sessões, rate limiting, mensageria leve.&lt;/p&gt;

&lt;p&gt;E é exatamente por isso que está em todo lugar.&lt;/p&gt;

&lt;p&gt;Mas há uma armadilha real: Redis é fácil de adicionar e difícil de operar bem. Um cache sem política de eviction bem definida vira um ponto cego. Um Pub/Sub sem tratamento de mensagens perdidas vira um bug silencioso. Um distributed lock sem token de ownership vira uma race condition esperando para acontecer. Um keyspace mal modelado vira um problema de memória que aparece às 2h da manhã.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usado corretamente&lt;/strong&gt;, Redis transforma caminhos lentos em rápidos e sistemas frágeis em responsivos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usado sem cuidado&lt;/strong&gt;, vira uma abstração cara e com vazamentos.&lt;/p&gt;

&lt;p&gt;A diferença está em entender não só o que ele faz bem, mas onde ele mente para você.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Gostou? Deixa um comentário com o padrão que você usa Redis no seu stack. E se você já teve um cache stampede em produção, conta como resolveu, as histórias de guerra são sempre as mais úteis.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Pare de usar Logs como MULETA. Voce está fazendo ERRADO!!!</title>
      <dc:creator>Milton Camara</dc:creator>
      <pubDate>Mon, 11 May 2026 17:27:51 +0000</pubDate>
      <link>https://forem.com/lostdeveloper/pare-de-usar-logs-como-muleta-voce-esta-fazendo-errado-369e</link>
      <guid>https://forem.com/lostdeveloper/pare-de-usar-logs-como-muleta-voce-esta-fazendo-errado-369e</guid>
      <description>&lt;p&gt;Se você já abriu um arquivo de log de 2GB no meio da madrugada tentando descobrir por que o pagamento do cliente falhou, esse post é pra você. E não, a culpa não é da ferramenta. A culpa é sua.&lt;/p&gt;

&lt;p&gt;A real é dura: &lt;strong&gt;a maioria dos devs trata log como se fosse &lt;code&gt;Console.WriteLine&lt;/code&gt; com esteroides.&lt;/strong&gt; Espalha &lt;code&gt;LogInformation&lt;/code&gt; em cada método, acha que está "instrumentando" a aplicação, e quando o sistema quebra em produção fica horas "grepando" arquivo atrás de pista.&lt;/p&gt;

&lt;p&gt;Logs não foram feitos pra explicar fluxos. Eles servem pra registrar eventos pontuais. Quem te entrega o &lt;strong&gt;fluxo completo&lt;/strong&gt; de uma operação são os &lt;strong&gt;traces&lt;/strong&gt;. E se você ainda não sabe a diferença, esse é o problema que você precisa resolver hoje.&lt;/p&gt;




&lt;h2&gt;
  
  
  O pecado capital: log como narrador de novela
&lt;/h2&gt;

&lt;p&gt;Olha se você nunca escreveu algo parecido com isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entrou no método ProcessarPagamento"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Validando dados do cliente {ClienteId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chamando API de Pagamento com valor {Valor}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pagamento retornou sucesso"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Saiu do método ProcessarPagamento"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parece responsável. Parece "boa prática". &lt;strong&gt;Não é.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Esse código tem 4 problemas graves que ninguém te conta:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Zero correlação entre as linhas
&lt;/h3&gt;

&lt;p&gt;Em produção, com 500 requests por segundo, essas 5 linhas vão estar &lt;strong&gt;embaralhadas com outras 50.000&lt;/strong&gt; de outros usuários. Boa sorte tentando montar a sequência de um pedido específico sem um &lt;code&gt;TraceId&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Você está pagando caro por lixo
&lt;/h3&gt;

&lt;p&gt;Datadog, New Relic, CloudWatch, Splunk: todos cobram por &lt;strong&gt;volume ingerido&lt;/strong&gt;. Cada &lt;code&gt;LogInformation("Entrou no método X")&lt;/code&gt; é dinheiro real saindo da sua conta. Já vi empresa gastar R$ 80k/mês em log que ninguém nunca leu.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. PII e vazamento de dados (LGPD chegou em todo mundo)
&lt;/h3&gt;

&lt;p&gt;Quando o dev coloca &lt;code&gt;logger.LogInformation("Request: {@Request}", request)&lt;/code&gt; achando que está sendo esperto, ele acabou de &lt;strong&gt;logar CPF, cartão, token JWT e senha&lt;/strong&gt; em texto puro. Auditoria vai te encontrar. A LGPD também.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Logs não contam história, contam fofoca
&lt;/h3&gt;

&lt;p&gt;Você sabe &lt;strong&gt;que&lt;/strong&gt; algo aconteceu, mas não sabe &lt;strong&gt;quando&lt;/strong&gt;, &lt;strong&gt;quanto tempo demorou&lt;/strong&gt;, &lt;strong&gt;o que veio antes&lt;/strong&gt; nem &lt;strong&gt;o que veio depois&lt;/strong&gt;. É como tentar entender um filme só pelas legendas, fora de ordem.&lt;/p&gt;




&lt;h2&gt;
  
  
  A virada de chave: traces
&lt;/h2&gt;

&lt;p&gt;Trace é uma &lt;strong&gt;árvore&lt;/strong&gt;. Cada operação (uma chamada HTTP, um query no banco, uma publicação no Kafka) é um &lt;strong&gt;span&lt;/strong&gt;, e os spans se conectam formando o fluxo completo de uma requisição, mesmo quando ela atravessa 8 microsserviços diferentes.&lt;/p&gt;

&lt;p&gt;Com trace você responde perguntas que log nunca vai conseguir responder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Por que esse pedido demorou 4 segundos?" → você vê &lt;strong&gt;exatamente&lt;/strong&gt; qual span travou.&lt;/li&gt;
&lt;li&gt;"O bug está no meu serviço ou no fornecedor?" → o trace mostra a cadeia inteira.&lt;/li&gt;
&lt;li&gt;"Quantos % das requests estão lentas por causa do banco?" → métricas derivadas do span do EF Core.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E o melhor: você instrumenta &lt;strong&gt;uma vez&lt;/strong&gt;, com OpenTelemetry, e exporta pra qualquer backend (Jaeger, Tempo, Datadog, Honeycomb, Azure Monitor). &lt;strong&gt;Sem vendor lock-in.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands-on: o mesmo cenário, agora bem feito (.NET 8 + OpenTelemetry)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup mínimo no &lt;code&gt;Program.cs&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenTelemetry.Resources&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenTelemetry.Trace&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="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MinhaApp.Pagamento"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MinhaApp.Pagamento"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClientInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEntityFrameworkCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// exporta pro coletor que você quiser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto. Toda chamada HTTP de entrada, toda chamada HTTP de saída e toda query no EF Core &lt;strong&gt;já estão sendo rastreadas automaticamente&lt;/strong&gt;. Você nem precisa escrever código pra isso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrumentação manual onde importa
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Telemetry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ActivitySource&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MinhaApp.Pagamento"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PagamentoService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PagamentoService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PagamentoService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PagamentoService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Telemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"processar-pagamento"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;ActivityKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Internal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// tags = metadados consultáveis no backend de observabilidade&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pedido.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cliente.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"valor.total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"moeda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"BRL"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;AddEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ActivityEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"validando-cliente"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ValidarClienteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clienteId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;AddEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ActivityEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"chamando-gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ActivityTagsCollection&lt;/span&gt; 
                &lt;span class="p"&gt;{&lt;/span&gt; 
                    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Stripe"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"endpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"POST /v1/charges"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}));&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/charges"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http.status_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"response.size"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActivityStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Gateway recusou pagamento"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="c1"&gt;// log SÓ aqui, no erro, e SEM payload sensível&lt;/span&gt;
                &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pagamento recusado para pedido {PedidoId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActivityStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;RecordException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActivityStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// deixa o ASP.NET Core lidar&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que esse código faz que o anterior não fazia
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Antes (logs)&lt;/th&gt;
&lt;th&gt;Depois (traces)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5 linhas soltas no arquivo&lt;/td&gt;
&lt;td&gt;1 span estruturado com início, fim e duração&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nenhuma correlação entre serviços&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TraceId&lt;/code&gt; propaga automaticamente via HTTP headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Busca textual com &lt;code&gt;grep&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Query estruturada por &lt;code&gt;pedido.id&lt;/code&gt;, &lt;code&gt;cliente.id&lt;/code&gt;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sem noção de tempo&lt;/td&gt;
&lt;td&gt;Latência precisa de cada etapa, em microssegundos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logger genérico vazando PII&lt;/td&gt;
&lt;td&gt;Tags controladas, sem campos sensíveis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custo proporcional ao volume bruto&lt;/td&gt;
&lt;td&gt;Custo proporcional a operações + sampling configurável&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  "E os logs, jogo fora?"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Não.&lt;/strong&gt; Logs continuam sendo úteis. O que muda é &lt;strong&gt;quando&lt;/strong&gt; você os usa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Use log pra:&lt;/strong&gt; eventos de negócio importantes (&lt;code&gt;"Pedido X cancelado pelo cliente"&lt;/code&gt;), erros inesperados, auditoria de ações sensíveis.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Não use log pra:&lt;/strong&gt; rastrear fluxo, medir performance, debugar requisição específica, "marcar que passou aqui".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A regra que eu sigo: &lt;strong&gt;se a informação faz parte da história de uma requisição, é trace. Se é um fato isolado que importa fora do contexto da requisição, é log.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E quando você precisar dos dois juntos? Correlacione pelo &lt;code&gt;TraceId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Pedido {PedidoId} cancelado. TraceId: {TraceId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;TraceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora, do dashboard de logs, você clica no &lt;code&gt;TraceId&lt;/code&gt; e cai direto no trace. &lt;strong&gt;Fim do &lt;code&gt;grep&lt;/code&gt; eterno.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A pirâmide da observabilidade (decora isso)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        🔺 Logs        ← eventos pontuais, raros, com contexto
       🔺🔺 Métricas    ← agregações, dashboards, alertas
      🔺🔺🔺 Traces      ← a base de tudo, o fluxo das requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quem começa pela base &lt;strong&gt;debuga em segundos&lt;/strong&gt;. Quem começa pelo topo &lt;strong&gt;debuga em horas&lt;/strong&gt;.&lt;/p&gt;




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

&lt;p&gt;Se você ainda está enchendo seu código de &lt;code&gt;LogInformation&lt;/code&gt; achando que está fazendo observabilidade, você está &lt;strong&gt;uma década atrás&lt;/strong&gt; do estado da arte. Trace não é luxo de Big Tech, é o &lt;strong&gt;mínimo&lt;/strong&gt; pra qualquer aplicação séria em 2026.&lt;/p&gt;

&lt;p&gt;Instala o OpenTelemetry hoje. Roda um Jaeger ou Tempo local. Vê seu sistema do jeito que ele realmente é: uma árvore de operações com latências, erros e dependências. Você nunca mais vai voltar pro &lt;code&gt;tail -f&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E aí, vai continuar caçando agulha no palheiro ou vai aprender a usar a ferramenta certa?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;💬 Se esse post fez sentido, deixa um comentário com o pior cenário de "debug por log" que você já viveu.&lt;/p&gt;

</description>
      <category>logs</category>
      <category>traces</category>
      <category>debug</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>🔍 Como tornei milhões de conversas históricas "invisíveis" buscáveis em tempo real</title>
      <dc:creator>Milton Camara</dc:creator>
      <pubDate>Mon, 11 May 2026 14:40:00 +0000</pubDate>
      <link>https://forem.com/lostdeveloper/como-tornei-milhoes-de-conversas-historicas-invisiveis-buscaveis-em-tempo-real-d15</link>
      <guid>https://forem.com/lostdeveloper/como-tornei-milhoes-de-conversas-historicas-invisiveis-buscaveis-em-tempo-real-d15</guid>
      <description>&lt;h2&gt;
  
  
  O problema
&lt;/h2&gt;

&lt;p&gt;Trabalhei recentemente em uma solução para um cenário que muita gente que &lt;br&gt;
mexe com sistemas em escala já enfrentou: um sistema gera diariamente &lt;br&gt;
arquivos JSON que vão para um storage ou qualquer outro banco como histórico, seguindo um padrão de nomenclatura previsível. Com o tempo, esses arquivos se acumulam aos milhões.&lt;/p&gt;

&lt;p&gt;A dor apareceu quando surgiu a necessidade de consultar esse histórico. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Três problemas surgiram ao mesmo tempo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Parte da informação que identifica cada arquivo nem sempre estava preenchida no nome, porque é um dado mutável e nem sempre estava disponível no momento da geração.&lt;/p&gt;

&lt;p&gt;As pessoas precisavam buscar por critérios variados, alguns estruturados, outros por palavra-chave dentro do conteúdo, e nada disso estava exposto no nome do arquivo&lt;/p&gt;

&lt;p&gt;Eram milhões de arquivos espalhados em containers diferentes, varrer linearmente era inviável. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Em resumo: tínhamos os dados, mas eram efetivamente invisíveis.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução
&lt;/h2&gt;

&lt;p&gt;Construí um pipeline em .NET 9 que varre o storage e indexa todo o conteúdo em um cluster Elasticsearch (rodando em Kubernetes via ECK &lt;br&gt;
Operator), com mapping otimizado para texto em português (analyzer com &lt;br&gt;
stemmer, asciifolding e stopwords em PT-BR). O resultado é busca textual &lt;br&gt;
em milhões de documentos em tempo real, mesmo quando o termo aparece &lt;br&gt;
apenas no meio de uma seção específica do conteúdo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Os pontos de design que fizeram diferença&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline producer/consumer com System.Threading.Channels&lt;/strong&gt; &lt;br&gt;
Producer lista arquivos paginado, workers em paralelo baixam e parseiam em streaming, e bulk indexer envia batches pro Elastic. Channels bounded controlam pressão de memória.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parser tolerante a dados sujos&lt;/strong&gt;&lt;br&gt;
Os arquivos históricos vinham com toda sorte de inconsistência: HTML entities, campos ausentes, datas inválidas, números como string, ordem de seções variável. Implementei discriminated union ParseResult (Success/Skipped/Failed) com helpers TryGet defensivos. &lt;br&gt;
Política: indexa o que conseguiu, descarta apenas o irrecuperável, nunca derruba o pipeline por um arquivo ruim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Busca híbrida em duas camadas&lt;/strong&gt;&lt;br&gt;
Quando o termo digitado tem um padrão estruturado, a API consulta o próprio storage (prefix lookup nativo, extremamente rápido) e o Elastic em paralelo. Resultados unidos: hits do storage no topo, hits textuais abaixo. O melhor de dois mundos sem latência adicional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boosts diferenciados por campo no Elastic&lt;/strong&gt;&lt;br&gt;
Match em campos estruturados tem boost maior. Match em texto solto tem boost menor. Resultado: busca por uma palavra específica sobe primeiro quem tem essa palavra em campo identificador, e só depois quem mencionou em texto livre.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estratégia de alias para reindexação sem downtime&lt;/strong&gt;&lt;br&gt;
Cada carga gera um índice físico novo (com timestamp no nome) e o alias é trocado atomicamente no final. Permite reprocessar o histórico inteiro sem afetar produção.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkpointing stateless&lt;/strong&gt;&lt;br&gt;
Cada partição tem seu progresso salvo em storage externo. Se o pod morrer no meio da carga, retoma exatamente de onde parou. Sem volumes persistentes, sem estado local.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights com fragmentos contextuais&lt;/strong&gt;&lt;br&gt;
Quando a busca casa em texto livre, o Elastic retorna trechos do conteúdo com a palavra encontrada destacada. A interface mostra o contexto direto na lista de resultados, sem precisar abrir cada item para descobrir por que ele apareceu.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 O papel da IA nesse processo
&lt;/h2&gt;

&lt;p&gt;Não vou fingir que cheguei nessa arquitetura sozinho em uma tarde. Usei &lt;br&gt;
LLM como par técnico ao longo de todo o caminho, e a forma como usei &lt;br&gt;
fez toda a diferença no resultado.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onde a IA ajudou de verdade&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brainstorming de trade-offs arquiteturais&lt;/strong&gt;&lt;br&gt;
"Mensageria entre processos faz sentido aqui ou é overhead?", "Cursor pagination ou from/size pra infinite scroll?", "Alias com swap ou reindex in-place?". &lt;br&gt;
Em vez de só pedir resposta, debati cada decisão e pesei prós e contras. A IA é excelente como sparring de design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Análise crítica do código existente&lt;/strong&gt;&lt;br&gt;
O projeto inicial tinha mais peças do que precisava. Pedi uma análise técnica completa, code smells, dead code, bugs latentes, aderência a boas práticas. Saiu um documento de diagnóstico priorizado por severidade. Decidi simplificar a arquitetura com base nesse diagnóstico.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modelagem do parser tolerante&lt;/strong&gt;&lt;br&gt;
Os cenários de dados sujos foram enumerados de forma exaustiva e cada um virou um caso de teste antes da implementação. Test-driven, mas com IA gerando o checklist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mapping do Elasticsearch&lt;/strong&gt;&lt;br&gt;
Definir analyzer correto pra português, decidir entre tipos de campo, projetar nested vs flat, é o tipo de decisão onde experiência prévia conta muito. Conversar com a IA acelerou em horas o que levaria dias de leitura de docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompts estruturados para agente de codificação&lt;/strong&gt; &lt;br&gt;
Quebrei a implementação em fases, cada uma com prompt detalhado contendo arquitetura alvo, decisões já tomadas, restrições, e ordem de execução. Resultado: o agente executou sem perder contexto e sem inventar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onde a IA NÃO substituiu o engenheiro&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decisões de negócio (volumes reais, padrões de acesso, regras de tratamento de exceções específicas do domínio)&lt;/li&gt;
&lt;li&gt;Validar suposições contra a realidade do projeto (rodar build, ler logs, executar queries pra verificar comportamento)&lt;/li&gt;
&lt;li&gt;Conhecer a infra real (capacidade dos volumes, classes de storage disponíveis, limites do cluster)&lt;/li&gt;
&lt;li&gt;Priorizar segurança (a IA aponta riscos, mas a decisão de parar tudo pra resolver é humana)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A lição mais importante&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;IA é multiplicador, não substituto. Quem souber fazer perguntas certas, validar respostas e contextualizar com a realidade do projeto extrai muito valor. Quem só pede "me dá a solução" recebe código genérico que não resolve o problema real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack utilizada
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;.NET 9 (Worker Service + ASP.NET Core)&lt;/li&gt;
&lt;li&gt;Azure Blob Storage (origem dos dados)&lt;/li&gt;
&lt;li&gt;Elasticsearch 8.x em Kubernetes via ECK Operator&lt;/li&gt;
&lt;li&gt;Azure Table Storage (checkpoint stateless)&lt;/li&gt;
&lt;li&gt;SDK oficial do Elastic (tipado)&lt;/li&gt;
&lt;li&gt;System.Threading.Channels (pipeline assíncrono in-process)&lt;/li&gt;
&lt;li&gt;Claude (sparring técnico e análise de código)&lt;/li&gt;
&lt;li&gt;Claude Code (execução guiada por prompts estruturados)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lições que valeram o post
&lt;/h2&gt;

&lt;p&gt;Dados reais são sujos. Investi mais tempo projetando tolerância a inconsistências do que no pipeline em si. E foi o tempo mais bem gasto, em produção, um único arquivo malformado não pode derrubar uma carga em larga escala.&lt;/p&gt;

&lt;p&gt;Mensageria nem sempre é a resposta. Para uma carga histórica única + ingestão futura via eventos do próprio storage, removi a camada de filas intermediária e simplifiquei para um indexer mais direto. Menos infra, menos custo, mais simples de testar.&lt;/p&gt;

&lt;p&gt;Storage tem capacidades subutilizadas. O prefix lookup nativo do storage é absurdamente rápido e barato. Combinar isso com Elastic para o resto deu uma estratégia híbrida que nenhum dos dois sozinho daria.&lt;/p&gt;

&lt;p&gt;Reindexar sem downtime é decisão arquitetural, não otimização. Resolver isso com alias desde o dia 1 me poupou de um problema futuro que apareceria do nada.&lt;/p&gt;

&lt;p&gt;IA não pensa por você, mas amplifica quem pensa. O diferencial está em fazer as perguntas certas, contextualizar com seu projeto e validar tudo na prática.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>elasticsearch</category>
    </item>
  </channel>
</rss>
